java内存区域(内存结构)与内存溢出异常(OOM)

1、jvm内存模型

JVM的内存模型有人也喜欢叫java的内存模型,下面就是显示图(在网上复制的,不自己动手画了,画有可能也没别人的好)

在这里插入图片描述
上面的图片很形象,每个线程都有自己的工作内存,工作内存可以看成主存的一部分副本(因为不可能主存里的数据它都需要),只需要它需要的放进工作内存供自己使用,当然这部分是私有的(也不确切,因为我们都是说线程是共享内存),只是说只有这个线程能操作这个工作内存,而对于内存中的数据,其他线程都可以拥有(因为工作内存里的也只是上面大的主存的副本)

但是如果第一个线程修改一个数据后另一个线程想访问修改后的数据怎么办?
这个只是副本,主存才是Leader,所以副本中的数据更改后就会和主存中的数据进行同步

如果两个一起同步呢?
这个就是多线程的问题了,当然这里可以用volatile关键字

上面主要描述了线层和主存之间的关系,可能让你有种误会,你可以这么理解,上面那个大的主存是进程持有的主存(这个图也可以是线程进程关于内存的关系):进程才有独立的内存单位,进程里的线程共享进程的内存。

2、JVM内存结构

这里是根据<深入理解java虚拟机 JVM高级特性与最佳实践>来写的,总结一下
当然下面的图是复制的:嘻嘻

在这里插入图片描述
jvm内存结构就是jvm运行时的数据区
看上面的图分为5部分:

  1. 程序计数器:可以看做是当前线程的所执行字节码的行号
  2. 虚拟机栈:存储局部变量表,操作数栈,动态链接,方法出口等
  3. 本地方法栈:存的是那些被Native修饰的底层源码
  4. 方法区:存储虚拟机加载的类信息,常量,静态变量,即时编译器编译的代码,jdk1.8已经将方法区取消,替代的是元数据区
  5. :存放对象实例,但也有例外逃逸分析,参考逃逸分析原理

下面将细讲上面一些最重要的部分

(1) 虚拟机栈

就像上图画的那样,里面存的是方法的栈帕,而栈帕存的是局部变量操作数栈动态连接方法的返回地址
平常我们经常会说两个一个是"",一个是"",这里的虚拟机栈就是我们常说的栈
异常两种(都是内存溢出)

  1. StackOverflowError:线程请求的栈的深度大于虚拟机规定的深度(单线程容易触发)
  2. OutOfMemoryError:栈动态扩展,如果扩展时无法申请到足够的内存时抛出(多线程容易触发)

详细的在博客下面

(2) 本地方法栈

这个和上面的虚拟机栈主要就是为了一个区分,有的虚拟机会把虚拟机栈和本地方法栈合在一起,比如Sun HotSpot虚拟机

========================================================

这做一个隔离,上面的虚拟机栈和本地方法栈加上没写的程序计数器都是私有的,而且生命周期和线程相同

(3) 方法区

存储已被虚拟机加载的类(class)信息 ,如常量静态变量即时编译器编译后的代码等

还有一个关键的运行时常量池:存放编译期生成的各种字面常量符号引用

jdk1.6jdk1.7永久区(1.6和1.7版本的方法区)可以使用参数 -XX:PermSize-XX:MaxPermSize指定

jdk1.8已经将方法区取消,替代的是元数据区
jdk1.8元数据区内存溢出:java.lang.OutOfMemoryError:Metaspace
jdk1.8的元数据区可以使用参数 -XX:MaxMetaspaceSzie 设定大小,这是一块堆外的直接内存,与永久区不同,如果不指定大小,默认情况下,虚拟机会耗尽可用系统内存

(4) 堆

堆基本上都是老生长谈了,你可以认为堆就是存放的对象的实例,但也不是绝对,上面就有一种:逃逸,可以去那个博客看看,挺好的。
堆也是垃圾收集器管理的主要区域,也被成为GC堆,关于GC 下一篇文章总结,
堆肯定也有内存溢出的问题,报java.lang.OutOfMemoryError: Java heap space
堆的大小可以设置,通过 -Xmx(最大) -Xms(最小) 来控制,动态扩展

(5) 直接内存

是上面图中没有的,并不是虚拟机运行时数据区的一部分,也不是java虚拟机定义的内存区域,但是它也会引发OutOfMemoryError,主要的任务是避免java堆Native堆来回复制数据,做一个缓冲区的作用

3、OutOfMemoryError异常

这个异常就是内存溢出,比如上面的和""在动态扩展时如果没有足够的内存就会抛出这个(或者超过限制的最大容量)

(1) 堆溢出

当对象数量达到最大堆的容量限制后就会报这个异常ava.lang.OutOfMemoryError: Java heap space,而且是很常见内存溢出异常情况(java中别的可能不多,但是对象肯定多,万物皆对象)

出现这种情况先分析是内存泄漏还是内存溢出,这两个还是有本质区
别的

  1. 内存泄漏:通俗点讲就是原本应该死掉对象竟然还活着,在占用着内存
  2. 内存溢出:里面的对象必须存在,但是堆的最大容量不够放下他们

堆内存溢出 :就要检查某些存在的对象是否生命周期过于长持有状态时间过长,减少这些来腾出堆的空间

通过 -Xmx 来设置最大堆的容量
通过 -Xms 来设置最小堆的容量,中间的就让堆动态扩展

(2) 本地方法栈和虚拟机栈

这两可以放在一起,就像Sun HotSpot 就把这两合成了一个

对于这两个如果出现内存溢出会有两种错误:

  1. StackOverflowError:线程请求的栈的深度大于虚拟机规定的深度(单线程容易触发)
  2. OutOfMemoryError:栈动态扩展,如果扩展时无法申请到足够的内存时抛出(多线程容易触发)

经过实验发现,用单线程栈帕太大(减少栈的深度)或者虚拟机栈容量不够报的都是StackOverflowError,但是多线程的情况下如果荣国栈的容量不够就会报OutOfMemoryError

解决办法:“减少内存”

  1. 对于栈的深度虚拟机默认的是1000~2000,对于正常的方法调用完全够了,
  2. 对于多线程导致的内存溢出,在不能减少线程或者更换64位虚拟机的情况下可以通过减少最大堆的容量(腾内存空间),减少栈的容量(因为一个线程一个栈,是私有的,所以可以减少每个私有栈的容量大小来创建更多的栈)
    当然可以通过 -Xss 参数来规定栈的大小

(3) 方法区和运行时常量池的溢出

这个在JDK1.8时就更改为元数据区,异常信息也变成了java.lang.OutOfMemoryError:Metaspace

它的溢出举个例子:对于CGLib这个动态代理通过继承父类进行代理,如果代理的太多,增强的类就多,那方法区存的class类的信息就越多,就会报这个错误
JDK1.8后,可以通过 -XX:MaxMetaspaceSzie 设定大小,默认耗尽系统内存,

(4) 本机直接内存的溢出

它也会抛OutOfMemoryError,后面有可能跟一个null,但是区别是这个是手动抛出,还没申请内存,通过计算得知内存不够,就手动抛出了,真正申请内存分配的方法是unsafe.allocateMemory()
可以通过**-XX:MaxDirectMemorySize指定大小,不指定默认和堆的 -Xmx一样大,
越过了
DirectByteBuffer类,直接通过反射获取Unsafe**实例进行内存分配,
DirectMemory 导致的内存溢出,一个明显特征是 Heap Dump文件中 不会看见明显的异常,Dump文件也很小,而程序 直接或者间接使用NIO,那就可以考虑是不是这方面溢出的问题

发布了213 篇原创文章 · 获赞 22 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_43113679/article/details/100087650