死磕Java虚拟机,带你走进性能调优理论

一. 如何找到一个垃圾?

1) 引用计数算法:给对象添加一个引用计数器,有一次引用,计数器值就加1;当引用失效时,计数器值就减1。很多流程的编程语言例如Python都使用这种方法管理内存,但是主流的Java虚拟机没有选用它,主要原因是它很难解决对象之间相互循环引用的问题。

2) 根可达性分析算法:因为引用计数算法无法解决对象之间相互循环引用的问题,继而引出了这个算法。思想是以GC Roots作为起始点开始向下搜索,所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链时,则这个对象是不可用的,就是垃圾。如图所示:

死磕Java虚拟机,带你走进性能调优理论

二. 找到一个垃圾后,如何清除它?

1) Mark-Sweep(标记清除):先标记出可回收的对象,然后清除。如下黑色部分为可回收对象,灰色部分为存活对象,绿色部分为未使用对象。

死磕Java虚拟机,带你走进性能调优理论

它的主要不足有两个:

1. 标记和清除两个过程的效率都不高

2. 另一个是空间的问题,标记清除之后产生了大量不连续的内存碎片,碎片会导致以后需要分配较大对象时,无法找到足够的连续内存,继而提前触发另一次垃圾收集动作。

2) Copying(拷贝):将内存划分为两块,将存活对象全部copy到下面的区域,然后把上面的全部清除。

死磕Java虚拟机,带你走进性能调优理论

新生代中的Survivor1和Survivor2就是这样的。

缺点:浪费内存

3) Mark-Compact(标记压缩或者标记整理):既不想碎片化,又不想浪费内存,就先将回收的对象标记起来,然后一边回收,一边把存活对象向一端移动。

死磕Java虚拟机,带你走进性能调优理论

缺点:效率偏低

三. 内存分配和回收的过程是什么样子的

死磕Java虚拟机,带你走进性能调优理论

内存分配与回收策略如下图:

死磕Java虚拟机,带你走进性能调优理论

名词解释:

YGC ==Young GC == Minor GC 新生代回收

Major GC 老年代回收

Full GC 新生代和老年代一块回收

图示解释:

1) new出一个对象,首先尝试在栈上分配,如果能直接分配下,就在栈上分配。对象在栈里一旦pop,对象就没了。(栈上分配好处:不需要GC介入,对象用完就可以消失,但是栈的空间非常小)

2) 如果栈上分配不下,并且对象比较大,会直接进入老年代,经历Major GC/Full GC的回收,然后消亡。

3) 如果对象不够大,先在线程本地分配 (TLAB: Thread Local Allocation Buffer),如果TLAB满了,直接进入Eden,不管进不进入TLAB,最终都会进入Eden区域,进入Eden区域会竞争资源,出现线程同步,特别消耗资源,因此Hotspot做了优化,Eden区域为每个线程都分配了一小块区域,这样就不会每个线程都去抢夺资源。

4) 在Eden区域,对象如果能被GC清除,直接就消亡了

5) 在Eden区域,如果回收不掉,进入Survivor1区域,然后再次回收,年龄加1,如果年龄到了(CMS默认是6岁,其他默认都是15岁),然后进入老年代,如果年龄没到,进入Survivor2区域,然后再次回收,循环往复,Survivor1和Survivor2还会来回替换(因为有个copy垃圾回收算法),直到进入老年代。

四. 垃圾收集器(10种)

死磕Java虚拟机,带你走进性能调优理论

前面六个是分代模型,后面四个是不分代模型。

1. 垃圾收集器的发展路线,是随着内存越来越大的过程而演进的。

从分代算法演化到不分代算法

Serial算法 几十兆

Parallel算法 几个G

CMS 几十个G 承上启下,开始并发回收

-三色标记-

2. JDK诞生,Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,并发垃圾回收的原因是因为无法忍受Serial回收的STW。

3. Serial 收集器 年轻代 串行回收

死磕Java虚拟机,带你走进性能调优理论

特点:在回收垃圾时,必须暂停其他所有的工作线程,知道它收集结束

4. PS 年轻代 并行回收

死磕Java虚拟机,带你走进性能调优理论

5. ParNew 年轻代 配合CMS的并行回收,它和Parallel Scavenge一样,区别就是它配合CMS使用。

死磕Java虚拟机,带你走进性能调优理论

6. Serial Old 收集器

死磕Java虚拟机,带你走进性能调优理论

7. Parallel Old

8. CMS 全称 ConcurrentMarkSweep 老年代 并发的,垃圾回收和应用程序同时进行,降低STW的时间(200ms),CMS问题很多,所以没有一个版本默认是CMS,只能手动设定。CMS既然是MarkSweep,就一定会有碎片化的问题,碎片达到一定的程度,CMS老年代分配对象分配不下的时候,使用Serial Old进行老年代回收。(面试重灾区)

死磕Java虚拟机,带你走进性能调优理论

主要有四个过程:

初始标记、并发标记、重新标记、并发清理

死磕Java虚拟机,带你走进性能调优理论死磕Java虚拟机,带你走进性能调优理论

CMS的缺点是:产生了浮动垃圾,并且使用Serial Old来清理整个老年代,这是CMS设计的缺陷;但是如果CMS做好调优,支持的内存要比Parallel Old大的多。

想象一下:

PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + Serial Old (几个小时-几天的STW) ,几十个G 的内存,单线程的回收 -> G1 + Full GC 几十个G -> 上T内存的服务器 ZGC

CMS并发标记采用的是: 三色标记法 + Incremental Update

9. G1 垃圾回收器 (200ms - 10 ms)

算法:三色标记 + SATB

由于越来越多的内存需要回收,必然会产生STW,所以G1应运而生。

逻辑分代,物理不分代。

死磕Java虚拟机,带你走进性能调优理论

10. ZGC (10ms - 1ms ) PK C++

算法:ColoredPointers + LoadBarrier

11. Shenandoah

算法:ColorPointers + WriteBarrier

CMS中新生代的默认年龄是6,PS/PO中新生代的默认年龄是15,进入老年代,可以通过参数:-XX:MaxTenuringThreshold配置

jdk1.0自带的Serial和Serial Old,现在用的最多的是Parallel Scavenge和Parallel Old,调优用的是ParNew和CMS,jdk1.8用G1也没有问题

上图:前面六种,一般都是新生代和老年代配合使用。ParNew和CMS,Serial和Serial Old,Parallel Scavenge和Parallel Old。

jdk1.8默认是Parallel Scavenge和Parallel Old,不管是单线程Serial还是并行Parallel,只要人多,都会出现问题,所以就有了承前启后的ParNew和CMS。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章