JVM垃圾回收(四)- GC算法:实现(1)
GC算法:实现
上面我们介绍了GC算法中的核心概念,接下来我们看一下JVM里的具体实现。首先必须了解的一个重要的事实是:对于大部分的JVM来说,两种不同的GC算法是必须的,一个是清理Young Generation的算法,另一种是清理Old Generation的算法。
在JVM里有各种各样的这种内置算法,如果你没有特别指定GC算法,则会使用一个默认的、适应当前平台(platform-specific)的算法。接下来我们会解释每种算法的工作原理。
下面的列表提供了一个快速的预览,关于哪些算法可能被结合使用。不过需要注意的是,它仅适用于Java 8,对应Java 8 之前的版本,可能稍有不同。

如果上表看起来很复杂,不要慌。在实际使用中,基本可以归结为上面表中标粗的部分。剩下的不是被弃用,就是不被支持,或是在实际场景中不实用。所以,下面我们仅仅会讨论下面的几种组合:
- Serial GC for both the Young and Old generation
- Parallel GC for both the Young and Old generation
- Parallel New for Young + Concurrent Mark and Sweep (CMS) for the Old Generation
- G1 in case of which the generation are not separated between the Young and Old
Serial GC
这种垃圾回收器在Young Generation使用mark-copy,在Old Generation使用mark-sweep-compact。正如它的名字一样,这两种收集器均是单线程的收集器,无法与当前的任务并行工作。这两种收集器均会触发stop-the-world pauses,暂时停止所有应用线程。
这种GC算法无法使用当前主流硬件上多核CPU的优点,不管有多少可用的CPU核数,JVM在GC阶段仅会使用一个核。可以通过指定以下配置应用此机制:
java -XX:+UseSerialGC com.company.testclass
这个选项仅推荐给:
- JVM中仅有几百MB的堆大小
- 运行的环境是单核CPU
对于大部分的服务端部署来说,很少会使用这种模式,因为大部分服务端的应用一般部署在多核平台,并不适合Serial GC的使用场景,会造成服务器资源的浪费。接下来我们看一下如果使用Serial GC 的话,那GC 收集器的日志会是什么形式。首先我们在JVM下开启GC日志:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
日志的输出类似以下内容:
2015-05-26T14:45:37.987-0200: 151.126: [GC (Allocation Failure) 151.126: [DefNew: 629119K->69888K(629120K), 0.0584157 secs] 1619346K->1273247K(2027264K), 0.0585007 secs] [Times: user=0.06 sys=0.00, real=0.06 secs]
2015-05-26T14:45:59.690-0200: 172.829: [GC (Allocation Failure) 172.829: [DefNew: 629120K->629120K(629120K), 0.0000372 secs]172.829: [Tenured: 1203359K->755802K(1398144K), 0.1855567 secs] 1832479K->755802K(2027264K), [Metaspace: 6741K->6741K(1056768K)], 0.1856954 secs] [Times: user=0.18 sys=0.00, real=0.18 secs]
这小部分GC日志可以提供我们很多信息,JVM内部当时发生了什么。具体地说,这部分日志片段反应了两轮GC,一个是清理Young Generation,另一个是清理整个堆。我们首先分析第一个在Young Generation发生的GC。
Minor GC
下面的日志片段包括了GC清理Young Generation时的一些信息:
2015-05-26T14:45:37.987-02001:151.1262:[GC3(Allocation Failure4) 151.126: [DefNew5:629119K->69888K6(629120K)7, 0.0584157 secs]1619346K->1273247K8(2027264K)9,0.0585007 secs10][Times: user=0.06 sys=0.00, real=0.06 secs]11
1. 2015-05-26T14:45:37.987-0200 : GC事件发生的时间
2. 151.126 : 相对于JVM的启动时间,GC事件发生的时间,以秒为单位
3. GC: GC类型的标志,用于区别是 Minor GC 还是 Full GC。这次显示的是一次 Minor GC
4. Allocation Failure :GC发生的原因。在这个日志中,表示的是是由于Young Generation 里的任何区域均无法满足一个(对某个数据结构的)空间分配
5. DefNew :使用的 GC 收集器名称。这个缩略名表示的是单线程的、mark-copy、stop-the-world 垃圾回收器,用于清理Young Generation
6. 629119K->69888K :Young Generation的使用情况,分为 GC 前以及 GC 后
7. (629120K) :Young Generation 的总大小
8. 1619346K->1273247K :堆内存使用的总大小,分为GC前与GC后
9. (2027264K) :堆内存总可用大小
10. 0.0585007 secs :GC事件的持续总长时间,以秒为单位
11. [Times: user=0.06 sys=0.00, real=0.06 secs] :GC事件的持续时间,从三种不同的类别衡量:
A. user:在GC 阶段,GC 线程消耗的整个CPU时间
B. sys:OS 调用消耗的时间,或是等待系统事件的时间
C. real:应用停止的时间。因为 Serial GC 一直使用的是单线程,所以这里 real time 等于 user 与 system 时间的总和
从上面的片段,我们可以精确地了解到在 GC 事件时,JVM内部的内存消耗情况。在这次回收前,heap 使用了总共 1,619,346K 大小的内存,其中 Young Generation 一共占了 629,120K 内存。基于此,我们可以计算出 Old Generation 使用量为 990,227K内存。
另一方面,我们也可以看到,在回收之后,Young Generation 的使用量降了 559,231K,但是整个heap 的使用量仅降了346,099K,由此可以推测出,有 213,132K 的对象从 Young Generation 被提升到了 Old Generation。
这次 GC 事件前后,内存的分布,也可以通过下图表示:

Full GC
在讨论了第一个 Minor GC 事件后,我们再来看看第二个 Full GC 事件日志:
2015-05-26T14:45:59.690-02001: 172.8292:[GC (Allocation Failure) 172.829:[DefNew: 629120K->629120K(629120K), 0.0000372 secs3]172.829:[Tenured4: 1203359K->755802K 5(1398144K) 6,0.1855567 secs7] 1832479K->755802K8(2027264K)9,[Metaspace: 6741K->6741K(1056768K)]10 [Times: user=0.18 sys=0.00, real=0.18 secs]11
1. 2015-05-26T14:45:59.690-0200 :GC事件开始的时间
2. 172.829 : 相对于JVM的启动时间,GC事件发生的时间,以秒为单位
3. [DefNew: 629120K->629120K(629120K), 0.0000372 secs :类似上一个例子(由于 Allocation Failure触发的一个minor GC),这次对 Young Generation 的回收也是同样由 DefNew 回收器完成。它将 Young Generation的使用量由 629,120K 降为 0。需要注意的是:这里的日志打印有问题,由于一个存在bug的行为,导致它打印的日志为 Young Generation 使用为满的状态。这次回收耗时 0.0000372 秒
4.Tenured :清理 Old 空间时使用的 GC 收集器名称。这里 Tenured 表示GC使用了一个单线程的、stop-the-world、mark-sweep-compact 垃圾回收器
5. 1203359K->755802K :在 GC 事件前后,Old Generation 使用的空间大小
6. (1398144K) :Old Generation 空间的总共大小
7. 0.1855567 secs :清理 Old Generation 的时间
8. 1832479K->755802K :清理 Young 以及 Old Generation 前后,整个 heap 使用的内存大小
9. (2027264K) :JVM 可用的 heap 大小
10. [Metaspace: 6741K->6741K(1056768K)] :类似 Metaspace 空间回收的信息,正如日志打印的,这次回收中,没有Metaspace的垃圾被回收
11. [Times: user=0.18 sys=0.00, real=0.18 secs] :GC事件的持续时间,从三种不同的类别衡量:
A. user:在GC 阶段,GC 线程消耗的整个CPU时间
B. sys:OS 调用消耗的时间,或是等待系统事件的时间
C. real:应用停止的时间。因为 Serial GC 一直使用的是单线程,所以这里 real time 等于 user 与 system 时间的总和
Full GC 与 Minor GC 的不同点显而易见:在 GC 事件中,除了对 Young Generation 做了垃圾回收外,Old Generation 与 Metaspace 也被做了清理。在这个例子中,在 GC 事件前后,内存的分布可如下如表示:

Parallel GC
这种GC收集器的组合(对 Young 与 Old 使用的两种 GC收集器),在 Young Generation 中使用 mark-copy,在Old Generation中使用mark-sweep-compact。对 Young 与 Old Generation的收集均会触发 stop-the-world 事件,暂停应用的所有线程,以运行 GC。两个收集器均会以多线程的方式运行 mark-copy / mark-sweep-compact,所以它的名字为 ‘Parallel’。使用并行的方式,可以明显减少GC的时间。在GC时,使用多少个线程也可以通过参数指定:-XX:ParallelGCThreads=NNN。默认的值是:机器的CPU核数。在启动JVM时使用以下任一配置即可启用ParallelGC:
java -XX:+UseParallelGC com.company.MyClass
java -XX:+UseParallelOldGC com.company.MyClass
java -XX:+UseParallelGC -XX:+UseParallelOldGC com.compay.MyClass
Parallel 垃圾收集器适用与多核机器,所以如果你的主要目标是为了提高吞吐,则Parallel GC是一个较好的选择。可以获得高吞吐是由于此方法高效地使用了系统资源:
- 在收集过程中,所有CPU 核均会并行回收垃圾,所以应用暂停时间会更短
- 在GC轮数之间,不会有收集器消耗任何资源
另一方面,由于在GC中所有的阶段在运行时不可被打断,所以在你的应用线程暂停时,这些收集器仍容易受到long pause的影响。所以,如果Latency是你需要优先考虑的目标,则你可以考虑下一个垃圾收集器组合。
下面我们看一下使用Parallel GC时,日志输出的信息。下面是一个 minor 和一个 major GC 的日志:
2015-05-26T14:27:40.915-0200: 116.115: [GC (Allocation Failure) [PSYoungGen: 2694440K->1305132K(2796544K)] 9556775K->8438926K(11185152K), 0.2406675 secs] [Times: user=1.77 sys=0.01, real=0.24 secs]
2015-05-26T14:27:41.155-0200: 116.356: [Full GC (Ergonomics) [PSYoungGen: 1305132K->0K(2796544K)] [ParOldGen: 7133794K->6597672K(8388608K)] 8438926K->6597672K(11185152K), [Metaspace: 6745K->6745K(1056768K)], 0.9158801 secs] [Times: user=4.49 sys=0.64, real=0.92 secs]
Minor GC
第一条表示了在 Young Generation里发生的一个GC事件:
2015-05-26T14:27:40.915-02001: 116.1152:[GC3(Allocation Failure4)[PSYoungGen5: 2694440K->1305132K6(2796544K)7]9556775K->8438926K8(11185152K)9, 0.2406675 secs10][Times: user=1.77 sys=0.01, real=0.24 secs]11
1. 2015-05-26T14:27:40.915-0200:GC事件发生的时间
2. 116.115 : 相对于JVM的启动时间,GC事件发生的时间,以秒为单位
3. GC: GC类型的标志,用于区别是 Minor GC 还是 Full GC。这次显示的是一次 Minor GC
4. Allocation Failure :GC发生的原因。在这个日志中,表示的是是由于Young Generation 里的任何区域均无法满足一个(对某个数据结构的)空间分配
5. PSYoungGen:使用的 GC 收集器名称。这里表示的是一个并行的、mark-copy、stop-the-world 垃圾回收器被用于清理Young Generation
6. 2694440K->1305132K:Young Generation的使用情况,分为 GC 前以及 GC 后
7. (2796544K):Young Generation 的总大小
8. 9556775K->8438926K :堆内存使用的总大小,分为GC前与GC后
9. (11185152K):堆内存总可用大小
10. 0.2406675 secs:GC事件的持续总长时间,以秒为单位
11. [Times: user=1.77 sys=0.01, real=0.24 secs] :GC事件的持续时间,从三种不同的类别衡量:
A. user:在GC 阶段,GC 线程消耗的整个CPU时间
B. sys:OS 调用消耗的时间,或是等待系统事件的时间
C. real:应用停止的时间。对于 Parallel GC 来说,它的值应该接近于(user time + system time)/ GC 收集器使用的 CPU 线程数。在这个例子中,GC 收集器使用的是 8 个线程。不过需要注意的是,由于一些活动并不会被并行执行,所以它的真实值会超过一定的比率。
从上面的日志可以看到,在 GC 事件前,整个 heap 中消耗的内存为 9,556,775K,其中 Young Generation 消耗了2,694,440K,也就是说 Old Generation 使用了 6,862,335K。在 GC 后,Young Generation 的使用量降了 1,389,308K,但是整个 heap 的使用量仅降了 1,117,849K。也就是说,有 271,459K 从 Young Generation 提升到了 Old Generation。

Full GC
下面我们继续看下一行 GC 日志,看看 GC 是如何清理整个 heap 内存的:
2015-05-26T14:27:41.155-02001:116.3562:[Full GC3 (Ergonomics4)[PSYoungGen: 1305132K->0K(2796544K)]5[ParOldGen6:7133794K->6597672K 7(8388608K)8] 8438926K->6597672K9(11185152K)10, [Metaspace: 6745K->6745K(1056768K)] 11, 0.9158801 secs12, [Times: user=4.49 sys=0.64, real=0.92 secs]13
1-3 省略
4. Ergonomics:GC 事件发生的原因。这里表示 JVM 的内部功效决定这时候需要做垃圾回收
5. [PSYoungGen: 1305132K->0K(2796544K)]:与之前的例子类似,一个名为“PSYoungGen”的、并行的、mark-copy、stop-the-world GC 回收器被用于清理 Young Generation。Young Generation 的使用情况由 1,305,132K 降为了 0,因为一般一次 Full GC 经常会将 Young GC 完全清理掉。
6. ParOldGen:用于清理 Old Generation 的收集器类型。在这个例子中,一个名为 ParOldGen 的、并行的、mark-sweep-compact、stop-the-world 垃圾回收器被用于清理 Old Generation。
7. 7133794K->6597672K :在 GC 事件前后,Old Generation 使用的空间大小
8. (8388608K) :Old Generation 空间的总共大小
9. 8438926K->6597672K:清理 Young 以及 Old Generation 前后,整个 heap 使用的内存大小
10. (11185152K) :JVM 可用的 heap 大小
11. [Metaspace: 6745K->6745K(1056768K)] :类似 Metaspace 空间回收的信息,正如日志打印的,这次事件中,没有Metaspace的垃圾被回收
12. 0.9158801 secs:GC 事件持续的时间
13. [Times: user=4.49 sys=0.64, real=0.92 secs] GC事件的持续时间,从三种不同的类别衡量:
A. user:在GC 阶段,GC 线程消耗的整个CPU时间
B. sys:OS 调用消耗的时间,或是等待系统事件的时间
C. real:应用停止的时间。对于 Parallel GC 来说,它的值应该接近于(user time + system time)/ GC 收集器使用的 CPU 线程数。在这个例子中,GC 收集器使用的是 8 个线程。不过需要注意的是,由于一些活动并不会被并行执行,所以它的真实值会超过一定的比率。
同样,Full GC 与 Minor GC 的区别较为明显:除了清理 Young Generation,Old Generation 与 Metaspace 也会被清理。在这个例子中,在 GC 事件前后,内存的分布可如下如表示:

References:
https://plumbr.io/handbook/garbage-collection-algorithms-implementations
JVM垃圾回收(四)- GC算法:实现(1)的更多相关文章
- JVM垃圾回收(GC)
JVM垃圾回收(GC) 1. 判断对象是否可以被回收 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收.此方法简单,但无法解决对象相互循环引用的问 ...
- Java:JVM垃圾回收(GC)机制
JVM垃圾回收算法 1.标记清除(Mark-Sweep) 原理: 从根集合节点进行扫描,标记出所有的存活对象,最后扫描整个内存空间并清除没有标记的对象(即死亡对象)适用场合: 存活对象较多的情况下比较 ...
- JVM 垃圾回收(GC)机制
目录 一.背景 二. 哪些内存需要回收? 1.引用计数算法 2 .可达性分析算法 三. 四种引用状态 1.强引用 2.软引用 3.弱引用 4.虚引用 对象死亡(被回收)前的最后一次挣扎 方法区如何判断 ...
- JVM垃圾回收机制GC
1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的 ...
- jvm 垃圾回收机制和算法(转)
stop-the-world 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.stop-the-world 意味着JVM因为需要执行GC ...
- JVM垃圾回收(GC)原理
一.基本垃圾回收算法 1.引用计数(Reference Counting) 比较古老的回收算法.原理是此对象有一个引用则增加一个引用计数,删除一个引用则较少一个引用计数.垃圾回收时,只回收引用计数为0 ...
- JVM——垃圾回收(GC)
GC简单介绍 java语言执行在java虚拟机(jvm)上.为了解决有限的空间和性能的保证这个矛盾体,jvm所具备的GC能力.能够有效的清除不用的对象.使空间的利用更加合理.以下介绍该机制的原理. 推 ...
- JVM垃圾回收(GC)整理总结学习
基本回收算法 1. 引用计数(Reference Counting)比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只用收集计数为0的对象.此算法最 ...
- jvm 垃圾回收概念和算法
1.概念 GC 中的垃圾,特指存在于内存中.不会再被使用的对象.垃圾回收有很多种算法,如引用计数法.复制算法.分代.分区的思想. 2.算法 1.引用计数法:对象被其他所引用时计数器加 1,而当引用失效 ...
- JVM 垃圾回收 Minor gc vs Major gc vs Full gc
关于垃圾回收机制及比较请参见:http://colobu.com/2015/04/07/minor-gc-vs-major-gc-vs-full-gc/ http://colobu.com/2014/ ...
随机推荐
- 北京大学Cousera学习笔记--7-计算导论与C语言基础--基本数据类型&变量&常量
1.整形数据 1.基本型(int 4B).短整型(short 2B).长整型(long 4B) VC环境下 sizeof运算符用于计算某种类型的对象在内存中所占的字节数 ,用法:size(int) ...
- python爬虫简单的添加代理进行访问
在使用python对网页进行多次快速爬取的时候,访问次数过于频繁,服务器不会考虑User-Agent的信息,会直接把你视为爬虫,从而过滤掉,拒绝你的访问,在这种时候就需要设置代理,我们可以给proxi ...
- Django框架详细介绍---认证系统
在web开发中通常设计网站的登录认证.注册等功能,Django恰好内置了功能完善的用户认证系统 1.auth模块 from django.contrib import auth 模块源码 import ...
- 《ASP.NET Core In Action》读书笔记系列五 ASP.NET Core 解决方案结构解析1
创建好项目后,解决方案资源管理器窗口里我们看到,增加了不少文件夹及文件,如下图所示: 在解决方案文件夹中,找到项目文件夹,该文件夹又包含五个子文件夹 -Models.Controllers.Views ...
- 【转】Powershell与jenkins集成部署的运用(powershell运用)
powershell简介: 远程管理采用的一种新的通信协议,Web Services for Management,简称WS-MAN它通过http或者https进行工作,WS-WAN的实现主要基于一个 ...
- 制作Win10 U盘版移动便携系统
制作U盘Win10 灌装WIM VHD_OneKey_beta2 把wim导入VHD文件 复制 WIN8USB.VHD boot bootmgr三个文件到U盘 把制作的Win10的VHD文件重命名为 ...
- java 命令查字节码文件, 查.class文件内容
1. 需要用javac,javap命令,所以先配下环境变量 2.配置环境变量 单击“计算机-属性-高级系统设置”,单击“环境变量”.在“系统变量”栏下单击“新建”,创建新的系统环境变量. 3.写需要用 ...
- 高校表白APP-冲刺第四天
第四天,我们进行了团队的的四次会议. 一.任务: 昨天任务:完成登录界面注册界面修改密码界面. 今日任务:完成跳转,并解决闪退问题. 明日任务:连接本地数据库,进行APP的第一次登陆. 二.遇到的困难 ...
- tomcat1(servlet,http,socket)
1.servlet容器是如何工作的? a.创建一个request对象,用可能会在调用的Servlet中使用到的信息填充该request对象(参数,头,cookies,查询字符串,URI等).reque ...
- shell编辑器vi的常用命令
一:翻页 ctrl+u向上翻半页 ctrl+f向上翻一页 ctrl+d 向下翻半页 ctrl+b 向下翻一页 二:移动光标指令 0: 光标移至当前行首 $: 光标移至当前行尾 三:常用插入.删除指令 ...