1.背景

在上一节中,我们掌握了垃圾收集的一些算法,也弄明白了分代回收的原理,

那么HotSpot虚拟机是如何发起内存回收的?

2.如何找到GC Roots根节点(枚举根节点)

  从可达性分析中GC Roots 节点找引用链这个操作为例,可作为GC Roots 的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中,现在的很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然会消耗很多时间。

  另外,可达性分析对执行时间的敏感还体现在GC停顿上,因为这项分析工作必须在一个能确保一致性的快照中进行—这里的一致性的意思是指在整个分析期间整个执行系统看起来像被冻结在某个时间点上,不可以出现在分析过程中对象引用关系还在不断的变化,该点不满足的话分析结果的准确性就无法得到保证。这点导致GC进行时必须停顿所有Java执行线程(Sun称这件事情为“Stop The World”)的其中一个重要的原因,即使在号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。

  目前主流的Java虚拟机使用的都是准确式GC,所以当执行系统停顿下来后,并不需要一个不漏的检查完所有的执行上下文和全局的引用位置,虚拟机应当是有办法直接得知哪些地方存在着对象引用。在HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样,GC在扫描时就可以直接得知这些信息了。

总结:

3.安全点

3.1.什么是安全点,为什么需要安全点

在 JVM 中如何判断对象可以被回收 一文中,我们知道 HotSpot 虚拟机采取的是可达性分析算法。即通过 GC Roots 枚举判定待回收的对象。

那么,首先要找到哪些是 GC Roots。

有两种查找 GC Roots 的方法:

一种是遍历方法区和栈区查找(保守式 GC)-成本高。

一种是通过 OopMap 数据结构来记录 GC Roots 的位置(准确式 GC)-可以快速定位。

很明显,保守式 GC 的成本太高。

准确式 GC 的优点就是能够让虚拟机快速定位到 GC Roots。

对应 OopMap 的位置即可作为一个安全点(Safe Point),因为不能为每一条指令都生成对应的OopM,那将会需要大量的额外空间

在执行 GC 操作时,所有的工作线程必须停顿,这就是所谓的”Stop-The-World”。

为什么呢?

因为可达性分析算法必须是在一个确保一致性的内存快照中进行。如果在分析的过程中对象引用关系还在不断变化,分析结果的准确性就不能保证。

安全点意味着在这个点时,所有工作线程的状态是确定的,JVM 就可以安全地执行 GC 。

3.2.如何选择安全点

安全点太多,GC 过于频繁,增大运行时负荷;

安全点太少,GC 等待时间太长。

一般会在如下几个位置选择安全点:

1、循环的末尾

2、方法临返回前

3、调用方法之后

4、抛异常的位置

3.3.为什么选择以上的位置作为安全点

主要的目的就是避免程序长时间无法进入 Safe Point。

比如 JVM 在做 GC 之前要等所有的应用线程进入安全点,

如果有一个线程一直没有进入安全点,就会导致 GC 时 JVM 停顿时间延长。

比如这里,超大的循环导致执行 GC 等待时间过长。

3.4.如何在GC发生时,所有线程都跑到最近安全点上

主要有两种方式:

抢断式中断:在 GC 发生时,首先中断所有线程,如果发现线程未执行到 Safe Point,就恢复线程让其运行到 Safe Point 上。

主动式中断:在 GC 发生时,不直接操作线程中断,而是简单地设置一个标志,让各个线程执行时主动轮询这个标志,发现中断标志为真时就自己中断挂起。

JVM 采取的就是主动式中断。轮询标志的地方和安全点是重合的。

4.安全区域

4.1.为什么需要安全域

上面讲述的Safepoint似乎已经完美的解决了如何进入GC的问题,但实际情况却不一定。

Safepoint机制保证了程序执行时,在不长的时间内就会遇到可进入GC的Safepoint。

但是,程序不执行的时候呢?所谓的程序不执行就是没有分配CPU时间,典型的例子就是线程处于sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全的地方去中断挂起,

JVM显然也不太可能等待线程重新被分配CPU时间。这种情况,就需要安全区域来解决了。

4.2.什么是安全域

Safe Region 是指在一段代码片段中,引用关系不会发生变化(如sleep的时候)。在这个区域内的任意地方开始 GC 都是安全的。

4.3.安全域中如何发起GC

线程在进入 Safe Region 的时候先标记自己已进入了 Safe Region,

等到被唤醒时准备离开 Safe Region 时,先检查能否离开,

如果 GC 完成了,那么线程可以离开,

否则它必须等待直到收到安全离开的信号为止。

5.综合总结

假设HotSpot需要发生GC,首先需要找所有的GC Roots根节点,

但是在内存中要想找到所有的根节点,并检查引用链,必然会消耗很多时间,HotSpot肯定不会去扫描整个内存去找根节点;

HotSpot是使用一组oopMap数据结构来存放对象的引用,这样就可以快速准确的完成GC Roots的查找;

但是,问题又来了,如果每一条指令都生成对应的oopMap将会需要大量的额外空间;

那怎么办呢,我们可以只在需要垃圾回收的点上记录(生成oopMap)这些信息,这个点我们就叫这安全点,要发送GC时,等所有的线程都运行到这个点上再回收;

但是问题又来了,如果有的线程处于sleep或者Blocked状态呢,这时候这些线程无法响应JVM的中断请求,到达安全点;

这样这种情况那就是安全域来解决,在安全域中引用关系不会发生变化,所以只要在安全域中就可以直接回收,不需要到达安全点;

完美!

参考:《深入理解Java虚拟机》 周志明 编著:

JVM之HotSpot虚拟机是如何发起内存回收的? 转载的更多相关文章

  1. JVM之HotSpot虚拟机是如何发起内存回收的?

    1.背景 在上一节中,我们掌握了垃圾收集的一些算法,也弄明白了分代回收的原理, 那么HotSpot虚拟机是如何发起内存回收的? 2.如何找到GC Roots根节点(枚举根节点) 从可达性分析中GC R ...

  2. 深入理解JVM:HotSpot虚拟机对象探秘

    对象的创建 java是一门面向对象的语言.在Java程序执行过程中无时无刻有Java对象被创建出来.在语言层面上,创建对象(克隆.反序列化)一般是一个newkeyword而已,而在虚拟机中,对象的创建 ...

  3. 深入理解JVM(③)——之HotSpot虚拟机对象探秘

    前言 上篇文章介绍了Java虚拟机的运行时数据区域,大致明白了Java虚拟机内存模型的概况,下面就基于实用优先的原则,以最常用的虚拟机HotSpot和最常用的内存区域Java堆为例,升入探讨一下Hot ...

  4. JVM:Hotspot虚拟机中的对象

    在HotSpot虚拟机中,对象在内存中存储的布局可以被分为3个区域:对象头(Header).实例数据(Instance data)和对齐填充(Padding).对象头包括两部分信息,第一部分存储自身的 ...

  5. Java基础-JVM内存回收

    Sun的JVMGenerationalCollecting(垃圾回收)原理是这样的:把对象分为年青代(Young).年老代(Tenured).持久代(Perm),对不同生命周期的对象使用不同的算法.( ...

  6. 虚拟机--第二章java内存区域与内存溢出异常--(抄书)

    这是本人阅读周志明老师的<深入理解Java虚拟机>第二版抄写的,有很多省略,不适合直接阅读,需要阅读请出门左转淘宝,右转京东,支持周老师(侵权请联系删除) 第二章java内存区域与内存溢出 ...

  7. Java内存回收 - 落日之心的日志 - 网易博客

    body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...

  8. Java虚拟机垃圾回收(二) :垃圾回收算法(转载)

    1.标记-清除算法 标记-清除(Mark-Sweep)算法是一种基础的收集算法. 1.算法思路 "标记-清除"算法,分为两个阶段: (A).标记 首先标记出所有需要回收的对象: 标 ...

  9. JVM系列之七:HotSpot 虚拟机

    1. 对象的创建 1. 遇到 new 指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载.解析和初始化过.如果没有,执行相应的类加载. 2 ...

随机推荐

  1. python 基础知识-day10(面向对象)

    1.面向对象的概念 拥有共同属性的一类进行归类的过程叫做面向对象. 2.注意事项 class定义类的时候,类名的首字母必须大写 3.面向对象案例 1 class Person(object): 2 d ...

  2. python基础知识-day8(动态参数)

    1.动态参数 函数的形式参数个数不确定.函数的形式数据类型不确定,使用动态参数,*代表元组,**代表字典. 2.代码案例演示 1 def func(*args,**kwargs): 2 print(a ...

  3. Nginx+Keepalived+VIP漂移实现HA高可用技术之详细教程

    https://www.cnblogs.com/zcc666/p/13141626.html  这个是nginx安装教程地址 https://www.cnblogs.com/zcc666/p/1313 ...

  4. NC19115 选择颜色

    NC19115 选择颜色 题目 题目描述 \(n\) 个人排成一个环形,每个人要从 \(c\) 种颜色中选择一个. 牛牛希望相邻的人选择的颜色是不同的 问有多少种方案. 输出方案数对 \(10007\ ...

  5. APISpace万券齐发,API采购大放价

    Eolink APISpace 是 Eolink 旗下专业的API 数据交易平台,上面拥有海量的API,开发者可以根据需求自由选择. 环境天气 全国天气预报,支持全国以及全球多个城市的天气查询,包含国 ...

  6. Note -「狄利克雷前缀和」

    学到一个诡异东西,当个 Trick 处理用吧. 现在有一个形如 \(\sum \limits _{i = 1} ^{n} \sum \limits _{d | i} f(d)\) 的柿子,不难发现可以 ...

  7. CF1706A Another String Minimization Problem 题解

    题意 给定一个长度为 \(n\) 的序列 \(a\) 以及一个长度为 \(m\) 的字符串 \(s\),初始 \(s\) 均为 \(\text{B}\),第 \(i\) 次操作可以把 \(s_{a_i ...

  8. Netty源码解读(一)-前置准备

    前置条件 源码版本netty4.1 了解Java NIO.Reactor模型和Netty的基本使用. 解释一下: Java NIO:了解BIO和NIO的区别以及Java NIO基础API的使用 Rea ...

  9. 常用的函数式接口_Supplier和常用的函数式接口Supplier接口练习_求数组中元素最大值

    Supplier接口 package com.yang.Test.SupplierStudy; import java.util.function.Supplier; /** * 常用的函数式接口 * ...

  10. MySQL主从复制原理及搭建过程

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 复制概述 复制即把一台服务器上的数据通过某种手段同步到另外一台或多台从服务器上,使得从服务器在数据上与主服务器保持一致. ...