理解 JVM 工作机制(七) 内存分配和回收策略
内存分配和回收策略
对象的内存分配,从概念上讲,应该都是在堆上分配(而实际上也有可能经过即时编译后被拆散为标量类型并间接地在栈上分配)。在经典分代的设计下,新生对象通常会分配在新生代中,少数情况下(例如对象大小超过一定阈值)也可能会直接分配在老年代。对象分配的规则并不是固定的,《Java 虚拟机规范》并未规定新对象的创建和存储细节,这取决于虚拟机当前使用的是哪一种垃圾收集器,以及虚拟机中与内存相关的参数的设定。
对象优先在 Eden 区分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC
。
Minor GC vs Major GC/Full GC:
Minor GC:回收新生代(包括 Eden 和 Survivor 区域),因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
Major GC / Full GC: 回收老年代,出现了 Major GC,经常会伴随至少一次的 Minor GC,但这并非绝对。Major GC 的速度一般会比 Minor GC 慢 10 倍 以上。
在 JVM 规范中,Major GC 和 Full GC 都没有一个正式的定义,所以有人也简单地认为 Major GC 清理老年代,而 Full GC 清理整个内存堆。
大对象直接进入老年代
大对象是指需要大量连续内存空间的 Java 对象,最典型的如很长的字符串或元素数量庞大的数组
大对象对虚拟机来说是一个不折不扣的坏消息,比这个消息更坏的是短命的大对象
一个大对象能够存入 Eden 区的概率比较小,发生分配担保的概率比较大,而分配担保需要涉及大量的复制,就会造成效率低下。
虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。(新生代采用复制算法回收垃圾)
长期存活的对象将进入老年代
虚拟机为每个对象定义了一个对象年龄(age)计数器,存储在对象头
对象通常在 Eden 区诞生,如果经过第一次 Minor GC 后仍然存活,并且能被 Survivor 区容纳的话,该对象会移动到 Survivor 区,并且它的年龄计数器会+1,每经历过一次 Minor GC,对象年龄就增加 1 岁。当它的年龄超过到一定程度时(默认 15),就会移动到老年代。
-XX:MaxTenuringThreshold 设置晋升老年代阈值
动态对象年龄判定
HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
空间分配担保
JDK 6 Update 24 之前的规则是这样的:在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间, 如果这个条件成立,Minor GC 可以确保是安全的; 如果不成立,则虚拟机会查看 HandlePromotionFailure 值是否设置为允许担保失败, 如果是,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小, 如果大于,将尝试进行一次 Minor GC,尽管这次 Minor GC 是有风险的; 如果小于,或者 HandlePromotionFailure 设置不允许冒险,那此时也要改为进行一次 Full GC。
JDK 6 Update 24 之后的规则变为:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。
通过清除老年代中废弃数据来扩大老年代空闲空间,以便给新生代作担保。
这个过程就是分配担保。
总结一下有哪些情况可能会触发 JVM 进行 Full GC。
System.gc() 方法的调用此方法的调用是建议 JVM 进行 Full GC,注意这只是建议而非一定,但在很多情况下它会触发 Full GC,从而增加 Full GC 的频率。通常情况下我们只需要让虚拟机自己去管理内存即可,我们可以通过 -XX:+ DisableExplicitGC 来禁止调用 System.gc()。
老年代空间不足老年代空间不足会触发 Full GC 操作,若进行该操作后空间依然不足,则会抛出如下错误:
java.lang.OutOfMemoryError: Java heap space
永久代空间不足 JVM 规范中运行时数据区域中的方法区,在 HotSpot 虚拟机中也称为永久代(Permanet Generation),存放一些类信息、常量、静态变量等数据,当系统要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,会触发 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出如下错误信息:
java.lang.OutOfMemoryError: PermGen space
CMS GC 时出现 promotion failed 和 concurrent mode failurepromotion failed,就是上文所说的担保失败,而 concurrent mode failure 是在执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足造成的。
统计得到的 Minor GC 晋升到旧生代的平均大小大于老年代的剩余空间
版权声明: 本文为 InfoQ 作者【ue4】的原创文章。
原文链接:【http://xie.infoq.cn/article/adb2dd52bb125cc7b4b63d09a】。文章转载请联系作者。
评论