JVM 理解(一)谈到了jvm工作机制、内存模型、以及内存的垃圾回收算法
今天继续深入,再谈谈jvm内存区域划分 和 哪些区域可能发生OutOfMemory
JVM内存区域组成部件分析
程序计数器(Program Counter Register)
在JVM规范中,每个线程都有它自己的程序计数器,任何时间一个线程都有一个方法在执行,有就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址,如果在执行本地方法,未未指定值。
Java虚拟机棧(Java Virtual Meachine Stack)
每个线程创建都会创建一个虚拟机棧,内部保存一个一个棧帧(Stack Frame) ,对应一次次的方法调用。
前边谈到程序计数器谈到了当前方法,同理在一个时间点,对应的只会有一个活动的棧帧,通常称为当前帧。方法所在的类叫当前类。如果在该方法中调用其它方法,对应的新的棧帧会创建出来,称为新的当前帧,一直到它返回结果或者执行结束。JVM直接对java棧的操作只有两个,即对棧帧的压棧和出棧。
棧帧中存储着局部变量表、操作数棧、动态链接、方法正常或者异常退出的定义。
堆(heap)
java内存管理的核心区。放置java对象实例,几乎所有创建的java对象实例都被直接分配在堆中。堆被所有的线程共享,在虚拟机启动时,制动的‘Xmx’等参数就是为了指定堆空间的指标。
堆也是垃圾回收器重点光顾的区域,堆空间还会被不同的垃圾收集器进一步细分,如分代划分
方法区(Method Area)
和堆一样,也是所有线程棧共享的一块内存区域,用于存储元数据 , 如 类结构信息、对应的运行时常量池、字段、方法代码等
另外,早期的Hotspot JVM实现,很多人习惯将方法区称为永久代,jkd8中将永久代移除,增加了元数据区。
运行时常量池(Run-Time Constant Pool)
方法区中谈到过,是属于方法区的一部分,分析反编译类文件能发现 版本号、字段、方法、超类、接口等信息,还有一项信息即常量池。java的常量池可以存放各种常量信息,无论是编译期生成的各种字面常量,还是需要在运行时决定的符号引用,它比一般的符号表存储的信息更加宽泛。
本地方法棧(Native Method Stack)
和java虚拟机非常相似,至此对本地方法的调用,每个线程都会创建一个,在Oracle Hotspot jvm中本地方法棧和java虚拟机棧是在同一块区域。
内存结构图总结
上边谈到了几个组成部分的概念和作用。一个图集成他们。
反映了实际中java进程内存占用,与规范中定义的JVM运行时数据区之间的差别。可以看作运行时数据区的超集,理论上的视角和现实中的视角是有区别的规范侧重通用、无差别部分,而应用开发者而言,只要是java进程在运行时会占用,都会影响到工程时间。
直接内存它就是Direct Buffer 所直接分配的内存,也是最容易出现问题的地方。在jvm工程师眼中,并不人为它是jvm内存中的一部分,也并未体现jvm内存模型当中。
jvm本身是本地程序,还需要其它内存区完成各种基本人为,比如 JIT Compiler在运行时,对热点地方进行编译,会将编译后的方法存储在code cache中。GC等功能需要运行在本地线程之中,类似部分都需要占用内存空间。这些都是实现JVM JIT功能的需要,但是在规范中未涉及。
关于OOM
JVM内存不够用时,会造成OOM,也就是说没有空闲空间,并且垃圾收集器也无法提供更多内存。
在抛出OutOfMemoryError之前,垃圾收集器会触发,尽可能区清理出空间。但不是在任何情况下,垃圾收集器都会被触发,比如分配一个超大对象,类似超级数组超过堆的最大值,JVM可以判断垃圾收集并不能解决这个问题,也会直接抛出OOM。从数据区的角度,除了程序计数器,其它区域都可能因为空间不足而产生OOM。
内存不足是OOM最常见诱因之一,产生不足的原因很多:可能是堆的大小不合理(比如处理比较客观的数据量,但是没有显示的指定堆的大小或者指定的太小);可能是内存泄漏问题;可能处理引用不及时导致堆积,内存无法释放
对于java虚拟机棧和本地方法棧,如果不断的进行递归调用而没有退出条件,会导致不断的压棧。JVM会抛出StackOverFlowError
老版本jdk永久代大小有限,jvm堆永久代回收非常不积极,当我们不断的添加新类型的时候,永久代出现OOM也会非常多,尤其在运行时存在大量动态类型生成的场合,类似Intern字符串缓存会占用空间,也会导致OOM:PermGen space;
随着元数据区域引入,方法内存已经不那么尴尬,堆相应的OOM也有所改善。OOM:Metaspace;
直接内存不足,也会造成OOM。首先,它不在堆上,所以Xmx之类的参数并不影响Direct Buffer等堆外成员的内存额度,在计算java可使用内存时,不能只考虑堆的需要,还要考虑直接内存等对外内存。如果出现内存不足的异常,也有可能是对外内存导致。
大多数垃圾收集过程中,不会主动去收集Direct Buffer,它的垃圾回收过程是基于Cleaner 和 幻引用对象机制实现的,本身不是public类型,内部实现了一个Deallocator负责销毁的逻辑,对他的销毁往往要拖到full gc,所以使用不当往往会造成oom。
如何分析OOM
首先要了解jvm内存。可以使用jconsole、visualvm等图形化工具,直观的观察java进程,掌握内存的使用情况。如JConsole可以直观的查看常见的堆内存和各种堆外内存的使用情况。
也能使用命令行进行分析,如 jstst和jmap等可以查看堆、方法区等。使用jmap等命令生成堆转储文件(Heap Dump),利用jhat或者eclipse mat等转储分析工具进行详细分析。
另外gc日志等输出,同样能看到很多信息
这里暂且提及一些理论后续会堆OOM的解决进行深入讨论
https://www.cnblogs.com/lishun1005/p/6019678.html