JVM GC
垃圾收集器-学习笔记
java支持的回收器种类
串行垃圾回收器
- 使用单线程进行垃圾回收,并且是独占式的垃圾回收。
- 适用场景:
- 在并行较弱的计算机上使用串行回收器,此回收器的专注性和独占性会有更好的体现。
- 可以在新生代
新生代串行回收器
和老年代老年代串行回收器
使用。
- 新生代串行回收器:使用
复制算法
,不存在线程切换的开销。逻辑处理高效。当VM在Client模式下,为默认垃圾收集器。- 老年代串行回收器:使用
标记压缩法
,在GC时耗时较长。可以和多种新生代收集器配合,同时可以作为CMS回收器的备用回收器。- 使用
-XX:+\-UseSerialGC
参数来切换新生代串行和老年代串行。-XX:+UseSerialGC
:新生代,老年代都用串行收集器。-XX:+UseParNewGC
:新生代使用ParNew回收器,老年代使用串行回收器。-XX:+UseParallelGC
:新生代使用ParalleGC回收器。老年代使用串行回收器。
并行垃圾回收器
基于串行回收器的改进。不再是单线程进行,而是多线程同时进行垃圾回收。
适用场景:
- 对于并行能力强的计算机,可以有效的提高GC的效率。
新生代ParNew回收器:
新生代串行回收器多线程化。使用复制算法。也是独占式的回收器,在GC时,由于多线程处理,停顿的时间会缩短,但是多线程也会带来性能的压力,其实际的表现也可能在某些场景下比串行回收器差。开启ParNew回收器的参数:
-XX:+UseParNewGC
新生代使用ParNew,老年代使用串行。
-XX:+UseConcMarkSweepGC
新生代使用ParNew,老年代使用CMS。一次ParNew的GC回收日志:
[GC [ParNew: 1720K->427K(7424K), 0.0025326 secs] 1720K->427K(9472K), 0.0026082 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
新生代ParallelGC回收器:
和ParNew回收器都是复制算法、多线程、独占式收集器。除此之外ParallelGC有一个特别重要的点:比较关注系统的吞吐量
新生代ParallelGC开启的参数:
-XX:+UseParallelGC:
新生代->ParallelGC,老年代->SerialGC。-XX:+UseParallelOldGC:
新生代->ParallelGC,老年代->ParallelOldGC。-XX:ParallelGCThreads
指定回收器在工作时的线程数量。注意:最好与CPU的数量相当,避免过多的线程数量,影响GC的性能,适得其反。当CPU数量大于8的时候,ParallelGCThreads的值等于3+((5 \* CPUCount)/8)
新生代ParallelGC提供两个控制系统吞吐量的参数:
-XX:MaxGCPauseMillis:
设置最大垃圾收集停顿时间。他的值>0的整数。在设置了ParallelGC时,设置了当前参数,JVM会调整JavaHeap的大小和一些其他的参数,尽可能保证停顿时间在设置的时间区间内。-XX:GCTimeRatio:
设置吞吐量的大小。他的值是一个0-100的值。其GC时间的计算方式:GCTimeRatio的值为n,则系统花费不超过(1/(1+n))的时间用于垃圾收集。默认GCTimeRatio的值为99
-XX:+UseAdaptiveSizePolicy:
打开自适应GC策略。新生代的大小,eden和survivior的比例,晋升老年代的年龄参数都会被自动调整,以达到一个平衡点。
注意:在手动调整优化比较困难的场合,可以使用自适应的方式,只需要指定 JVM的-Xmx,GCTimeRatio和MaxGCPauseMillis
,但是GCTimeRatio和MaxGCPauseMillis是互相矛盾的,系统吞吐量会随着停顿时间的减少而减少。一次ParallelGC的日志参数:
[GC [PSYoungGen: 1661K->592K(7168K)] 1661K->600K(9216K), 0.0025245 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
老年代ParallelOldGC 回收器:
多线程并发、关注吞吐量的垃圾收集器。和ParallelGC搭配使用。老年代ParallelOldGC参数设置:
-XX:+UseParallelOldGC:
新生代使用ParallelGC,老年代使用ParallelOldGC。一次ParallelOldGC的日志:
[Full GC [PSYoungGen: 512K->0K(4608K)] [ParOldGen: 32K->378K(5120K)] 544K->378K(9728K), [Metaspace: 3137K->3137K(1056768K)], 0.0048941 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
CMS(Concurrent Mark Sweep)回收器
CMS回收器
主要关注于系统的停顿时间。其使用的是标记清除法,并且是一个多线程并行的垃圾回收器。
CMS的主要工作步骤
图1-1
上图1-1则表示了CMS的执行过程,以及每一步的作用*
CMS的主要的设置参数:
-XX:-CMSPrecleaningEnabled
关闭预清理操作,在CMS时不实行预清理操作。-XX:+UseConcMarkSweepGC
启用CMS回收器。-XX:ConcGCThreads
\-XX:ParallelCMSThreads
设置CMS回收器的并发线程数量。设置合理的线程数量会对系统的性能产生很直接的影响。-XX:CMSInitiatingOccupancyFraction
默认是68(百分比,即68%)。指定CMS当前堆内存的使用阀值(当堆内存达到指定的使用阀值的时候,就开始进行CMS垃圾回收)。可以根据堆内存的增长速度的缓慢设置阀值,内存增长快时,则可以设置低阀值,避免出现CMS回收不及时,导致内存不足,使CMS回收失败。-XX:+UseCMSCompactAtFullCollection
设置CMS完成后,进行一次内存碎片整理。此操作不是并发操作。-XX:CMSFullGCsBeforeCompaction
设定执行n次MCS操作以后,进行一次内存压缩。-XX:+CMSClassUnloadingEnabled
设置在条件允许的情况下,系统使用CMS来清理Perm区的Class数据。
注意:并法收集器并非时并行收集器,不能混淆为一体。并发是指:垃圾收集器线程和应用系统线程交替执行。而并行是指:垃圾收集器在执行的时候,应用系统线程全程挂起。不存在交替执行的。
G1(Garbage-First)回收器
JDK1.7引进来的全新垃圾收集器。取代CMS收集器。
从分代上看
,属于一个分代收集器。(用于新生代,老年代,并且拥有eden和survivor)。
从堆结构上看
,属于一个分区算法的收集器。
G1回收器的特点:
并行性
在回收期间,可以由多个GC线程同时工作,在多核计算机上性能不错。并发性
在整个回收期间线程与应用程序线程交替执行,不会完全阻塞应用程序执行。分代GC
G1 属于一个分代收集器,并且同时兼顾着新生代和老年代。空间整理
在GC过程中,会进行适当的对象移动。每次GC完成以后都会复制有效的对象,减少空间碎片。可预见性
由于分区的原因,G1可以在回收时仅选取部分的区域进行回收。这样就避免了回收时系统的停顿时间。
G1的收集过程:
新生代GC
主要回收新生代的eden和survivor区。并发标记周期
并发标记周期和CMS相似,有以下几点:
初始标记
标记根节点直接可达的对象。并且会产生一次全局停顿的新生代GC。(系统停顿)跟区域扫描
扫描由servitor区直接可达的老年代区域。(并发执行)并发标记
扫面全堆存活的对象,并对其进行标记。(这个过程会被一次新生代的GC打断)(并发执行)重新标记
对并发标记进行一次修正以及补充。在G1中,这个过程使用SATB(Snapshot-At-The-Beginning)
算法完成。G1会在初次标记时为每个存活的对象存储一个快照。这个快照有助于加快重新标记的速度。(系统停顿)独占停顿
这个阶段会引起停顿,会进行各个区域的存活对象和回收比例并且进行排序,识别可供混合回收的区域。(系统停顿)并发清理阶段
识别并清理完全空闲的对象内存区域。并且是一个并发的操作,不会引起系统停顿。(并发执行)混合回收
针对已经标记明确的垃圾比例较高的区域进行回收。这个阶段会同时对年轻代和老年代进行GC。这个阶段被清理后的区域中的存活对象会被迁移到其他区域。在需要的情况下,可以进行一次Full GC
由于在收集的过程中,GC线程和系统程序不断的交替执行。因此不能避免会出现在回收过程中出现内存不足的情况。当出现这种情况时,系统则会进行一次Full GC。
G1相关的运行参数:
-XX:+UseG1GC
使用G1收集器。-XX:MaxGCPauseMillis
指定每次GC时,系统最大的停顿时间。此参数会影响新生代、老年代的比例,以及调整堆的大小,调整晋升年龄。-XX:ParallelGCThreads
用于设置并行回收时,GC的工作线程数量。-XX:initiatingHeapOccupancyPercent
用于指定当整个堆使用率达到多少时,出发并发标记周期的执行。默认是45,即45%。
一次G1进行GC的日志分析:
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0022208 secs] [Parallel Time: 1.3 ms, GC Workers: 8] [GC Worker Start (ms): Min: 8202.8, Avg: 8203.0, Max: 8203.1, Diff: 0.2] [Ext Root Scanning (ms): Min: 0.4, Avg: 0.5, Max: 0.6, Diff: 0.2, Sum: 4.3] [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0] [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] [Object Copy (ms): Min: 0.4, Avg: 0.5, Max: 0.6, Diff: 0.2, Sum: 3.6] [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] [Termination Attempts: Min: 1, Avg: 4.4, Max: 6, Diff: 5, Sum: 35] [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1] [GC Worker Total (ms): Min: 0.9, Avg: 1.0, Max: 1.1, Diff: 0.2, Sum: 8.1] [GC Worker End (ms): Min: 8204.0, Avg: 8204.0, Max: 8204.0, Diff: 0.0] [Code Root Fixup: 0.0 ms] [Code Root Purge: 0.0 ms] [Clear CT: 0.2 ms] [Other: 0.8 ms] [Choose CSet: 0.0 ms] [Ref Proc: 0.5 ms] [Ref Enq: 0.0 ms] [Redirty Cards: 0.2 ms] [Humongous Register: 0.0 ms] [Humongous Reclaim: 0.0 ms] [Free CSet: 0.0 ms] [Eden: 1024.0K(1024.0K)->0.0B(3072.0K) Survivors: 1024.0K->1024.0K Heap: 5206.3K(20.0M)->4698.2K(20.0M)] [Times: user=0.01 sys=0.00, real=0.01 secs] [GC concurrent-root-region-scan-start] [GC concurrent-root-region-scan-end, 0.0004071 secs] [GC concurrent-mark-start] [GC concurrent-mark-end, 0.0000444 secs] [GC remark [Finalize Marking, 0.0002250 secs] [GC ref-proc, 0.0000386 secs] [Unloading, 0.0004214 secs], 0.0008206 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [GC cleanup 5845K->5845K(20M), 0.0004485 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
内存分配和回收的细节问题
禁用 System.gc()
System.gc()一般会出发Full GC,对老年代和年轻代同时进行回收。
public final class System { /** * Runs the garbage collector. * <p> * Calling the <code>gc</code> method suggests that the Java Virtual * Machine expend effort toward recycling unused objects in order to * make the memory they currently occupy available for quick reuse. * When control returns from the method call, the Java Virtual * Machine has made a best effort to reclaim space from all discarded * objects. * <p> * The call <code>System.gc()</code> is effectively equivalent to the * call: * <blockquote><pre> * Runtime.getRuntime().gc() * </pre></blockquote> * * @see java.lang.Runtime#gc() */ public static void gc() { Runtime.getRuntime().gc(); } } <--------------------------------------------------------------------------------> public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Runs the garbage collector. * Calling this method suggests that the Java virtual machine expend * effort toward recycling unused objects in order to make the memory * they currently occupy available for quick reuse. When control * returns from the method call, the virtual machine has made * its best effort to recycle all discarded objects. * <p> * The name <code>gc</code> stands for "garbage * collector". The virtual machine performs this recycling * process automatically as needed, in a separate thread, even if the * <code>gc</code> method is not invoked explicitly. * <p> * The method {@link System#gc()} is the conventional and convenient * means of invoking this method. */ public native void gc(); }
由上图的代码看出,显示GC方法的调用是一个native本地方法。这个本地方法最终实现是在jvm.cpp中:如下图所示:
JVM_ENTRY_NO_ENV(void,JVM_GC(void)) JVMWrapper("JVM_GC"); if(!DisableExplicitGC){ Universe::heap()->collect(GCCause::_java_lang_system_gc); } JVM_END
由上图中看出,如果设置了参数:
-XX:+DisableExplicitGC
,条件就不会成立,那么此时的System.gc()
将会是一个空函数的调用。
System.gc()使用并发回收
默认的情况下,显示GC
System.gc()
会使用传统的Full GC方式回收。哪怕你的参数中设置了-XX:+UseC1GC
和-XX:+UseConcMarkSweepGC
两种方式也会忽略的。-XX:+PrintGCDetails -XX:+UseG1GC 或者 -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC
如果想要显示的GC
System.gc()
使用并发的方式进行回收,则需要添加如下参数-XX:+PrintGCDetails -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent 或者 -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC -XX:+ExplicitGCInvokesConcurrent
配置了上述的参数,则可以改变默认的行为,使用显示的GC函数可以进行G1或者CMS的并发回收方式。
并行GC前额外出发的新生代的GC
对于并行回收器的Full GC,
-XX:+UseParallelOldGC
或者-XX:+UseParallelGC
,都会在Full GC之前进行一次新生代的GC。//一个简单的例子 public class GCQuestionDemo { public static void main(String[] args) { System.gc();//显示GC } } 启动参数: -XX:+PrintGCDetails -XX:+UseParallelGC 控制台输出 [GC (System.gc()) [PSYoungGen: 5243K->528K(76288K)] 5243K->536K(251392K), 0.0032774 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 528K->0K(76288K)] [ParOldGen: 8K->378K(175104K)] 536K->378K(251392K), [Metaspace: 3137K->3137K(1056768K)], 0.0075225 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] ..... 启动参数 -XX:+PrintGCDetails -XX:UseParallelOldGC 控制台输出 [GC (System.gc()) [PSYoungGen: 5243K->592K(76288K)] 5243K->600K(251392K), 0.0026578 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 592K->0K(76288K)] [ParOldGen: 8K->378K(175104K)] 600K->378K(251392K), [Metaspace: 3137K->3137K(1056768K)], 0.0086605 secs] [Times: user=0.04 sys=0.01, real=0.01 secs] .....
由上述例子中可以看出,每次Full GC之前都伴随着一次新生代的GC。
这次额外触发新生代的GC不是没有原因的:
这次额外的新生代GC主要是提前对新生代的垃圾进行一次收集回收,避免将新生代和老年代同时交给一次Full GC来进行回收。这样的做法可以缩短回收时系统停顿的时间。
如果不需要此特性,则可以输入以下参数取消这次额外的年轻代GC,
-XX:-ScavengeBeforeFullGC
,默认情况下,ScavengeBeforeFullGC的值为TRUE
。
对象何时进入老年代?
对象首次的创建会被存放在新生代中的Eden区中,如果没有GC的介入,对象会一直存在其中。
public class GCEdenDemo { public static final int _1K=1024; public static void main(String[] args) { for (int i = 0; i < 5*_1K; i++) { byte[] bytes = new byte[_1K]; } } } 启动参数 -Xmx64m -Xms64m -XX:+PrintGCDetails 控制台输出GC日志 Heap PSYoungGen total 18944K, used 7230K [0x00000007beb00000, 0x00000007c0000000, 0x00000007c0000000) eden space 16384K, 44% used [0x00000007beb00000,0x00000007bf20fa50,0x00000007bfb00000) from space 2560K, 0% used [0x00000007bfd80000,0x00000007bfd80000,0x00000007c0000000) to space 2560K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007bfd80000) ParOldGen total 44032K, used 0K [0x00000007bc000000, 0x00000007beb00000, 0x00000007beb00000) object space 44032K, 0% used [0x00000007bc000000,0x00000007bc000000,0x00000007beb00000) Metaspace used 3267K, capacity 4496K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K
上述例子中,Heap的内存大小设置为64m,上述的代码申请了5m的堆内存。期间没有发生GC,对象完整分配到Eden中,其余from,to,老年代均为使用。
此对象何时进入老年代?
当对象达到设定的晋升年龄时,此时对象则会由新生代晋升到老年代中去,这个晋升年龄的增加则是由每次对象经历的GC次数来决定。如果GC没有被回收,则年龄+1。
MaxTenuringThreshold
次参数是JVM提供设定最大的晋升年龄,默认是15 。
TargetSurvivorRatio
用于设置survivor区的目标使用率。默认为:50。即如果survivor区的对象使用率超过50%,那么JVM就可能会使用较小的age作为晋升年龄。此参数设置的越小,则会加快对象的晋升速度。import java.util.HashMap; import java.util.Map; public class MaxTenuringThreshold { public static final int _1M = 1*1024*1024; public static final int _1K = 1 * 1024; public static void main(String[] args) { Map<Integer, byte[]> map = new HashMap<>(); for (int i = 0; i < 5 * _1K; i++) { byte[] bytes = new byte[_1K]; map.put(i, bytes); } for (int k = 0; k < 17; k++) { for (int i = 0; i < 270; i++) { byte[] bytes = new byte[_1M]; } } } } 启动参数: -Xmx1024m -Xms1024m -XX:+PrintGCDetails -XX:MaxTenuringThreshold=15 -XX:+PrintHeapAtGC 控制台输出 {Heap before GC invocations=1 (full 0): PSYoungGen total 305664K, used 261616K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000) eden space 262144K, 99% used [0x00000007aab00000,0x00000007baa7c100,0x00000007bab00000) from space 43520K, 0% used [0x00000007bd580000,0x00000007bd580000,0x00000007c0000000) to space 43520K, 0% used [0x00000007bab00000,0x00000007bab00000,0x00000007bd580000) ParOldGen total 699392K, used 0K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000) object space 699392K, 0% used [0x0000000780000000,0x0000000780000000,0x00000007aab00000) Metaspace used 3308K, capacity 4496K, committed 4864K, reserved 1056768K class space used 364K, capacity 388K, committed 512K, reserved 1048576K [GC (Allocation Failure) [PSYoungGen: 261616K->6096K(305664K)] 261616K->6104K(1005056K), 0.0031630 secs] [Times: user=0.00 sys=0.02, real=0.00 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 305664K, used 6096K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000) eden space 262144K, 0% used [0x00000007aab00000,0x00000007aab00000,0x00000007bab00000) from space 43520K, 14% used [0x00000007bab00000,0x00000007bb0f4020,0x00000007bd580000) to space 43520K, 0% used [0x00000007bd580000,0x00000007bd580000,0x00000007c0000000) ParOldGen total 699392K, used 8K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000) object space 699392K, 0% used [0x0000000780000000,0x0000000780002000,0x00000007aab00000) Metaspace used 3308K, capacity 4496K, committed 4864K, reserved 1056768K class space used 364K, capacity 388K, committed 512K, reserved 1048576K } .....此处省略 {Heap before GC invocations=15 (full 0): PSYoungGen total 339968K, used 330190K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000) eden space 330752K, 99% used [0x00000007aab00000,0x00000007bed73b10,0x00000007bee00000) from space 9216K, 0% used [0x00000007bf700000,0x00000007bf700000,0x00000007c0000000) to space 9216K, 0% used [0x00000007bee00000,0x00000007bee00000,0x00000007bf700000) ParOldGen total 699392K, used 6344K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000) object space 699392K, 0% used [0x0000000780000000,0x0000000780632080,0x00000007aab00000) Metaspace used 3308K, capacity 4496K, committed 4864K, reserved 1056768K class space used 364K, capacity 388K, committed 512K, reserved 1048576K [GC (Allocation Failure) [PSYoungGen: 330190K->0K(339968K)] 336534K->6344K(1039360K), 0.0004944 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap after GC invocations=15 (full 0): PSYoungGen total 339968K, used 0K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000) eden space 330752K, 0% used [0x00000007aab00000,0x00000007aab00000,0x00000007bee00000) from space 9216K, 0% used [0x00000007bee00000,0x00000007bee00000,0x00000007bf700000) to space 8704K, 0% used [0x00000007bf780000,0x00000007bf780000,0x00000007c0000000) ParOldGen total 699392K, used 6344K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000) object space 699392K, 0% used [0x0000000780000000,0x0000000780632080,0x00000007aab00000) Metaspace used 3308K, capacity 4496K, committed 4864K, reserved 1056768K class space used 364K, capacity 388K, committed 512K, reserved 1048576K } Heap PSYoungGen total 339968K, used 186277K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000) eden space 330752K, 56% used [0x00000007aab00000,0x00000007b60e9528,0x00000007bee00000) from space 9216K, 0% used [0x00000007bee00000,0x00000007bee00000,0x00000007bf700000) to space 8704K, 0% used [0x00000007bf780000,0x00000007bf780000,0x00000007c0000000) ParOldGen total 699392K, used 6344K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000) object space 699392K, 0% used [0x0000000780000000,0x0000000780632080,0x00000007aab00000) Metaspace used 3314K, capacity 4496K, committed 4864K, reserved 1056768K class space used 365K, capacity 388K, committed 512K, reserved 1048576K
由上述日志看出,
- 在进行第一次GC开始之前,Eden使用了99%,所以导致GC的触发,为了存储新创建的对象,所以对Eden区进行一次清理,GC完成之后,可以看出Eden区已经被清空了,存活的对象全部存放在from中,存活的对象大约为6M左右,则可以看出刚好是我们存储的map对象。
- 到第15次时,from中的对象的晋升年龄已经达到15次,所以成功晋升到老年代,此时from中为0%,而老年代
ParOldGen total 699392K, used 6344K
则有6M左右,即为from中晋升的对象。注意MaxTenuringThreshold指的是最大晋升年龄,即达到该年龄时,对象必然晋升,什么时候都会存在偶然性,有时候对象未达到年龄时,对象也有可能会晋升。事实上,具体的晋升年龄还是会根据运行时环境因素,虚拟机自行判断。
大对象如何进入老年代?
除了晋升年龄的区别之外,对象的体积也会影响对象的晋升。在新生代存在大对象时,如果servitor区的内存容纳不下,不论此时的晋升年龄是否达到进入老年代,这个大对象则会直接晋升到老年代。
图1-2
图1-2表示了当出现的大对象新生代无法存放时,则会直接晋升到老年代。
-XX:PretenureSizeThreshold
用来设置对象直接晋升到老年代的阀值,单位是字节。设置此参数以后,只要对象的大小大于指定的阀值,则会直接晋升到老年代。默认值为0,一切有JVM运行时决定。
注意:此参数只会在串行回收器和ParaNew下有效。对ParallelGC无效。
下面的例子演示了PretenureSizeThreshold的使用
public class PretenureSizeThresholdDemo { public static final int _1k =1024; public static void main(String[] args) { Map<Integer, byte[]> map = new HashMap<>(); for (int i = 0; i < 5; i++) { byte[] bytes = new byte[1 * _1k * _1k]; map.put(i, bytes); } } } 启动参数配置:-Xmx32m -Xms32m -XX:+UseSerialGC -XX:+PrintGCDetails 控制台参数: Heap def new generation total 9792K, used 7111K [0x00000007be000000, 0x00000007beaa0000, 0x00000007beaa0000) eden space 8704K, 81% used [0x00000007be000000, 0x00000007be6f1e08, 0x00000007be880000) from space 1088K, 0% used [0x00000007be880000, 0x00000007be880000, 0x00000007be990000) to space 1088K, 0% used [0x00000007be990000, 0x00000007be990000, 0x00000007beaa0000) tenured generation total 21888K, used 0K [0x00000007beaa0000, 0x00000007c0000000, 0x00000007c0000000) the space 21888K, 0% used [0x00000007beaa0000, 0x00000007beaa0000, 0x00000007beaa0200, 0x00000007c0000000) Metaspace used 3165K, capacity 4496K, committed 4864K, reserved 1056768K class space used 347K, capacity 388K, committed 512K, reserved 1048576K
当没有使用PretenureSizeThreshold参数时,可以看到存储的对象都在新生代中。
启动参数配置:-Xmx32m -Xms32m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1024 控制台输出: Heap def new generation total 9792K, used 1939K [0x00000007be000000, 0x00000007beaa0000, 0x00000007beaa0000) eden space 8704K, 22% used [0x00000007be000000, 0x00000007be1e4da8, 0x00000007be880000) from space 1088K, 0% used [0x00000007be880000, 0x00000007be880000, 0x00000007be990000) to space 1088K, 0% used [0x00000007be990000, 0x00000007be990000, 0x00000007beaa0000) tenured generation total 21888K, used 5172K [0x00000007beaa0000, 0x00000007c0000000, 0x00000007c0000000) the space 21888K, 23% used [0x00000007beaa0000, 0x00000007befad060, 0x00000007befad200, 0x00000007c0000000) Metaspace used 3165K, capacity 4496K, committed 4864K, reserved 1056768K class space used 347K, capacity 388K, committed 512K, reserved 1048576K
使用了-XX:PretenureSizeThreshold参数以后,会发现大部分的内存全部晋升到老年代中去,细心的程序猿也会发现一个问题,那就是为何新生代中还会存在大于1024k的对象?
这是由于JVM在为Thread分配空间时,小的对象会优先使用TLAB中的空间。所以可以添加一个参数比变这个问题。
在TLAB上分配对象
TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存,是一个线程专用的内存分配区域。
TLAB的出现主要是加速线程内的对象分配,每个线程会拥有一块TLAB的区域。
上面的问题,在被内容可以解决。上述代码配置如下参数:
启动参数:-Xmx32m -Xms32m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1025 -XX:-UseTLAB 控制台输出: Heap def new generation total 9792K, used 680K [0x00000007be000000, 0x00000007beaa0000, 0x00000007beaa0000) eden space 8704K, 7% used [0x00000007be000000, 0x00000007be0aa410, 0x00000007be880000) from space 1088K, 0% used [0x00000007be880000, 0x00000007be880000, 0x00000007be990000) to space 1088K, 0% used [0x00000007be990000, 0x00000007be990000, 0x00000007beaa0000) tenured generation total 21888K, used 5914K [0x00000007beaa0000, 0x00000007c0000000, 0x00000007c0000000) the space 21888K, 27% used [0x00000007beaa0000, 0x00000007bf066a00, 0x00000007bf066a00, 0x00000007c0000000) Metaspace used 3175K, capacity 4496K, committed 4864K, reserved 1056768K class space used 349K, capacity 388K, committed 512K, reserved 1048576K
使用了
-xx:-UseTLAB
参数则会发现,主要是大于1024字节的对象都会被直接晋升到老年代中去。TLAB中的参数:
-XX:+PrintTLAB
显示TLAB的使用日志。-XX:-\+ResizeTLAb
禁用和启用JVM自动配置TLAB的大小。-XX:TLABSize
手动设置TLAB的大小下面会有一个对象分配的流程来说明TLAB的作用
图1-3
上图中则显示了一个对象出现时被分配空间的流程。
方法finalize()对垃圾对象的影响
此方法允许子类被重载,用于对象在被回收时进行资源地的释放。
目前不建议使用重载此方法:
- 会导致对象的复活。
- 方法执行的时间无法保证,都是依赖于GC的执行时间。
- 会影响GC的性能,会导致一些垃圾对象存储在堆中,长时间会导致OOM的异常。
参考书籍:《实战Java虚拟机—JVM故障诊断与性能优化》