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线程同时工作,在多核计算机上性能不错。
  • 并发性在整个回收期间线程与应用程序线程交替执行,不会完全阻塞应用程序执行。
  • 分代GCG1 属于一个分代收集器,并且同时兼顾着新生代和老年代。
  • 空间整理在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()使用并发回收

默认的情况下,显示GCSystem.gc()会使用传统的Full GC方式回收。哪怕你的参数中设置了-XX:+UseC1GC-XX:+UseConcMarkSweepGC两种方式也会忽略的。

-XX:+PrintGCDetails  -XX:+UseG1GC
或者
-XX:+PrintGCDetails  -XX:+UseConcMarkSweepGC

如果想要显示的GCSystem.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的异常。

JVM GC 学习笔记(一)
JVM GC 学习笔记(二)

参考书籍:《实战Java虚拟机—JVM故障诊断与性能优化》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值