GC基础
https://plumbr.io/handbook/what-is-garbage-collection
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
https://www.oracle.com/java/technologies/tuning-garbage-collection-v50-java-virtual-machine.html
https://www.oracle.com/java/technologies/visual-garbage-collection-monitoring-tool.html
Visual VM是jdk自带的一款可以监视垃圾收集过程过程的软件。我们可以使用该工具去观察JVM的GC过程。
找到jdk的安装目录下的bin目录,如下图jvisualvm.exe即为Visual VM,双击启动
由于Visual VM默认没有安装Visual GC插件,所以我们需要自己手动安装,安装步骤:点击工具-》插件-》可用插件-》勾选Visual GC-》安装,安装后默认为激活状态。在介绍如何使用之前,我们需要先了解一些基础知识。
什么是自动垃圾收集?自动垃圾收集是检查堆内存、确定哪些对象正在使用、哪些没有使用以及删除未使用的对象的过程。一个使用中的对象,或一个引用的对象,意味着你的程序的某些部分仍然保持一个指向那个对象的指针。未使用的对象或未引用的对象不再被程序的任何部分引用。因此,未引用对象使用的内存可以被回收。
在像C这样的编程语言中,分配和释放内存是一个手工过程。在Java中,回收内存的过程是由垃圾收集器自动处理的。基本过程可以描述如下。
这个过程的第一步叫做标记。垃圾收集器在这里标记哪些内存正在使用,哪些没有。
为了进一步提高性能,除了删除未引用的对象外,还可以压缩其余的引用对象。通过将引用的对象移动到一起,这使得新的内存分配更加简单和快速。
从对象分配行为中获得的信息可以用于增强JVM的性能。因此,堆被分解为更小的部分或代。堆的部分是:年轻代、老年代和永久代。
年轻代是所有新对象被分配和老化的地方。当年轻代填满时,这将导致一次较小的垃圾收集(又称次要垃圾收集【minor garbage collection】)。假设对象具有较高的死亡率,可以优化次要垃圾收集。当Eden区被对象充满时,会很快的经历一次垃圾收集,一些存活下来的对象被标记为老的对象,最终移动到老年代。
所有次要垃圾收集都是“停止世界”事件。这意味着所有应用程序线程都将停止,直到操作完成。次要垃圾收集总是停止世界事件。
老年代用于存储长期存活的对象。通常,为年轻代对象设置一个阈值,当满足该年龄时,对象将被移动到老年代。最终需要收集老年代。收集老年代的事件称为主要垃圾收集。
主要垃圾收集(major garbage collection)也是停止世界事件。通常,主要垃圾收集要慢得多,因为它涉及到所有活动的对象。因此,对于响应性应用程序【不断接受请求和发送结果的程序称响应应用程序,例如WEB程序等】,应该尽量减少主要垃圾收集。还请注意,主要垃圾收集的Stop the World事件的长度受老年代使用的垃圾收集器类型的影响。
永久代包含JVM描述应用程序中使用的类和方法所需的元数据。永久代是由JVM在运行时根据应用程序所使用的类填充的。另外,Java SE库类和方法可以存储在这里。
如果JVM发现不再需要某个类A,并且可能其他类需要占用的空间不足,则类A可能会被收集(卸载)。永久代包含在一个完整的垃圾收集中。
首先,将任何新对象allocated(分配)到Eden空间。两个幸存者空间开始都是空的。
被引用的对象将移到到第一个幸存者空间S0。清除Eden空间时,将删除那些未被引用的对象。
Eden在下一次要GC时,Eden空间依旧发生同样的事情,删除未引用的对象,并将引用的对象移到幸存者空间。但是在这种情况下,它们被移至第二个幸存者空间(S1)。此外,来自第一个幸存者空间(S0)上的对象也经过一次次要GC后,将存活下来的对象年龄+1,也移至S1。将所有尚存的对象移至S1之后,将同时清除S0和Eden。请注意,我们现在在幸存者空间中拥有不同年龄的对象。
Eden在下一次GC时,重复相同的过程。但是,这次幸存者空间又切换了。引用对象将移至S0,幸存的对象会继续老化。
下面这张图演示promotion(提升)过程,在一次次GC之后,当老化的对象达到某个年龄阈值(本例中为8)时,它们将从年轻代提升到老年代。
当次要GC继续运行时,达到阈值的对象将被继续提升到老年代。(下图是分配到年轻代->提升到老年代过程图)
下面的图几乎涵盖了年轻一代的整个过程。最后,将在老年代上执行一次主要GC,以清理和压缩空间。
在这里下载演示需要的案例。启动Visual VM后,启动我们的演示案例:
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
双击本地-Java2Demo.jar(pid...)后,出现右图,点击Visual GC即可查看到GC图。
在Eden不断被涨满的过程中,S0和S1不断的交替出现空白,直至Old涨满,内存溢出,程序结束,演示结束。
重新启动演示案例,我们切换如下图,在查看Visual GC会看到更明显的效果。
更多案例:你可以打开IDEA,监视IDEA启动到运行过程的内存使用(IDEA也是用java开发的)。
package gc; /** * 引用计数,引用环问题 */ public class ReferenceCountingGC { private Object instance = null; private static final int _1MB = 1024 * 1024; // 让对象创建的过程中在堆中创建点内存 private byte[] byteSize = new byte[_1MB * _1MB]; public static void main(String[] args) { ReferenceCountingGC referenceCountingGC1 = new ReferenceCountingGC(); ReferenceCountingGC referenceCountingGC2 = new ReferenceCountingGC(); referenceCountingGC1.instance = referenceCountingGC2; referenceCountingGC2.instance = referenceCountingGC1; System.gc(); } }
> java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=132446720 -XX:MaxHeapSize=2119147520 -XX:+PrintCommandLineFlags -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC java version "1.7.0_80" Java(TM) SE Runtime Environment (build 1.7.0_80-b15) Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
开关 |
描述 |
-Xms |
设置JVM启动时的初始堆大小。 |
-Xmx |
设置最大堆大小。 |
-Xmn |
设置年轻代的大小。 |
-XX:PermSize |
设置永久代的起始大小。 |
-XX:MaxPermSize |
设置永久代的最大大小 |
串行GC(The Serial GC)是JSE5/6中客户端计算机的默认设置。使用串行GC,次要和主要垃圾收集都是串行进行的(使用单个虚拟CPU)。此外,它使用标记-压缩集合方法,该方法将较旧的内存移到堆的开头,以便在堆的末尾将新的内存分配成一个连续的内存块。内存压缩使堆分配新内存块的速度更快。
对于大多数对 暂停时间 要求不高且运行在客户机类型机器上的应用程序来说,串行GC是首选的垃圾收集器。它只利用一个虚拟处理器来进行垃圾收集工作。不过,在当今的硬件上,串行GC可以有效地管理大量具有几百兆Java堆内存的重要应用程序,最坏情况下暂停时间也相对较短(对于完全的垃圾收集,大约只有几秒钟)。
串行GC的另一个流行用途是在相同机器上运行大量jvm的环境,在这样的环境中,当JVM进行垃圾收集时,最好只使用一个处理器,以最大限度地减少对其余JVM的干扰,不过这可能导致垃圾收集会持续的时间长些。
随着具有最小内存和少数核心的嵌入式硬件的激增,串行GC可能会卷土重来。
-XX:+UseSerialGC
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
// 我们在测试代码段上使用串行GC,查看打印日志信息 // VM options:-XX:+PrintGCDetails -XX:+UseSerialGC E:\jdk\jdk1.7.0_80\bin\java.exe -verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC "-javaagent:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\lib\idea_rt.jar=61471:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\bin" -Dfile.encoding=UTF-8 -classpath E:\jdk\jdk1.7.0_80\jre\lib\charsets.jar;E:\jdk\jdk1.7.0_80\jre\lib\deploy.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\access-bridge-64.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\dnsns.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\jaccess.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\localedata.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunec.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunjce_provider.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunmscapi.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\zipfs.jar;E:\jdk\jdk1.7.0_80\jre\lib\javaws.jar;E:\jdk\jdk1.7.0_80\jre\lib\jce.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfr.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfxrt.jar;E:\jdk\jdk1.7.0_80\jre\lib\jsse.jar;E:\jdk\jdk1.7.0_80\jre\lib\management-agent.jar;E:\jdk\jdk1.7.0_80\jre\lib\plugin.jar;E:\jdk\jdk1.7.0_80\jre\lib\resources.jar;E:\jdk\jdk1.7.0_80\jre\lib\rt.jar;E:\0_PROJECT\workspace\JVM\target\classes;E:\mvn\response\cglib\cglib\2.2.2\cglib-2.2.2.jar;E:\mvn\response\asm\asm\3.3.1\asm-3.3.1.jar gc.ReferenceCountingGC [Full GC[Tenured: 0K->524K(86272K), 0.0054502 secs] 2069K->524K(125056K), [Perm : 2932K->2932K(21248K)], 0.0055563 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 38912K, used 1731K [0x000000077c800000, 0x000000077f230000, 0x00000007a6a00000) eden space 34624K, 5% used [0x000000077c800000, 0x000000077c9b0d98, 0x000000077e9d0000) from space 4288K, 0% used [0x000000077e9d0000, 0x000000077e9d0000, 0x000000077ee00000) to space 4288K, 0% used [0x000000077ee00000, 0x000000077ee00000, 0x000000077f230000) tenured generation total 86272K, used 524K [0x00000007a6a00000, 0x00000007abe40000, 0x00000007fae00000) the space 86272K, 0% used [0x00000007a6a00000, 0x00000007a6a83288, 0x00000007a6a83400, 0x00000007abe40000) compacting perm gen total 21248K, used 2939K [0x00000007fae00000, 0x00000007fc2c0000, 0x0000000800000000) the space 21248K, 13% used [0x00000007fae00000, 0x00000007fb0dec98, 0x00000007fb0dee00, 0x00000007fc2c0000) No shared spaces configured.
并行垃圾收集器(The Parallel GC)使用多个线程来执行年轻代垃圾收集。默认情况下,在具有N个CPU的主机上,并行垃圾收集器在收集中使用N个垃圾收集器线程。垃圾收集器线程的数量可以通过命令行选项控制:
-XX:ParallelGCThreads=<desired number>
在具有单个CPU的主机上,即使已请求并行垃圾收集器,也会使用默认垃圾收集器。在具有两个CPU的主机上,并行垃圾收集器的性能通常与默认垃圾收集器相同,并且在具有两个以上CPU的主机上,可以预期减少年轻代垃圾收集器的暂停时间。
并行收集器也称吞吐量收集器。由于它可以使用多个CPU来加快应用程序吞吐量,当需要完成大量工作并且可以接受长时间暂停时,应使用此收集器。例如,批处理,打印报告或账单或执行大量的数据库查询。
使用此命令行选项,您将获得一个多线程年轻代收集器和一个单线程老年代收集器。该选项还对老年代进行单线程压缩。
java -Xmx12m -Xms3m -Xmn3m -XX:PermSize=20m -XX:MaxPermSize=20M -XX:+UseParallelGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
使用该参数,GC即可以是多线程的年轻代收集器,也可以是多线程的年老代收集器。它也是多线程压缩收集器。HotSpot只在老一代中进行压缩,HotSpot中年轻一代被认为是拷贝收集器,因此没有必要进行压缩。
压缩描述了在对象之间以不存在空隙的方式移动对象的行为。在垃圾收集清理之后,活动对象之间可能会留下一些空隙,压缩移动这些活动对象,使其没有剩余的空隙。垃圾收集器可能是非压缩收集器,因此,并行收集器与并行压缩收集器之间的差异可能是后者在垃圾收集清除之后压缩了空间,前者不会。
java -Xmx12m -Xms3m -Xmn3m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseParallelOldGC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
// 我们在测试代码段上使用并行GC,查看打印日志信息 // VM options:-XX:+PrintGCDetails -XX:+UseParallelGC E:\jdk\jdk1.7.0_80\bin\java.exe -verbose:gc -XX:+PrintGCDetails -XX:+UseParallelGC "-javaagent:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\lib\idea_rt.jar=61550:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\bin" -Dfile.encoding=UTF-8 -classpath E:\jdk\jdk1.7.0_80\jre\lib\charsets.jar;E:\jdk\jdk1.7.0_80\jre\lib\deploy.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\access-bridge-64.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\dnsns.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\jaccess.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\localedata.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunec.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunjce_provider.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunmscapi.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\zipfs.jar;E:\jdk\jdk1.7.0_80\jre\lib\javaws.jar;E:\jdk\jdk1.7.0_80\jre\lib\jce.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfr.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfxrt.jar;E:\jdk\jdk1.7.0_80\jre\lib\jsse.jar;E:\jdk\jdk1.7.0_80\jre\lib\management-agent.jar;E:\jdk\jdk1.7.0_80\jre\lib\plugin.jar;E:\jdk\jdk1.7.0_80\jre\lib\resources.jar;E:\jdk\jdk1.7.0_80\jre\lib\rt.jar;E:\0_PROJECT\workspace\JVM\target\classes;E:\mvn\response\cglib\cglib\2.2.2\cglib-2.2.2.jar;E:\mvn\response\asm\asm\3.3.1\asm-3.3.1.jar gc.ReferenceCountingGC [GC [PSYoungGen: 2665K->688K(38400K)] 2665K->688K(124416K), 0.0021792 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 688K->0K(38400K)] [ParOldGen: 0K->524K(86016K)] 688K->524K(124416K) [PSPermGen: 2932K->2931K(21504K)], 0.0149853 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] Heap PSYoungGen total 38400K, used 1664K [0x00000007d5e00000, 0x00000007d8880000, 0x0000000800000000) eden space 33280K, 5% used [0x00000007d5e00000,0x00000007d5fa00d8,0x00000007d7e80000) from space 5120K, 0% used [0x00000007d7e80000,0x00000007d7e80000,0x00000007d8380000) to space 5120K, 0% used [0x00000007d8380000,0x00000007d8380000,0x00000007d8880000) ParOldGen total 86016K, used 524K [0x0000000781a00000, 0x0000000786e00000, 0x00000007d5e00000) object space 86016K, 0% used [0x0000000781a00000,0x0000000781a83288,0x0000000786e00000) PSPermGen total 21504K, used 2959K [0x000000077c800000, 0x000000077dd00000, 0x0000000781a00000) object space 21504K, 13% used [0x000000077c800000,0x000000077cae3c78,0x000000077dd00000)
PSYoungGen、PSPermGen、ParOldGen分别是什么?
这里的PS指的是Parallel Scavenge收集器的缩写,它是并行GC收集器的一种。PSYoungGen表示年轻代并行垃圾回收,PSPermGen则表示永久代并行垃圾收集。ParOldGen这个Par是Parallel Old收集器的简写,它是Parallel Scavenge收集器的老年代版本,所以ParOldGen则表示老年代并行垃圾回收。
并发标记扫描(The Concurrent Mark Sweep 简称CMS)收集器(也称并发低暂停收集器)收集老年代,它试图通过与应用程序线程并发地执行大部分垃圾收集工作来最小化垃圾收集造成的暂停时间,通常并发低暂停收集器不会复制或压缩活动对象,在不移动活动对象的情况下进行垃圾收集。如果碎片化成为一个问题,请分配一个更大的堆。
内存碎片分为内部碎片和外部碎片,要想搞清楚内部碎片与外部碎片的区别首先要明白分页与分段。有关分段和分页的介绍请参见《程序员的自我修养》的1.5章节。分页是分段的更细粒度划分,分段可以解决地址隔离(程序运行时保护)和程序里地址和物理地址绑定的问题,但是不能解决内存使用效率问题,如果内存不足,被换入换出到磁盘的都是整个程序,这就造成了大量的磁盘访问操作,效率低下。
如上图,同一个段被不同进程访问,我们用Process1和Process2表示,对段进行更细粒度划分成多个页,例如如上图VP0-VP7,将该程序划分为7个页,根据程序的局部性原理,在某一小时间段,程序只会频繁的用到某一小块区域,我们没必要把整个段内的每一页都放到内存里,用到时才放,没用的就先留在磁盘,如上图Process1用到VP1,VP0,VP7,这三个页被放入内存,而VP3和VP2没用到就依旧留着磁盘DP1和DP0。如上图,可以看到虚拟空间的有些页被映射到同一个物理页,这样就实现内存共享。
外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。产生的原因:外部碎片是出于任何已分配区域或页面外部的空闲存储块。这些存储块的总和可以满足当前申请的长度要求,但是由于它们的地址不连续或其他原因,使得系统无法满足当前申请。
例如,我们假设现在总内存为100M,程序A运行需要10M内存,程序B运行需要70M内存,程序C运行需要40M内存。现在假设A和B在运行中,若想运行C,但由于剩下的这20M太小了,无法分配给C,这剩下的20M称为外部碎片。
内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被利用的空间。产生原因:由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免。
例如上图,即使我们进行了更细粒度的按页划分,如上图假设PP0页为512K大小,而VP0真实只使用了500K,即Process1使用了PP0,但是只用了500k,剩下的12k不能给Process1完全使用,这12k称为内部碎片。
CMS收集器应用于需要短暂停时间并可以与垃圾收集器共享资源的应用程序,例如响应事件的桌面UI应用程序、响应请求的Web服务器和响应查询的数据库。
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads=<n>
java -Xmx12m -Xms3m -Xmn3m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
// 我们在测试代码段上使用并行GC,查看打印日志信息 // VM options:-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC E:\jdk\jdk1.7.0_80\bin\java.exe -verbose:gc -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC "-javaagent:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\lib\idea_rt.jar=61625:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\bin" -Dfile.encoding=UTF-8 -classpath E:\jdk\jdk1.7.0_80\jre\lib\charsets.jar;E:\jdk\jdk1.7.0_80\jre\lib\deploy.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\access-bridge-64.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\dnsns.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\jaccess.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\localedata.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunec.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunjce_provider.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunmscapi.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\zipfs.jar;E:\jdk\jdk1.7.0_80\jre\lib\javaws.jar;E:\jdk\jdk1.7.0_80\jre\lib\jce.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfr.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfxrt.jar;E:\jdk\jdk1.7.0_80\jre\lib\jsse.jar;E:\jdk\jdk1.7.0_80\jre\lib\management-agent.jar;E:\jdk\jdk1.7.0_80\jre\lib\plugin.jar;E:\jdk\jdk1.7.0_80\jre\lib\resources.jar;E:\jdk\jdk1.7.0_80\jre\lib\rt.jar;E:\0_PROJECT\workspace\JVM\target\classes;E:\mvn\response\cglib\cglib\2.2.2\cglib-2.2.2.jar;E:\mvn\response\asm\asm\3.3.1\asm-3.3.1.jar gc.ReferenceCountingGC [Full GC[CMS: 0K->532K(86272K), 0.0279589 secs] 2070K->532K(125056K), [CMS Perm : 2973K->2972K(21248K)], 0.0280846 secs] [Times: user=0.02 sys=0.01, real=0.03 secs] Heap par new generation total 38912K, used 1038K [0x000000077c800000, 0x000000077f230000, 0x00000007914c0000) eden space 34624K, 3% used [0x000000077c800000, 0x000000077c903ba0, 0x000000077e9d0000) from space 4288K, 0% used [0x000000077e9d0000, 0x000000077e9d0000, 0x000000077ee00000) to space 4288K, 0% used [0x000000077ee00000, 0x000000077ee00000, 0x000000077f230000) concurrent mark-sweep generation total 86272K, used 532K [0x00000007914c0000, 0x0000000796900000, 0x00000007fae00000) concurrent-mark-sweep perm gen total 21248K, used 2979K [0x00000007fae00000, 0x00000007fc2c0000, 0x0000000800000000)
Java7中提供了Garbage First或称G1垃圾收集器,旨在长期替代CMS收集器。G1收集器是并行、并发、增量压缩的低暂停垃圾收集器,它的布局与前面描述的其他垃圾收集器完全不同。不在此详细讨论。
-XX:+UseG1GC
java -Xmx12m -Xms3m -XX:+UseG1GC -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
// 我们在测试代码段上使用并行GC,查看打印日志信息 // VM options:-XX:+PrintGCDetails -XX:+UseG1GC E:\jdk\jdk1.7.0_80\bin\java.exe -verbose:gc -XX:+PrintGCDetails -XX:+UseG1GC "-javaagent:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\lib\idea_rt.jar=61684:E:\IDEA\IntelliJ IDEA Community Edition 2020.2\bin" -Dfile.encoding=UTF-8 -classpath E:\jdk\jdk1.7.0_80\jre\lib\charsets.jar;E:\jdk\jdk1.7.0_80\jre\lib\deploy.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\access-bridge-64.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\dnsns.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\jaccess.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\localedata.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunec.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunjce_provider.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\sunmscapi.jar;E:\jdk\jdk1.7.0_80\jre\lib\ext\zipfs.jar;E:\jdk\jdk1.7.0_80\jre\lib\javaws.jar;E:\jdk\jdk1.7.0_80\jre\lib\jce.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfr.jar;E:\jdk\jdk1.7.0_80\jre\lib\jfxrt.jar;E:\jdk\jdk1.7.0_80\jre\lib\jsse.jar;E:\jdk\jdk1.7.0_80\jre\lib\management-agent.jar;E:\jdk\jdk1.7.0_80\jre\lib\plugin.jar;E:\jdk\jdk1.7.0_80\jre\lib\resources.jar;E:\jdk\jdk1.7.0_80\jre\lib\rt.jar;E:\0_PROJECT\workspace\JVM\target\classes;E:\mvn\response\cglib\cglib\2.2.2\cglib-2.2.2.jar;E:\mvn\response\asm\asm\3.3.1\asm-3.3.1.jar gc.ReferenceCountingGC [Full GC 1217K->524K(7168K), 0.0317178 secs] [Eden: 2048.0K(6144.0K)->0.0B(2048.0K) Survivors: 0.0B->0.0B Heap: 1218.0K(127.0M)->525.0K(7168.0K)], [Perm: 2932K->2932K(20480K)] [Times: user=0.02 sys=0.01, real=0.03 secs] Heap garbage-first heap total 7168K, used 524K [0x000000077c800000, 0x000000077cf00000, 0x00000007fae00000) region size 1024K, 1 young (1024K), 0 survivors (0K) compacting perm gen total 20480K, used 2956K [0x00000007fae00000, 0x00000007fc200000, 0x0000000800000000) the space 20480K, 14% used [0x00000007fae00000, 0x00000007fb0e3188, 0x00000007fb0e3200, 0x00000007fc200000) No shared spaces configured.
针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:
Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式
Mixed GC:只收集整个young gen以及部分old gen的GC。只有G1有这个模式
Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模式。
Major GC通常是跟Full GC是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的Full GC还是Old GC。
最简单的分代式GC策略,按HotSpot VM的serial GC的实现来看,触发条件是:
young GC:当young gen中的Eden区分配满的时候触发。注意yuong GC中有部分存活对象会晋升到Old gen(满年龄阈值),所以young GC后Old gen的占用量通常会有所升高。
full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其他能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC);或者System.gc()、heap dump带GC、默认也是触发full GC。
HotSpot VM里其他非并发GC的触发条件复杂一些,不过大致的原理与上面说的其实一样。
Parallel Scavenge框架下,默认是在要触发full GC前先执行一次young GC,并且两次GC之间能让应用程序稍微运行一小下,以期降低full GC的暂停时间(因为young GC会尽量清理了young gen的死对象,减少了full GC的工作量)。控制这个行为的VM参数是-XX:+ScavengeBeforeFullGC。这是HotSpot VM里的奇葩。
并发GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen做并发收集。