阅读这篇文章前需要对JVM虚拟机内存结构有一定的概念,参考JVM(一)JVM虚拟机内存结构 和 JAVA内存模型(JMM)
很好的一篇文章,转载了Java堆内存又溢出了!教你一招必杀技_李振良的技术博客的技术博客_51CTO博客
JAVA堆内存管理是影响性能主要因素之一。
堆内存溢出是JAVA项目非常常见的故障,在解决该问题之前,必须先了解下JAVA堆内存是怎么工作的。
一、堆的内存划分结构
先看下JAVA堆内存是如何划分的,如图:
- JVM内存划分为 堆内存 和 非堆内存 ;堆内存分为年轻代(Young Generation)、老年代(Old Generation);非堆内存就一个永久代(Permanent Generation)。
- 年轻代又分为 Eden 和 Survivor 区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
- 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
- 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
元空间有注意有两个参数:
- MetaspaceSize :初始化元空间大小,控制发生GC阈值
- MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存
2、分代概念
(1)为什么分代?
将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及GC频率。针对分类进行不同的垃圾回收算法,对算法扬长避短。
(2)为什么survivor分为两块相等大小的幸存空间?
主要为了解决碎片化。如果内存碎片化严重,也就是两个对象占用不连续的内存,已有的连续内存不够新对象存放,就会触发GC。
移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
有了元空间就不再会出现永久代OOM问题了!
二、数据在堆内存中的生命周期
在堆中,每触发一次GC后存活的对象,存活年龄会+1
GC期间会停止所有线程等待GC完成
- 新生成的对象优先分配到年轻代Eden区,当Eden空间满了,触发Minor GC;存活下来的对象移动到Survivor中的From/To区其中一个,然后清空Eden区继续存放新的数据
- 当Eden再次存满后触发执行Minor GC ,会将存活在Eden区和上一次Survivor区(From/To)的对象移动到,另一个Survivor区(From/To),然后清空上次存放数据的Survivor区。Survivor区(From/To)是交替存储和清空的,这样保证了一段时间内总有一个survivor区为空。
- 经过多次Minor GC仍然存活的对象(分代年龄=15次)移动到老年代。
- 老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。
Minor GC : 清理年轻代
Major GC : 清理老年代
Full GC : 清理整个堆空间,包括年轻代和永久代
所有GC都会停止应用所有线程
第一次GC: 新生成的对象优先分配到年轻代Eden区,当Eden空间满了,触发Minor GC;存活下来的对象移动到Survivor(From)区,然后清空Eden区继续存放新的数据
第二次GC:当Eden再次满后,Minor GC会收集Eden区和Survivor(From)区两个,将存活下来的放入Survivor(To)区,清空Eden区和Survivor(From)区
第三次GC:当Eden再次满后,Minor GC会收集Eden区和Survivor(To)区两个,将存活下来的放入Survivor(From)区,清空Eden区和Survivor(To)区
第N次GC:当经过多次GC后,如果有对象存活年龄=15时,将该对象放入老年代
进入老年代的条件,触发下面任意情况都会进入老年代
- 大对象直接分配到老年代(-XX:PretenureSizeThreshold)
- 长期存活的对象分配老年代(-XX:MaxTenuringThreshold=15)
- 空间分配担保(-XX:+HandlePromotionFailure):检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小
- 动态对象年龄对象:如果在Survivor空间中相同年龄所有对象大小的综合大于Survivor空间的一般,年龄大于或等于该年龄的对象就可以直接进入老年代(-XX:TargetSurvivorRatio)
JVM堆内存常用参数
参数 | 描述 |
---|---|
-Xms | 堆内存初始大小,单位m、g |
-Xmx(MaxHeapSize) | 堆内存最大允许大小,一般不要大于物理内存的80% |
-XX:PermSize | 非堆内存初始大小,一般应用设置初始化200m,最大1024m就够了 |
-XX:MaxPermSize | 非堆内存最大允许大小 |
-XX:NewSize(-Xns) | 年轻代内存初始大小 |
-XX:MaxNewSize(-Xmn) | 年轻代内存最大允许大小,也可以缩写 |
-XX:SurvivorRatio=8 | 年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1 |
-Xss | 堆栈内存大小 |