学习笔记 | Java 垃圾回收(面试必备)

Java 垃圾回收与垃圾收集器

垃圾回收(Garbage Collection,GC),就是经过垃圾收集器把内存中没用的对象清理掉。垃圾回收涉及到内容:算法

  • 判断对象是否已死
  • 选择垃圾收集算法
  • 选择垃圾收集的时间
  • 选择适当的垃圾收集器清理垃圾

判断对象是否已死

判断对象是否已死:找出哪些对象是已经死掉的,之后不会再用到的。性能优化

判断对象是否已死的方法:引用计数算法可达性分析算法多线程

引用计数算法

给每个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加 1;每当有一个地方不在引用它时,计数器值减 1,这样只要计数器的值不为 0,就说明还有地方引用它,它就不是无用的对象。并发

这种方法看起来很是简单,可是目前许多主流的虚拟机都没有选用这种算法来管理内存,原理就是当某些对象之间互相引用时,没法判断出这些对象是否已死。性能

可达性分析算法

算法的基本思路:经过一系列的称为GC Roots的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证实此对象是不可用的。学习

在 Java 语言中,可做为 GC Roots 的对象包括下面几种:优化

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI (即通常说的 Native 方法)引用的对象

经常使用垃圾回收算法

经常使用的垃圾回收算法有三种:spa

  • 标记-清除算法
  • 复制算法
  • 标记-整理算法
  • 分代收集算法

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

分为 标记清除 两个阶段,首先标记出全部须要回收的对象,标记完成后统一回收全部被标记的对象。线程

不足之处:3d

  • 效率问题:标记和清除两个阶段的效率都不高;
  • 空间问题:标记清除以后会产生大量不连续的内存碎片,致使在程序运行过程当中须要分配较大对象的时候,没法找到足够的连续内存而不得不提早触发另外一次垃圾回收动做。

复制算法(Copying)

为了解决 "标记-清除" 算法内存碎片化的缺陷而被提出的算法。根据内存容量将内存划分为相等大小的两块。每次只使用其中一块,当这一块内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用过的内存空间一次清理掉。

优势:效率要高于标记-清除算法,不会产生过多的碎片。在对象存活率较高时要进行较多的复制操做,效率会较低。

缺点:可用内存被压缩到本来的一半。且存储对象增多的话,Copying 算法的效率会大大下降。

标记-整理算法(Mark-Compact)

先对可用的对象进行标记,而后全部被标记的对象都向一端移动,最后直接清理掉端边界之外的内存。

优势:自带整理功能,这样不会产生大量不连续的内存空间,适合老年代的大对象存储。

分代收集算法(Generational Collection)

分代收集算法是目前大部分 JVM 所采用的方法,其核心思想就是根据对象存活的不一样生命周期将内存划分为不一样的域(把堆内存分为新生代和老年代)。老年代的特色是每次垃圾回收只有少许对象须要被回收,新生代的特色是每次垃圾回收时都有垃圾须要被回收,所以能够根据不一样区域选择不一样的回收算法。

新生代与复制算法

目前大部分 JVM 的 GC 对于新生代都采用 Copying 算法,由于新生代中每次垃圾回收都会回收大部分对象,即要复制的对象比较少,一般并非按照 1:1 来划分新生代。通常将新生代划分为一块较大的 Eden 区和两个较小的 Survivor 区(From Survivor、To Survivor),每次使用 Eden 区和其中一块 Survivor 区,当进行回收的时候,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。

老年代与标记复制算法

老年代由于每次只会回收少许对象,于是采用 Mark-Compact 算法

  1. Java 虚拟机提到过的处于方法区的永生代,它用来存储 class 类、常量、方法描述等,对永生代的回收主要包括废弃常量和无用的类。
  2. 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Space 目前存放对象的那一块),少数状况会直接分配到老年代。
  3. 当新生代的 Eden 区和 From Survivor 区空间不足时会触发一次 MinorGC,进行 GC 后,Eden 区和 From Survivor 区的存活对象会被挪到 To Survivor,而后将 Eden 区和 From Survivor 进行清理。
  4. 若是 To Survivor 没有足够存储某个对象,则将这个对象存储到老年代。
  5. 在进行 GC 后,使用的即是 Eden 区和 To Survivor区了,如此反复循环。
  6. 当对象在 Survivor 区躲过一次 GC 后,其年龄就会加 1。默认状况下年龄达到 15 的对象会被移到老年代中。

垃圾收集器

常见垃圾收集器

如今常见的垃圾收集器有以下几种:

  • 新生代收集器:Serial、ParNew、Parallel Scavenge
  • 老年代收集器:Serial Old、CMS、Parallel Old
  • 堆内存垃圾收集器:G1

HosSpot 虚拟机的垃圾收集器

Serial 收集器

Serial 收集器是最基本、发展历史最悠久的收集器。Serial 收集器是一个单线程的收集器,可是这个"单线程"的意义并不只仅说明它只会使用一个 CPU 或一条收集线程去完成垃圾收集工做,更重要的是在它进行垃圾收集时,必须暂停其余全部的工做线程,直到它收集结束(Stop The World)。

Serial 收集器是虚拟机运行在 Client 模式下的默认新生代收集器。

优势:简单而高效,对于限定单个 CPU 的环境来讲,Serial 收集器没有线程交互的开销,专心作垃圾收集能够得到最高的单线程收集效率。

适用场景:适合运行在 Client 模式下的虚拟机。

ParNew 收集器

ParNew 收集器是 Serial 收集器的多线程版本,可使用多条线程进行垃圾收集。

ParNew 是运行在 Server 模式下的虚拟机中首选的新生代收集器,只有 ParNew 收集器可以与 CMS 收集器配合工做。

ParNew 默认开启的收集线程数与 CPU 的数量相同,在 CPU 很是多的环境下,可使用 -XX:ParallelGCThreads参数来限制垃圾收集的线程数。

Parallel Scavenge

Parallel Scavenge 是一个新生代收集器,使用复制算法实现,并行的多线程收集器,吞吐量优先的收集器。

Parallel Scavenge 收集器的目标是 达到一个可控制的吞吐量(Throughput)。

Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数以及设置吞吐大小的 -XX:GCTimeRatio 参数。

MaxGCPauseMills:容许的值是一个大于 0 的毫秒数,收集器将尽量地保证内存回收话费的时间不超过设定值。

GCTimeRatio:参数的值是一个大于 0 且小于 100 的证书,就是垃圾收集时间占总时间的比率,至关因而吞吐量的倒数。

Serial Old 收集器

Serial Old 是 Serial 收集器的老年代版本,单线程收集器,使用"标记-整理"算法。

适合 Client 模式下的虚拟机使用。

在 Server 模式下,还有两大用途:

  • 在 JDK1.5 以及之前的版本中与Parallel Scavenge 收集器搭配使用
  • 做为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。

Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和"标记-整理"算法。从 JDK 1.6 开始提供。 在注重吞吐量以及 CPU 资源敏感的场合,均可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。

CSM 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS 收集器是基于"标记-清除"算法实现的,它的运做过程相对于前面几种收集器来讲更复杂一些,整个过程分为 4 个步骤:

  1. 初始标记
  2. 并发标记
  3. 从新标记
  4. 并发清除

CMS 是一款优秀的收集器,主要优势:并发收集、低停顿。可是 CMS 还存在如下 3 个缺点:

  • CMS 收集器对 CPU 资源很是敏感。
  • CMS 收集器没法处理浮动垃圾(Floating Garbage)。
  • CMS 基于"标记-清除"算法实现,会产生大量空间碎片。

G1 收集器

G1(Garbage First) 收集器是当今收集器技术发展的最前沿成果之一。

G1 收集器是基于 标记-整理 算法实现的收集器,它不会产生空间碎片,能够很是精确地控制停顿。

G1 是一款 面向服务端应用 的垃圾收集器。与其余 GC 收集器相比,G1 具有以下特色:

  • 并行与并发:G1 能充分利用多 CPU、多核环境下的硬件优点,使用多个 CPU 来缩短 Stop-The-World 停顿时间,部分其余收集器本来须要停顿 Java 线程执行的 GC 动做,G1 收集器仍然能够经过并发的方式让 Java 程序继续执行。
  • 分代收集:与其余收集器同样,分代概念在 G1 中依然得以保留。虽然 G1 能够不须要其余收集器配合就能独立管理整个 GC 堆,但它可以采用不一样的方式去处理新建立的对象和已经存活了一段时间、熬过屡次 GC 的旧对象以获取更好的收集效果。
  • 空间整合:G1 从总体上来看是基于"标记-整理"算法实现,从局部上看是基于"复制"算法实现的,这两种算法都意味着 G1 运做期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利用程序长时间运行,分配大对象时不会由于没法找到连续内存空间而提早出发下一次 GC。
  • 可预测的停顿:这是 G1 相对于 CMS 的另外一大优点,下降停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能创建可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片断内,消耗在垃圾收集上的时间不得超过 N 毫秒,这几乎是实时 Java(RTSJ)的垃圾收集器的特征了。

垃圾收集器参数总结

参数 描述
UseSerialGC 虚拟机运行在 Client 模式下的默认值,打开此开关后,使用 Serial + Serial Old 的收集器组合进行内存回收
UseParNewGC 使用 ParNew + Serial Old 收集器组合进行内存回收
UseConcMarkSweepGC 使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将做为CMS收集器出现 Concurrent Mode Failure 失败后的后备收集器使用
UseParallelGC 虚拟机运行在 Server 模式下的默认值,使用 Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收
UseParallelOldGC 使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收
SurvivorRatio 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为8,表明Eden:From:To=8:1:1
PretenureSizeThreshold 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
MaxTenuringThreshold 晋升到老年代的年龄。每一个对象在坚持过一次 Minor GC 以后,年龄就加 1,当超过这个参数值时就进入老年代
UseAdaptiveSizePolicy 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄
HandlePromotionFailure 是否容许分配担保失败,即老年代的剩余空间不足以应对新生代的整个 Eden 和 Survivor 区的全部对象都存活的极端状况
ParallelGCThreads 设置并行 GC 时进行内存回收的线程数
GCTimeRatio GC 时间占总时间的比率,默认为99,即容许 1% 的 GC 时间,仅在使用 Parallel Scavenge 收集器时生效。
MaxGCPauseMillis 设置 GC 的最大停顿时间。仅在使用 Parallel Scavenge 收集器时生效
CMSInitiationOccupancyFraction 设置 CMS 收集器在老年代空间诶使用多少后触发垃圾收集。默认值为68%,仅在使用 CMS 收集器时生效
UseCMSCompactAtFullCollection 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用 CMS 收集器时生效
CMSFullGCsBeforeCompaction 设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用 CMS 收集器时生效

内存分配与回收策略

  1. 对象优先在 Eden 分配:大多数状况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够的空间进行分配时,虚拟机将发起一次 Minor GC。
  2. 大对象直接进入老年代:大对象是指须要大量连续内存空间的 Java 对象,虚拟机提供了一个 -XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配。这样作的目的是避免在 Eden 区及两个 Survivor 区以前发生大量的内存拷贝。
  3. 长期存活的对象将进入老年代:若是对象在 Eden 出生并通过第一次 Minor GC 后仍然存活,而且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设置为 1,对象在 Survivor 区中每熬过一次 Minor GC,年龄增长 1 岁,当它的年龄增长到必定程度(默认15岁)时,就会被晋升到老年代中。
  4. 动态对象年龄断定:为了更好的适应不一样程序的内存状况,虚拟机不老是须要对象年龄必须达到 MaxTenuringThreshold 才能晋升到老年代,若是在 Survivor 空间中相同年龄全部对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代,无须等待 MaxTenuringThreshold 中要求的年龄。
  5. 空间分配担保:在发生 Minor GC 时,虚拟机会检测以前每次晋升到老年代的平均大小是否大于老年代的赛过于空间大小,若是大于,则改成直接进行一次 Full GC;若是小于,则查看 HandlePromotionFailure 设置是否容许担保失败,若是容许,那只会进行 Minor GC,若是不容许,则也要改成进行一次 Full GC。

Minor GC vs Major GC vs Full GC

Minor GC:指发生在新生代(包括 Eden 区和 Survivor 区)的垃圾收集动做,由于 Java 对象大多都具有朝生夕灭的特性,因此 Minor GC 很是频繁,通常回收速度也比较快。

Major GC:指发生在老年代的 GC,出现了 Major GC,常常会伴随至少一次 Minor GC(但非绝对的,在 Parallel Scavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程)。Minor GC 的速度通常会比 Minor GC 的速度慢 10 倍以上。

Full GC:针对新生代、老年代、元空间(Metaspace、Java8 以上版本取代 Perm gen)的全局范围的 GC。Full GC 不等于 Major GC,也不等于 Minor GC + Major GC,发生 Full GC 须要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。

关注得到更多分享


推荐推荐:

相关文章
相关标签/搜索