乐趣区

关于java:JVM-垃圾回收基础

欢送关注公众号:五小竹

什么是垃圾

内存中不在被应用到的内存空间,就是垃圾。
Java 中的内存是动态分配和主动回收的。学习垃圾收集机制,调优策略,可能帮忙咱们解决和应答各种工作中面临的内存透露问题。


Java 虚拟机运行时数据辨别为程序计数器、虚拟机栈、本地办法栈、堆、办法区。
其中程序计数器、虚拟机栈、本地办法栈这 3 个区域是线程公有的,会随线程沦亡而主动回收,所以不须要治理。
因而垃圾收集只须要关注堆和办法区。

Java 的内存调配

堆上调配

  • 如果启动了本地线程调配缓冲,将按线程优先调配在 TLAB 上。
  • 对象优先在 Eden 上调配
  • 大对象进入老年代
  • 长期存活的对象进入老年代

栈上调配

基于逃逸剖析技术,如果一个对象始终在一个办法内。确定这个对象不会逃逸办法之外,那让这个对象在栈上分配内存,这样对象就会随着办法的完结而主动销毁,升高了垃圾回收的压力。

如何断定垃圾

援用计数法

给对象增加一个援用计数器,有对这个对象的援用就对计数加 1,援用生效就减 1,任何时刻计数器为 0 的对象就是断定为垃圾。

  • 长处:实现简略,效率高。
  • 毛病:不能解决对象循环援用的问题。在多线程环境下,援用计数变更也要进行低廉的同步操作,性能较低。

可达性分析法

目前支流的虚拟机才有的算法。他是从根节点(GC Root)向上来搜寻对象节点,搜寻走过的门路称为援用链,当一个对象到根节点之间没有联通的话,这个对象断定为垃圾。

GC Root 对象

能够作为 GC Roots 的对象包含:

  • 虚拟机栈中援用的对象
  • 办法区类动态属性信用的对象
  • 办法去常量援用的对象
  • 本地办法栈中 JNI(Native 办法)援用的对象

援用类型

判断一个对象的存活跟援用无关,jdk1.2 之前,援用只是指援用类型的数据中存储了另一块内存的地址。jdk1.2 之后,java 的援用分为以下四种。

  • 强援用:Object obj = new Object(); 最常见的援用,这样通过 new 创立的会产生该对象的强援用。只有对象有强援用指向,并且 GC Roots 可达,那么就不会回收该对象。
  • 软援用: SotfReference 类来实现。示意一些还有用然而非必须的对象。在 OOM 前,垃圾收集器会把这些软援用指向的对象退出回收范畴,对于软援用关联的对象,只有在内存不足的时候才会回收。
  • 弱援用:WeakReference, 示意非必要对象,在 YGC 时候会被回收。因为 YGC 工夫不确定,所以弱援用随时都有可能被回收。
  • 虚援用:PhantomReference, 无奈通过该援用获取指向的对象。在任何时候都可能被垃圾回收器回收。虚援用次要用来跟踪对象被垃圾回收器回收的流动,必须和援用队列配合应用:

ReferenceQueue<String> queue = new ReferenceQueue<>();
PhantomReference<String> phantomReference = new PhantomReference<>("Hello", queue);

当垃圾回收器筹备回收一个对象时,发现他有虚援用,就会在回收这对象的内存前,把这个虚援用退出队列,程序通过援用队列中是否有该虚援用,来理解对象是否要被垃圾回收器回收,而后能够在回收前做一些事件。

垃圾收集算法

标记 - 革除(Mark-Sweep)

最根底的算法,第一步,先标记,从 GC Roots 登程,顺次标记对象援用关系,而后,将没有被标记的对象革除。
毛病:一个是效率问题,标记和革除两个过程效率都不高;另外,革除过后会产生大量不间断的内存碎片。导致当咱们须要调配大对象时候,无奈找到足够的间断内存而又触发一次 GC。

复制(Copy)

复制算法:把内存分为两块大小雷同的区域,每次应用其中一块,当应用完了,就把这一块区域上还存活对象拷贝到另一块,而后把这一块革除掉。不会造成内存碎片,然而内存利用率不高,会造成一半的空间节约。

新生代上的对象大多数“朝生夕死”,HotSpot 默认将新生代内存划分为一个大的 Eden 区和两个小的 Surivor 区。在 GC 时,将 Eden 区和一个 Surivor 区中存活的对象,复制到另外一个 Surivor 区。Eden 区和两个 Surivor 区比例默认 8:1:1,所以这时候只会节约 10% 的空间。

标记 - 整顿 (Mark-Compact)

和标记革除一样,先标记,然而后续不间接回收对象清理,而是让存活的对象向一端挪动,而后间接清理掉端边界之外的内存。

因为复制算法在存活对象比拟多的时候,效率比拟低,且有空间节约,所以老年代个别会选用标记 - 整顿算法。

### 三种 GC 算法比照

回收算法 长处 毛病
标记 - 革除 实现简略 存在内存碎片
复制 无碎片,性能好 内存使用率低
标记 - 整顿 无碎片 整顿过程开销大

分代收集

将下面的各种算法组合在一起,就地取材。个别咱们把堆空间分为新生代和老年代。依据他们的特定采纳不同的垃圾回收算法,在新生代,每次 GC 都会有大量对象死去,多数存活,所以采纳复制算法。只用付出多数对象的复制老本,不会造成不间断的内存碎片。而在老年代,对象存活率高,则采纳标记整顿或者标记革除。

垃圾收集器

术语

  • STW: “Stop The World” 简写,也叫全局进展。会造成服务暂停,没有响应。
  • 串行收集:GC 单线程内存回收,会暂停所有的用户线程。比方 Serial, Serial Old
  • 并行收集: 多线程进行并发 GC,此时暂停用户线程。比方 Parallel
  • 并发收集:用户线程和 GC 线程同时执行,不须要进展用户线程. 适宜对响应工夫有要求的场景。比方 CMS 收集器

Serial/Serial Old

Serial 是一个单线程的收集器,它只会应用一个 CPU 或一个收集线程去做垃圾收集,并且在做垃圾收集时须要进行所有的工作线程,晓得收集工作完结。Serial 进行用户线程,采纳复制算法收集年老代,Serial Old 采纳标记 - 整顿算法收集老年代。

特点:单线程收集,STW

应用 -XX:+UseSerialGC 开启 Serial + Serial Old

ParNew

Serial 的多线程版本,应用多线程进行垃圾收集。他是新生代的垃圾收集器。须要配合老年代的 CMS 收集器应用。所以开启须要应用 CMS, 新生代默认 ParNew.

能够通过 ==-XX:ParallelGCThreads== 参数来管制收集的线程数,过程也是 STW

Parallel Scavenge/Paraller Old

Parallel Scavenge 是新生代的收集器,采纳复制算法,多线程的收集器,Paraller Old 是老年代收集器,也是多线程收集,采纳标记 - 整顿算法。

主要参数

  • -XX:UseParallelGC 开启
  • -XX:MaxGCPauseMillis 最大垃圾收集进展工夫
  • -XX:GCTimeRatio 设置吞吐量大小

可控的吞吐量

通过参数 -XX:MaxGCPauseMillis 管制最大 GC 进展工夫。-XX:GCTimeRatio 设置吞吐量大小

晋升吞吐量能够高效利用 CPU 工夫,尽快实现程序工作。

自适应 GC 策略

解决提供了下面两个参数管制吞吐量大小以外,Parallel Scavenge 还能够通过 -XX:+UseAdptiveSizePolicy, 开启指示仪 GC 策略。关上后,就不再须要手动设置新生代大小,Eden/Surivor 比例等参数,虚构机会依据零碎运行状况,动静调整这些参数,从而达到最优的进展工夫,和最高吞吐量。

CMS

  • 1. 初始标记 :只标记 GC Roots 能间接关联到的对象,会 STW。
  • 2. 并发标记 :进行 GC Roots Tracing 的过程,GC 线程和用户线程同时执行。
  • 3. 从新标记 :修改并发标记期间,因程序运行导致标记发生变化的那些对象。会 STW
  • 4. 并发革除 :并发回收垃圾对象(GC 线程和用户线程同时执行)
  • 5. 并发重置:清理本次 CMS GC 上下文信息,为下一次 GC 做筹备。

长处:低进展,并发执行

毛病:

  • 因为并发执行,所以对 CPU 资源压力大。
  • 无奈解决在收集过程中产生的浮动垃圾。
  • 因为采纳标记 - 革除算法,所以会才是大量内存碎片。而导致在须要调配大对象是内存不足,触发 FullGC。

应用 -XX:UseConcMarkSweepGC 开启 ParNew+CMS/SerialOld 收集器组合,即新生代采纳 ParNew, 老年代 CMS,当 CMS 出错后,SerialOld 备用。
为了解决内存碎片问题,CMS 能够通过 -XX:+UseCMSCompactAtFullCollection,强制 JVM 在 FullGC 实现后对老年代进行压缩,执行碎片整顿,同时会 STW。想要缩小 STW 次数,能够配置 -XX:+CMSFullGCsBeforeCompaction 参数,在执行设置的次数后,JVM 再在老年代进行空间整顿。

JDK9 曾经将 CMS 标记为弃用,在 JDK14 中曾经将 CMS 删除。

G1

JDK7 推出的新一代收集器,
是一个面向服务端利用的收集器。相比下面的收集器,
G1 作用在整个堆,而其余的收集器都是只作用在新生代或老年代。

G1 将 Java Heap 宰割成一些大小雷同的 Region, 通过参数 -XX:G1HeapRegionSize 指定 Region 的大小,取值范畴为 1~32M,应为 2 的 N 次幂。G1 对每个 Region 做了分类,别离包含:Eden,Surivor,Old,Humongous, 其中 Humongous 相当于一个大的 Old,用来寄存大对象。

如下图:G1 的堆内存布局和传统的堆内存布局不同。

G1 将空间分成多个区域,跟踪每个区域外面的垃圾沉积的价值大小,构建一个优先列表,优先收集垃圾最多的区域,这也是它为什么叫 Garbage-First 的起因。

G1 与 CMS 相比的,有何特点

  • 并发与并行:充沛的利用多 CPU,缩短 STW 工夫。并发标记阶段可与用户线程并发执行,最终标记阶段 GC 线程可并行执行。
  • 分代收集:G1 能够不与其余垃圾收集器配合,独立实现整个 GC 堆的垃圾收集。
  • 空间整合:G1 整体看是采纳“标记 - 整顿”算法实现的,部分也有 Eden 和 Surivor Region 看上去是采纳“复制”算法来实现。整个过程防止了产生内存碎片。
  • 进展工夫可管制:G1 除了谋求低进展,还能建设可预测的进展工夫模型,能让用户指定在一个时间段内,耗费在收集上的工夫不超过一个时间段。

G1 垃圾收集模式

Young GC
  • 1. 所有 Eden Region 都满了时,就会触发 Young GC
  • 2.Eden Region 里的对象会转移到 Surivor Region
  • 3. 原 Surivor Region 中的对象转移到另一个 Surivor, 或者降职到 Old Region
  • 4. 闲暇 Region 会被放入闲暇列表,期待下次应用。
Mixed GC

当老年代占整个 Heap 的大小百分比达到一个阀值(-XX:InitialingHeapOccupancyPercent)时, 默认 45%,就会触发 Mixed GC,
收集整个新生代以及局部老年代。

Mixed GC 回收过程

  • 1. 初始标记:只标记 GC Roots 能关联到的对象。批改 TAMS 的值,此阶段会 STW.
  • 2. 并发标记: 从 GC Root 开始对堆内存中的对象进行可达性剖析,找出存活的对象,此过程能够和用户线程并发执行。
  • 3. 最终标记:修改在并发标记阶段用户线程持续运行导致标记产生变动的记录。此过程 STW,然而能够并行执行。
  • 4. 筛选回收:对各个 Region 的回收价值和老本进行排序,依据用户冀望的进展工夫制订回收打算。
Full GC

当复制对象内存不够时,或无奈调配足够空间时,触发 Full GC
Full GC 模式是采纳 Serial Old 收集的,所以会 STW。

如何缩小 Full GC?

  • 增大 -XX:G1ReserverPercent, 来减少预留内存。
  • 缩小 -XX:InitialingHeapOccupancyPercent, 当老年代达到这个值是就触发 Mixed GC,
  • 减少 -XX:ConcGCThreads 并发阶段的线程数。

总结

对以上介绍的垃圾回收器总结

  • Serial 串行,作用于新生代,采纳复制算法
  • ParNew 并行,作用于新生代,采纳复制算法
  • Serial Old 串行,作用于老年代,标记 - 整顿算法
  • Parallel 并行,作用于新生代,复制算法
  • Parallel Old 并行,作用于老年代,标记 - 整顿算法
  • CMS 并发,作用于老年代,标记 - 革除算法
  • G1 并发 + 并行,作用于整个堆,复制算法,标记 - 整顿

参考资料

《深刻了解 Java 虚拟机 JVM 高级个性与最佳实际》

退出移动版