jvm垃圾回收机制原理读书笔记

7 篇文章 0 订阅
2 篇文章 0 订阅
可达性分析

主要是用来解决哪些对象是不可用的,可以被回收。

以一系列称为GC Roots的对象为起点,若对象到GC Roots之间没有任何引用关系,则认为该对象是不可用的,可以被回收.

具体可作为GC Roots的对象如下(不是全部):

  1. **所有Java线程中当前栈帧的引用(例如局部变量),也就是活着的线程,**这个就是我们通常意义上Java代码new一个对象引用,这个对象引用所在的地方.
  2. 所有ClassLoader
  3. 所有全局变量

对于classloader和全局变量,因为其一定伴随着应用的整个生命周期,所以能从此处开始遍历。对于线程中局部变量,因为此时一定处于活跃状态,所以此处一定能遍历出去

常见的清除算法

主要是进行垃圾回收时怎么具体去操作内存,主要分为如下几种类型,但实际上虚拟机通常都是在不同的代上使用不同的算法组合实现。

标记-清除算法

首先标记所有需要被回收的对象,当标记完成之后,再统一清理。CMS收集器使用该算法实现。

该算法存在两个问题:

  1. 标记和清除的效率不高

  2. 清除的时候容易导致内存不连续, 产生内存碎片

复制算法

为了解决标记-清除算法时导致内存碎片的产生,可以将内存划分为相等的两部分(不一定相等,此处方便描述),每次只使用其中的一块,当这一块用完之后,将其中存活的对象复制到另外一份,同时清空这份内存。

HotSpot中新生代Eden,S0,S1的划分就是依据此算法实现,其划分比例默认是8:1。

该算法对于那些存活时间短的对象清除效率高,因为在复制的时候大部分对象都已经不可用了,所以复制的对象较少。但是对于存活时间长的对象由于需要多次进行复制操作,效率会变低。

该算法还有另外一个弊端, 需要额外占用一部分内存。

标记-整理算法

上述的复制算法对于存活时间长的对象不友好。该算法的标记过程与标记-清除算法一样,但是在清理的时候,将存活的对象向一端移动,然后清理掉在该边界之外的所有对象。

一般老年代使用此算法实现。

常见垃圾收集器

在JDK8中server模式下默认的是: Parallel Scavenge + Parallel Old(其实就是Parallel并行收集器,分为老年代和新生代)

下面具体分析JVM中几种垃圾收集器

Serial 串行收集

单线程执行。会stop the world,也就是在进行收集时,会暂停所有其他用户线程。

该收集器包括两个版本,一个是新生代收集器,使用复制算法;一个是老年代,使用标记-整理算法

Parallel 并行收集

并行执行。目的是达到一个可控制的吞吐量

其主要特点如下:

  • 并行执行
  • 能自适应堆中各个区域的大小关系
  • 目的是达到一个可控制的吞吐量

该收集器包括两个版本:

一个是新生代收集器, Parallel Scavenge,使用复制算法实现;

一个是Parallel Old, 使用标记-整理算法实现。

CMS

concurrent mark sweep, 是一个和用户线程并行执行的垃圾收集器。

目的是获取最短回收停顿时间

基于标记-清除算法实现。

其收集流程如下:

  1. 初始标记 该过程需要stop the world, 仅仅标记GC ROOTS直接关联的对象,所以很快
  2. 并发标记 该过程和用户线程并行执行,占用时间较长, 进行GC roots tracing, 也就是对象的可达性分析
  3. 重新标记 该过程需要stop the world,标记在并发标记阶段新产生的需要回收的对象,占用时间较短
  4. 并发清除 该过程进行清除,占用时间较长

整个过程中占用时间最长的是并发标记和并发清除两个阶段。

CMS收集器的缺点如下:

  1. 由于使用了标记-清除算法,会导致内存碎片产生
  2. 需要预留一部分内存空间给用户,用以进行并发标记
G1收集器

G1 的主要关注点在于达到可控的停顿时间,在这个基础上尽可能提高吞吐量

G1 和 CMS 相同的地方在于,它们都属于并发收集器,在大部分的收集阶段都不需要挂起应用程序。区别在于,G1 没有 CMS 的碎片化问题(或者说不那么严重),同时提供了更加可控的停顿时间。

如果应用使用了较大的堆(如 6GB 及以上)而且还要求有较低的垃圾收集停顿时间(如 0.5 秒),此时G1是个较好的选择。

在内存划分上,之前介绍的分代收集器将整个堆分为年轻代、老年代和永久代,每个代的空间是确定的。而 G1 将整个堆划分为一个个大小相等的小块(每一块称为一个 region),每一块的内存是连续的。

和分代算法一样,G1 中每个块也会充当 Eden、Survivor、Old 三种角色,但是它们不是固定的,这使得内存使用更加地灵活。这里的eden,survivor和old是一个标签,只是一个逻辑表示,不是物理表示。如下图所示, E, S, O和其他的收集器不一样, 并不是连续的, 可以间隔存在。

p

在JVM中,存活下来的对象被虚拟机从一个region里被移动到另一个region中。这些小块(region)的回收是并行回收的,期间其他的应用线程照常工作。

G1中还存在一种特殊类型的region,叫Humongous, 主要是用来存储那些比标准块大50%,甚至更大的对象。这些大对象被保存到一整块连续的区域。需要注意的是,该区域目前还没有被优化到最佳,所以尽量不要创建很大的对象。

G1收集器主要有如下几个特点:

  1. 并行与并发。 可以通过多个CPU使用并发的方式来进行工作
  2. 空间整合。 从整体上看G1是基于标记-整理算法进行清除, 但是在两个region之间, 是基于复制算法进行清除
  3. 可预测的停顿。这是G1的一大特点。用户可设置在一个时间段内, 消耗在垃圾收集上的时间不能超过多长时间。具体实现是因为G1在进行垃圾回收时,首先会对每个region进行估值(主要是根据回收能获取到的空间大小以及回收需要时间的经验值), 并维护一个优先列表, 这样每次在进行回收的时候,根据用户设置的回收时间, 优先回收价值较大的那些region(也就是garbage first)。

现在有一个问题, 如果某个对象被其他不在该region的对象所引用, 那么在做对象的可达性分析时, 难道需要扫描整个堆吗?

明显不可能每次扫描整个堆, 这样效率会很低。G1(包括CMS也是一样)会为每个region维护一个remembered set, 当对该region内的引用对象有写操作时, 会将相关的引用信息记录到remembered set中, 在可达性分析的根节点GC Roots加入这些remembered set, 就能避免全堆扫描。

G1收集器的工作流程和CMS有些类似, 具体如下:

  1. 初始标记。和CMS类似, 只标记GC Roots直接关联的对象,所以时间很短。需要stop the world
  2. 并发标记。对对象进行可达性分析,耗时较长, 和用户线程并发执行
  3. 最终标记。对并发比较阶段由用户线程新产生的对象进行重新标记,并和初始标记的结果进行合并。 需要stop the world。
  4. 筛选回收。对各个region进行估值, 然后根据用户设置的停顿时间制定回收计划。由于只是回收部分region且暂停用户线程可以提高回收效率, 所以一般上也是需要stop the world的。

G1收集器常见的一些参数说明如下:

  • ‐XX:+UseG1GC 使用G1收集器
  • ‐XX:G1HeapRegionSize 指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区
  • ‐XX:G1NewSizePercent 新生代内存初始空间(默认整堆5%)
  • ‐XX:G1MaxNewSizePercent 新生代内存最大空间

参考资料:

深入理解Java虚拟机

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值