Hotspot VM垃圾回收器概览

所谓概览,先站在山顶俯瞰,在脑海里有个思路和映像。纲举目张,老叟认为,这是学习垃圾回收器的第一步。

1,问题由来

凡人皆有一死,垃圾都要回收。

垃圾回收并不是Java语言特有的东西,它是所有语言都需要面对的事情。这个江湖分为两大流派:

1)手动回收,像C/C++,这类语言跑起来非常之快,但是开发效率就打点折扣了,因为程序猿们除了需要关注业务,还需要手动回收内存空间,一不小心,就会发生泄露以至于最后溢出:)

2)自动回收,像Java,Python等,背后有一股神秘力量,在后面擦屁股,程序猿们只需要不断地创建对象,不用关心这个内存的释放问题。但并不是说因此这类语言就不存在泄露问题//TODO。

2,回收器实现

任何一个垃圾回收器都要解决三个问题(WWW):WHEN、WHAT、HOW。

2.1 什么时候回收When

这个很容易理解,执行垃圾回器的时机,应该是内存使用量达到了一定的程度。这个就是具体回收器具体分析了。

具体在Hotspot VM的回收器何时触发呢?下图是个大概的参考,具体到每个回收器,情况只能更加复杂,比如,CMS GC的触发条件,就不是old gen放不下,而是old gen的使用量超过了配置的触发比例,而G1是不断地在回收。

2.2 哪些对象该回收What

过气/过期/不再使用的对象,就应该被回收。业界有两个算法用来追踪这些对象。

  • 引用计数算法

基本思路就是,对象被引用了,那么这个对象的引用计数加1。而那些计数器为0的对象,则认为是可以被回收的。优点是实现起来相对简单,另外一个优点是,一旦计数为0即刻被清除,不需要统一的回收,既不会产生STW。但是这个有个缺点:

  1. 循环引用问题//TODO,其实循坏引用并不是不能解决,比如PythonGC就是用的引用计数算法,它是在引用算法之上通过可达算法来解决循环引用问题的。
  2. 计算管理,在多线程竞争计数时,需要上锁等并发控制手段,造成额外开销。
  • 根可达性算法

找一些对象作为根对象,然后一直顺藤摸瓜找到所有的引用链(具体到Java中,引用又分强软弱中(//TODO,后面分享这四种引用分别对垃圾回收的影响)),那么剩下的那些并没有在引用链上的对象,就是可回收对象。

JVM的GC Root有局部变量,常量,静态变量,Remenbered Set。

2.3 怎么回收How

上面讲了when&what,这里就要讲一讲how。

  • 复制算法(Coping)

把可用的空间,一分为二:A和B(并不一定是一分为二,也可以一分为三等等,比如Python GC分一二三代,但是复制算法归根结底是要分的)。新创建的对象都放在同一个空间A中,回收的时候,只需要把标记为存活的对象从空间A移动到空间B,然后清空空间A。接下来新创建的对象都放在空间B,再次回收的时候,把空间B中标记为存活的对象移动到空间A,然后清空空间B。周而复始。

当这个空间的回收率高的时候,比如新生代中,那么它就相对标记算法而言更快一些,因为只需要把少数的存活对象复制到出去,再清空即可,不需要遍历所有的对象去判断是否可回收。

其中Serial New, ParNew, Parallel Scavenge都是采用复制算法来。

  • 标记-清除算法(Mark-Sweep)

把标记为可回收的对象,清除。缺点是会产生碎片。

当空间的回收率比较低的时候,比如老年代中,那么它就相对复制算法更快一些,因为不需要把大部分的对象复制一遍,只需要遍历一遍,讲可回收对象清空即可。

CMS采用这个标记-清除算法,它通过Serial Old来整理碎片。

  • 标记-压缩算法(Mark-Compat)

也叫做标记-整理,是在标记-清除的基础之上整理、压缩空间,使得可以的空间是连续的。

Serial Old,Parallel Old就是采用此算法。

 

3,Hotspot VM的垃圾回收器

现在我们具体到Hotspot虚拟机的垃圾回收器。

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html

Oracle JDK从1.0走到如今的主流8.0,回收器也是一步步走过来的,从刚开始只需要管理几十上百M的空间到现在的几十上百个G,从串行到并发,它一直在进步。我把它分为两个时代,G1前,G1及其后。在G1前,JVM的堆空间,是分代的(Generation:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html#sthref16):年轻代和老年代。而在G1时代,则没有这个划分。

3.1,G1前

3.1.1 分代概念

G1回收器之前(JDK8及其之前的版本),JVM的堆空间划分为年轻代和老年代,分别使用不同的回收器进行垃圾回收。这里讲一下为什么要分代,据称,90%以上的对象,在第一次GC的时候都会被回收(//TODO找一找”90%“相关的实验数据)。让一些经历过多次GC存活下来的对象进入老年代,可以减少YoungGC的压力。换句话说,年轻代的回收效率高,老年代的回收效率低,这两个空间可以采用不同的回收算法达到最优的效果。

粗分

细分

GC

算法

年轻代(Young Generation)

伊甸园(Eden)

Young GC/Minor GC

Full GC

/MajorGC

注1

复制算法

逃生区1(survivor1,S1)

逃生区2(survivor2,S2)

老年代(Old Generation)

老年代(Tenured)

Major GC

标记-清除 | 标记-压缩

永久代(Perm Generation)

注2

   

从上图可知,年轻代用的是复制算法,为什么呢?因为年轻代的回收率高,然后因为复制算法的特别,必须要把少量的存货对象复制到其他空间,然后一口气把旧空间清除即可。假设年轻代用的是标记-清除,那么在清除阶段,就需要每一个查看每个对象的状态(是否被标记),这明显比复制算法要来的慢一些。

同样,老年代因为回收率低,所以才用标记-清除或者标记-压缩算法更为高效。假设老年代用的是复制算法,那么要复制的对象占比很高,几乎相当于把所有的对象复制了一遍,这明显比标记算法慢一些。

3.1.2 小内存时代(M)

  • Serial New

年轻代,复制算法(Coping)。

顾名思义,就是这两个回收器在工作的时候,采用的串行单线程的方式,也就是说,当回收线程在工作的时候,其他的工作线程,就是暂停在safepoint,造成全世界暂停的现象,叫做stop the world(STW)。

Serial回收器采用的复制算法:对Eden空间和S2空间的对象进行标记(根可达算法),然后把不可回收的对象复制到S1,并且清空Eden和S2;下一次触发的时候,对Eden空间和S1空间的对象进行标记,然后把不可回收对象复制到S2,并且清空Eden和S1;

对象每次从S1复制到S2或者S2复制到S1,年龄都会加1,到达一定年龄(年龄可配置)的对象,就会被移动到老年代中。

  • Serial Old

老年代,标记-压缩算法(Mark-Compact)。

这两个具体的GC回收器,什么时候会触发呢,是空间使用率达到一定阈值吗?还是空间不足的时候?答案是空间不足的时候。当JVM需要空间,而Eden空间不足的时候,就会STW,让Serial回收器工作。当需要把对一个对象移动到老年代的时候,发现空间不足,那么也会STW,触发一次fullGC。

上述两个回收器可以在小内存(几十百来M)或者移动设备上使用。

当JVM使用了client模式时,使用这两个回收器。

3.1.3 中内存时代(G)

  • ParNew

年轻代。复制算法(Coping)。

简单地讲,ParNew就是Serial New的多线程版本,可以最大地发挥多CPU的计算能力。它默认开启的线程数跟CPU个数一致。

  • CMS

老年代。标记-清除算法(Mark-Compact)。

只能搭配ParNew使用。

CMS是一个很重要的回收器。也是最值玩味的回收器。Hotspot虚拟机中第一款真正意义上的并发回收器(与工作线程并发,但仍然存在短暂的STW)。它的设计目标是最短的STW,换句话说,对响应时间要求严格的场景应该使用这个,互联网场景就是追求极致的响应速度,所以,这个是首选。为了追求最短的STW,采用了标记-清除算法,即在清除阶段,可以跟工作线程并发执行。但是这个回收器并不是完美的,导致他从来没有成为任何一个版本JDK的默认回收器。(CMS专题//TODO)

  • Parallel Scaverage

年轻代,Serial New的并发版本,能够应付的堆空间级别从M到G。

  • Parallel Old

老年代,Serial Old的并发版本

3.2,G1时代(大内存时代)

3.2.1 G1回收器

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

G1是集当代先进技术于一体的高性能回收器,被Oracle给予厚望,是大内存时代取代CMS回收器的重要候选人,既然是取代CMS的,那么他们所追求的目标就是一直的,那就是高响应,低延时。不过,它不再具有前面回收器的物理分代概念,而是才有逻辑分代,所谓逻辑分代,就是还有那么年轻代,老年代,但是他们并非物理内存连续的,官方叫做分区(Region),默认是2048个分区。每个分区既可以是年轻代也可以是老年代,所以G1的年轻代和老年代的比例是动态变化一直在调整中的。

每个分区又分卡片(Card),大小是521字节,对象分配的空间,就是占用这些连续的卡片。每个卡片还有一个头部,叫做卡片表(Global Card Table)。

回收器之间的搭配使用图总结如下,虚线之间的连线表示他们可以搭配使用。

注释

注1:根据R大的解释,MajorGC通常是跟full GC等价,收集整个GC堆,但是因为HotSpot VM发展了这么多年,外接对各种名词的解释已经混乱了,当有人说”Major GC“的时候一定要问清楚他想要指的是full GC还是old GC。

注2:永久代,永久的是JDK7及以前中Hotpot中方法区的实现,永久代和堆是相互隔离的,但是他们使用的物理空间是连续的。永久代的垃圾回收和老年代捆绑在一起,无论谁挂满了,都会触发永久代和老年代的垃圾收集。JDK8放弃永久代的原因是因为和JRockit VM进行融合。下图记录了永久代的信息:

版本

方法区实现

存放的数据

备注

 

JDK7以前

永久代(PermGen)

类信息,静态成员,常量

 

与堆内存连续

使用老年代回收器管理

JDK7

永久代

类信息

字符串常量存在堆中

符号引用存在NativeMemory

JDK8

元空间(MetaSpace)

类信息

C-heap即本地内存

默认可以无限使用本地内存,但也可以限制:注3

注3,限制元空间的使用:

  • -XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。
  • -XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
  • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集。
  • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集。

1,常见垃圾回收器组合参数设定(Hotspot,JDK8)

HotSpot会根据计算机配置和JDK版本自动选择收集器。

  • -XX:+UseSerialGC = Serial New + Serial Old
    • 小型应用
  • -XX:+UseParNewGC = ParNew + Serial Old
    • 这个组合很少用。
  • -XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old
    • 互联网常用,注重高响应,低延时。
    • 面试
  • -XX:+UseParallelGC = Parallel Scavenge + Parallel(PS + PO)
    • JDK8默认回收器,注重吞吐量。
  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
    • 同上。
  • -XX:+UseG1GC = G1
    • JDK8进入商用阶段,可开启使用。

2. 如何查看用的哪个GC:

  • windows下:java -XX:+PrintCommandLineFlags -version
  • 通过GC日志来分辨

一些参考

为什么GC要分代的哲学回答:https://www.zhihu.com/question/53613423/answer/144681796

Instagram通过禁用PythonGC来提高性能:https://www.infoq.cn/article/disable-python-gc-mechanism-instagram-performance-increase

吞吐量和处理速度:https://www.zhihu.com/question/279164955/answer/405448070

ThinkingClearlyAboutPerformance: https://queue.acm.org/detail.cfm?id=1854041

内存回收到底是什么:

Java对象是怎么记录标记状态的?

写屏障?write barrier:

泛化GC:https://juejin.im/post/5cf0ffa7f265da1ba56b052a

G1回收器:http://www.linkedkeeper.com/1511.html

Oracle G1: https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector.htm

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值