Java虚拟机内存模型
这里主要讲的是Java堆(Java Heap)
Heap是OOM故障最主要的发源地,它存储着几乎所有的实例对象,堆由垃圾收集器自动回收,堆区由各子线程共享使用
通常情况下,它占用的空间是所有内存区域中最大的,但如果无节制地创建大量对象,也容易消耗完所有的空间
堆的内存空间既可以固定大小,也可运行时动态地调整,通过如下参数设定初始值和最大值,比如
1
-Xms256M. -Xmx1024M
其中-X表示它是JVM运行参数
ms是memorystart的简称 最小堆容量
mx是memory max的简称 最大堆容量
但是在通常情况下,服务器在运行过程中,堆空间不断地扩容与回缩,势必形成不必要的系统压力,所以在线上生产环境中,JVM的Xms和Xmx设置成一样大小,避免在GC后调整堆大小时带来的额外压力
堆分成两大块:新生代和老年代
对象产生之初在新生代,步入暮年时进入老年代,但是老年代也接纳在新生代无法容纳的超大对象
新生代= 1个Eden区+ 2个Survivor区
绝大部分对象在Eden区生成,当Eden区装填满的时候,会触发Young GC。垃圾回收的时候,在Eden区实现清除策略,没有被引用的对象则直接回收。依然存活的对象会被移送到Survivor区,这个区真是名副其实的存在
Survivor 区分为S0和S1两块内存空间,送到哪块空间呢?每次Young GC的时候,将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,交换两块空间的使用状态
如果YGC要移送的对象大于Survivor区容量上限,则直接移交给老年代
假如一些没有进取心的对象以为可以一直在新生代的Survivor区交换来交换去,那就错了。每个对象都有一个计数器,每次YGC都会加1。
1
-XX:MaxTenuringThreshold
参数能配置计数器的值到达某个阈值的时候,对象从新生代晋升至老年代。如果该参数配置为1,那么从新生代的Eden区直接移至老年代。默认值是15,可以在Survivor 区交换14次之后,晋升至老年代
若Survivor区无法放下,或者超大对象的阈值超过上限,则尝试在老年代中进行分配;
如果老年代也无法放下,则会触发Full Garbage Collection(Full GC);
如果依然无法放下,则抛OOM.
堆出现OOM的概率是所有内存耗尽异常中最高的
出错时的堆内信息对解决问题非常有帮助,所以给JVM设置运行参数-
1
XX:+HeapDumpOnOutOfMemoryError
让JVM遇到OOM异常时能输出堆内信息
在不同的JVM实现及不同的回收机制中,堆内存的划分方式是不一样的
存放所有的类实例及数组对象
除了实例数据,还保存了对象的其他信息,如Mark Word(存储对象哈希码,GC标志,GC年龄,同步锁等信息),Klass Pointy(指向存储类型元数据的指针)及一些字节对齐补白的填充数据(若实例数据刚好满足8字节对齐,则可不存在补白)
特点
Java虚拟机所需要管理的内存中最大的一块.
堆内存物理上不一定要连续,只需要逻辑上连续即可,就像磁盘空间一样.
堆是垃圾回收的主要区域,所以也被称为GC堆.
堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的(通过-Xmx和-Xms控制),因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError.
线程共享
整个Java虚拟机只有一个堆,所有的线程都访问同一个堆.
它是被所有线程共享的一块内存区域,在虚拟机启动时创建.
而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个。