虚拟机采用的是HotSpot内核
对象分配规则
1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝。通过参数-XX:PretenureSizeThreshold=3145728控制。
3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,对象每熬过了1次Minor GC对象的年龄加1,达到阀值对象进入老年区。通过参数-XX:MaxTenuringThreshold=15(默认)控制。
4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold要求的年龄数。
5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。 (-XX:-HandlePromotionFailure)
术语说明:
Young Generation(新生代):分为:Eden区和Survivor区,Survivor区有分为大小相等的From Space和To Space。
Old Generation(老年代): 当 OLD 区空间不够时, JVM 会在 OLD 区进行 major collection。
Minor GC:新生代GC,指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。
Full GC/Major GC:发生老年代的GC,对整个堆进行GC。出现Major GC,经常会伴随至少一次Minor GC(非绝对)。MajorGC的速度一般比minor GC慢10倍以上。
规则一:对象优先在Eden分配
设置虚拟机参数为:-verbose:gc -Xms20M -Xmx20M -Xmn10M
-XX:SurvivorRatio=8 -XX:+PrintGCDetails
虚拟机提供了-XX:+PrintGCDetails 参数打印收集器日志,并且在进程退出时输出当前内存各区域的分配情况。
通过 -Xms20M -Xmx20M -Xmn10M这3个参数限制java堆大小为20MB,且不可扩展。其中10MB分配给新生代,剩下的10MB分配给老年代。
-XX:SurvivorRatio=8 决定了新生代中Eden区与一个Survivor区的比例为8:1,即Eden区=8MB,一个Survivor=1MB,另一个Survivor也为1MB。
/** * eden 对象通过分配担保机制提前转移到老年代去 * vm 参数 -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails */ public class MinorGC { private static final int _1MB=1024*1024; public static void testMinorGC(){ byte[] allocation1,allocation2,allocation3,allocation4; allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation4 = new byte[4 * _1MB]; //出现一次minor GC } public static void main(String[] args) { testMinorGC(); } }
控制台输出信息:
[GC [DefNew: 6471K->140K(9216K), 0.0074976 secs] 6471K->6284K(19456K), 0.0075433 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4400K [0x029f0000, 0x033f0000, 0x033f0000)
eden space 8192K, 52% used [0x029f0000, 0x02e18fd8, 0x031f0000)
from space 1024K, 13% used [0x032f0000, 0x03313208, 0x033f0000)
to space 1024K, 0% used [0x031f0000, 0x031f0000, 0x032f0000)
tenured generation total 10240K, used 6144K [0x033f0000, 0x03df0000, 0x03df0000)
the space 10240K, 60% used [0x033f0000, 0x039f0030, 0x039f0200, 0x03df0000)
compacting perm gen total 12288K, used 2105K [0x03df0000, 0x049f0000, 0x07df0000)
the space 12288K, 17% used [0x03df0000, 0x03ffe6e0, 0x03ffe800, 0x049f0000)
No shared spaces configured.
其中“[GC [DefNew:”后面内容为GC收集情况,DefNew为新生代GC
Heap 后面内容为进程退出时输出当前内存各区域的分配情况。
eden space 为eden区
from space 为Survivor区
tenured generation 为年老代
compacting perm gen为永久代(方法区)
日志分析:在执行方法testMinorGC()分配allocation4 对象语句时,会发生Minor GC ,这次GC结果是新生代由6471K变为140K(DefNew: 6471K->140K),而总内存占用量几乎没有减少(allocation1、2、3三个对象都是存活的,虚拟机没有找到可回收的对象)。
这次GC发生的原因是给allocation4分配内存的时候,发现eden已经被占用了6MB,剩余的空间不足以分配allocation4所需的4MB内存,因此发生Minor GC。GC期间虚拟机又发现已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。
这次GC结束后,4MB的allocation4对象被顺利分配到eden中。因此程序执行完的结果是eden占用4MB,Survivor空闲,老年代被占用6MB。
规则五:空间分配担保
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否是否大于老年代剩余空间的大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,则只进行Minor GC,如果不允许,则进行一次Full GC。
老年代进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年达对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多的空间。
如某次Minor GC存活后的对象突增,远高于平均值的话,会导致担保失败。如果出现了HandlePromotionFailure,那就只好在失败后重新发起一次Full GC。
/** * VM参数:-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure -XX:+PrintGCDetails */ public class HandlePromotiontest { private static final int _1MB = 1024 * 1024; @SuppressWarnings("unused") public static void testHandlePromotion() { byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7; allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation1 = null; allocation4 = new byte[2 * _1MB];//Minor GC allocation5 = new byte[2 * _1MB]; allocation6 = new byte[2 * _1MB]; allocation4 = null; allocation5 = null; allocation6 = null; allocation7 = new byte[2 * _1MB];//GC } public static void main(String[] args) { testHandlePromotion(); } }
设置HandlePromotionFailure时
[GC [DefNew: 6471K->140K(9216K), 0.0043721 secs] 6471K->4236K(19456K), 0.0044109 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6370K->6370K(9216K), 0.0000361 secs][Tenured: 4096K->4236K(10240K), 0.0045948 secs] 10466K->4236K(19456K), [Perm : 2086K->2086K(12288K)], 0.0047019 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
未设置HandlePromotionFailure时
[GC [DefNew: 6471K->140K(9216K), 0.0042261 secs] 6471K->4236K(19456K), 0.0042664 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6370K->140K(9216K), 0.0005921 secs] 10466K->4236K(19456K), 0.0006289 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
日志分析:
设置HandlePromotionFailure参数时:
在执行方法testHandlePromotion()给allocation4 分配对象时,会发生第一次 GC(一般第一次都为Minor GC) ,这次GC发生的原因是给allocation4分配内存的时候,发现eden已经被占用了6MB,剩余的空间不足以分配allocation4所需的2MB内存,因此发生Minor GC。而且已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。
第二次GC发生在给allocation7分配2MB对象时,此时eden已经被占用了6MB内存(allocation4、5、6),剩余内存不足需要转移到老年代。因为设置的是不允许担保失败,此时要进行一次Full GC。
注意:第二次GC开始时[GC DefNew: 6370K->6370K,进行minor GC并没有立即执行GC,先判断是否可以空间担保分配,不允许则执行Full GC。
未设置HandlePromotionFailure参数时:
第一次GC同上。
第二次GC发生在给allocation7分配2MB对象时,此时eden已经被占用了6MB内存(allocation4、5、6),剩余内存不足需要转移到老年代。虚拟机检测之前晋升到老年代的平均值(第一分配担保的值4MB即平均值)小于老年代剩余空间的大小。因为设置的是允许担保失败,只需进行一次minor GC。
相关推荐
如果使用 TLAB,在这一工作过程中也可以提前至 TLAB 分配时进行,这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用。 设置对象头 在初始化零值后,Jvm 需要对对象进行必要的设置,这个对象...
- **堆**:所有对象实例都在这里分配内存,也是GC的主要工作区域。 - **方法区**(或元空间):存储类信息、常量、静态变量等。 - **虚拟机栈**:每个线程都有一个独立的栈,用于存储方法调用的状态。 - **本地...
总之,《JVM性能调优-JVM内存整理及GC回收》是JAVA程序员提升自身技能,优化代码执行效率,解决内存问题的宝贵资源。通过深入学习,开发者可以更好地理解JVM的工作机制,从而编写出更加高效、稳定的Java应用程序。
本文主要围绕JVM内存区域的分配策略,尤其是对象在新生代(Young Generation)的Eden区分配,以及大对象直接进入老年代(Tenured Generation)的情况进行详细解释,并通过实例分析Minor GC和Full GC的区别。...
《JVM、GC详解及调优》是一份深入解析Java虚拟机(JVM)和垃圾收集(Garbage Collection,简称GC)的详细资料。本文将根据提供的信息,深入阐述JVM的工作原理,GC的机制以及如何进行JVM的性能调优。 首先,JVM是...
- **堆 (Heap)**: 存放对象实例,是JVM管理的最大一块内存。GC主要发生在堆中,通常会将堆细分为多个子区域,以便更高效地进行垃圾回收。 - **方法区域**: Hotspot JVM中的永久代(Permanent Generation),存放每个...
虚引用是最弱的一种引用关系,它不会对对象的生存时间构成影响,也无法通过虚引用来取得对象实例,仅用于在对象被回收时收到一个系统通知。 基本垃圾回收算法主要分为三种:引用计数、可达性分析清理和增量收集。...
堆内存是所有线程共享的一块区域,主要用于对象实例的分配;栈则用于存储方法调用时的局部变量和操作数;方法区存放类信息;程序计数器记录下一条指令的位置;本地方法栈服务于Java外的 native 方法。 垃圾收集(GC...
Java虚拟机(JVM)的垃圾回收(GC)机制是Java程序高效运行的关键部分,它自动管理内存,释放不再使用的对象以避免内存泄漏。本文主要探讨JVM堆内存的结构和GC的工作原理,以及如何进行性能调优。 JVM堆是Java应用...
- **堆**:所有对象实例以及数组都在这里分配内存,是JVM中最大的一块内存区域,支持垃圾回收。 - **栈**:每个线程都有一个独立的栈,用于存储方法调用的帧,包含局部变量表、操作数栈、动态链接和方法返回地址。...
- 新创建的对象首先在Eden空间分配,经过一次或多次GC后仍然存活的对象会被转移到Survivor空间或老年代。 - 这种设计基于观察结果:大多数对象很快就会死亡,只有少数会长期存活。 2. **GC类型** - Minor GC:...
Java堆是JVM所管理的内存中最大的一块,几乎所有的对象实例和数组都在这里分配内存。按照不同版本的Java,堆内存结构有所不同。在Java 8之前的版本中,堆内存分为新生代(New Generation)和老年代(Old Generation...
- **堆**:所有对象实例都在堆中创建,是JVM中最大的一块内存区域,也是线程共享的。在Java中,堆内存被进一步细分为新生代(Young Generation)、老年代(Old Generation)和永久代(PermGen,Java 8后被元空间...
- **对象**:IBM JVM中的对象由类实例组成,每个对象都有自己的属性和方法。 - **堆**:堆是IBM JVM中用于存储对象的区域,是GC的主要操作对象。 - 设置堆大小:可以通过命令行参数来设置初始堆大小和最大堆大小,...