深入理解Java虚拟机(四)——HotSpot垃圾收集器详解
垃圾收集器
新生代收集器
1.Serial收集器
特点:
- 单线程工作,收集的时候就会停止其他所有工作线程,用户不可知不可控,会使得用户界面出现停顿。
- 简单高效,是所有收集器中额外内存消耗最少的。
- 没有线程交互的开销,单线程收集效率高。
- 对于客户端模式下的虚拟机是一个很好的选择。
- 采用标记复制算法。
2.ParNew收集器
是Serial收集器的多线程版本。采用多条GC线程并行地清理垃圾。任然需要在清理过程中停止一切用户线程。
特点:
- 多线程执行,适合多处理器环境,单处理器效率不如Serial。
- 采用标记复制算法。
- 追求降低垃圾收集时用户线程的停顿时间。
如何降低用户线程停顿:
- 在多CPU环境中使用多条GC线程,从而垃圾回收的时间减少,从而用户线程停顿的时间也减少;
- 实现GC线程与用户线程并发执行。所谓并发,就是用户线程与GC线程交替执行,从而每次停顿的时间会减少,用户感受到的停顿感降低,但线程之间不断切换意味着需要额外的开销,从而垃圾回收和用户线程的总时间将会延长。
3.Parallel Scavenge收集器
与ParNew非常相似,是多线程新生代的收集器,采用标记复制算法。ParNew收集器追求降低用户线程的停顿时间,因此适合交互式应用,但是它的目标是达到一个可控的吞吐量,适合没有交互的后台应用。
所谓吞吐量是处理器用于运行用户代码的时间与处理器总耗时的比值。
Parallel Scavenge提供的参数:
设置“吞吐量”
通过参数-XX:GCTimeRadio设置垃圾回收时间占总CPU时间的百分比,直接设置吞吐量大小。设置“停顿时间”
通过参数-XX:MaxGCPauseMillis设置垃圾处理过程最久停顿时间。收集器尽力保证垃圾回收的时间不超过这个值,时间下降的同时,吞吐量也会下降。启用自适应调节策略
通过命令-XX:+UseAdaptiveSizePolicy就能开启自适应策略。我们只要设置好堆的大小和MaxGCPauseMillis或GCTimeRadio,收集器会自动调整新生代的大小、Eden和Survior的比例、对象进入老年代的年龄,以最大程度上接近我们设置的MaxGCPauseMillis或GCTimeRadio。
老年代收集器
1.Serial Old收集器
是serial收集器的老年代版本,同样是单线程收集。区别是老年代的使用标记整理算法,而新生代是使用标记复制算法。
2.Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,支持多线程并发收集。它们会搭配使用,针对在注重吞吐量或者处理器资源较为稀缺的场合。
区别是它是基于标记整理算法的。
3.CMS 收集器
CMS是一种以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现。它在工作时,可以使用户线程和GC线程并行执行,但是存在用户线程和GC线程切换的额外开销,回收的总时间会被延长。
回收过程:
- 初始标记:只是标记下GC Roots能直接关联到的对象,需要停顿用户线程,但是很快。
- 并发标记:从GC Roots的直接关联对象开始遍历整个堆的过程,这个过程比较慢,但是不需要停顿用户线程。
- 重新标记:为了修正并发标记期间,用户线程导致的标记产生变动的那部分记录,比初始标记时间稍微长一些,需要停顿用户线程。
- 并发清除:利用清除线程,删除被标记一句死亡的对象,过程很慢,不需要停顿用户线程。
存在缺点:
- 吞吐量低:因为收集过程中用户线程和GC线程并行执行,会有线程切换的额外开销。
- 无法处理浮动垃圾,导致频繁的Full GC产生
由于垃圾清理过程中,并发标记和并发清理阶段,用户线程还在继续运行,就会继续产生新的死亡对象,而这部分没有被标记,就需要都能到下次垃圾收集标记,这部分垃圾就称为浮动垃圾。
如果CMS在垃圾清理过程中,用户线程需要在老年代中分配内存时发现空间不足时,就需要再次发起Full GC,而此时CMS正在进行清除工作,因此此时只能由Serial Old临时对老年代进行一次Full GC。 - 标记-清除算法产生内存碎片
应对方法:- 开启-XX:+UseCMSCompactAtFullCollection
开启该参数后,每次FullGC完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块儿。但每次都整理效率 不高,因此提供了以下参数。 - 设置参数-XX:CMSFullGCsBeforeCompaction
本参数告诉CMS,经过了N次Full GC过后再进行一次内存整理。
- 开启-XX:+UseCMSCompactAtFullCollection
通用垃圾收集器G1
是目前最优秀的垃圾收集器。
特点:
- 追求停顿时间
- 多线程
- 面向服务端应用
- 混合使用标记整理和标记复制孙发,不会产生内存碎片
- 堆整个堆进行垃圾回收
- 可预测停顿时间
G1的内存模型
开创基于Region的内存布局,不存在固定的新生代和老年代。将Java堆划分为很多大小相等的独立区域,每个Region都可以根据需要扮演新生代或者老年代空间。还存在Humongous区域用来存储大对象。
对各个Region区域的垃圾进行估值,然后优先处理收益最大的那些Region,保证收集器尽可能高的收集效率。
利用记忆集解决跨Region引用的问题,只需要扫描变脏的Region区域,就能避免对这个堆进行扫描。
G1运作过程:
- 初始标记:只是标记GC Roots直接关联的对象,需要停顿用户线程,很快。
- 并发标记:从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆,找出需要回收的对象,耗时长,但是可以与用户线程并发执行。
- 最终标记:标记出并发标记过程中用户新产生的垃圾,需要暂停用户线程,很快。
- 筛选回收:移动存活对象到空的Region中,清空整个Region,多条收集线程并行,需要暂停用户线程。·
G1与GMS比较
G1优点:以指定最大停顿时间、分Region的内存布局、按收益动态确定回收集这些创新性设计带来的红利。整体采用标记整理,局部采用标记复制,不会产生内存空间碎片,空间利用率高。
G1缺点:垃圾收集时产生的内存占用和程序运行时的额外执行负载高于GMS。空间换时间。
具体实现细节知识点
根节点枚举
所谓根节点枚举就是在垃圾收集过程中,利用可达性算法标记有效对象前,寻找GC Roots的集合。
固定可作为GC Roots的节点主要在全局性的引用(如常量)与执行上下文(例如栈帧中的本地变量表)中。
存在问题:在根节点枚举的时候必须暂停用户线程,存在Stop the world的困扰。否则分析结果的准确性无法保证。
主流的Java虚拟机采用的都是准确式垃圾收集,所以在用户线程停顿之后需要一个不漏的检查完所有的执行上下文和全局变量中的引用位置。在HotSpot的解决方案中,利用称为OopMap的数据结构,在类加载完成时,将对象的数据计算出来,在即时编译的时候,在特定的位置记录下栈和寄存器中那些位置时引用,这样垃圾收集器在扫描的时候就利用这些信息进行获取引用,就不需要从方法区一个不漏地开始查找。
安全点
存在问题:利用OopMap,HotSpot可以快速地完成GC Roots的枚举,但是如果将使OopMap内存变化的指令都生成相应的数据,就会导致耗费大量的额外内存空间。
解决方案:生成OopMap时,只需要再特定的位置记录对象信息,这些位置被称为安全点。通过安全点,在用户线程中不是代码指令流的任意位置都能停顿下来进行来回收,而是必须到到安全点后才能暂停。
安全点的注意问题:
- 安全顶点位置选取:安全点不能太少,否则会使得收集器等待时间过长,也不能太多,这样频繁运行增大内存负荷。安全点位置的选取基本上是以“是否具有让程序长时间执行的特征”为标准进行选定的,例如方法调用、循环跳转、异常跳转等都属于指令序列复用,所以只有具有这些功能的指令才会产生安全点
- 如果在垃圾收集时,让所有线程都跑到最近的安全点暂停:
- 抢断式中断:不需要线程主动配合,发生垃圾收集的时候系统就把所有线程中断,如果发现某个线程不在安全点上,则恢复这条线程的执行,让它跑到安全点上。几乎没有虚拟机采用这个方式。
- 主动式中断:垃圾收集时,系统不对线程直接操作,而是设置线程上的一个标志位,各个线程在执行的时候,每当跑到安全点时就去判断这个标志位,如果发现这个标志被修改,就主动暂停运行。
安全区域
存在问题:安全点保证程序在运行的时候在短时间内都能停顿下来,进入垃圾收集过程的信息收集。但是,如果线程本来就被阻塞或者睡眠了,就无法进入安全点挂起自己。
解决方案:必须引入安全区域来解决,是指能够确保在某一段代码片中,引用关系不会发生变化,因此在这个区域的任意地方开始垃圾收集都是安全的。可以把安全区域看多被拉伸的安全点。
当用户线程进入安全区域,就会标识自己,这样虚拟机在发生垃圾回收的时候就不会管这些在安全区的线程。当线程离开安全区,虚拟机就会要求其提交根节点枚举,没有完成就继续留着。这样,线程被阻塞在安全区域,就不会有影响,因为其引用没有发生改变。
记忆集与卡表
存在问题:老年代中的对象可能会引用新生代中的对象,这样在Minor GC的时候,就需要扫描整个老年代,非常耗时。
解决方案:通过记忆集记录从非收集区指向收集区的内存区域集合。这样,需要收集新生代的时候,只需要扫描被记忆集标识过的老年代,就能检测出所有的跨代引用,而不需要扫描整个老年代。
记忆集中的记录粒度:
- 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的32位或64位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
- 对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。
- 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。
其中卡精度所指的是利用卡表的方式实现记忆集。这是最常用的实现方式。卡表就是记忆集的一种具体实现,它定义了记忆集的记录精度、与堆内存的映射关系等。卡表最简单的形式可以只是一个字节数组,数组的每个元素对于一个内存块,这个块被称为卡页。当卡页中的字段存在跨代引用,那么这个页就被变脏,垃圾收集的时候,只需要将脏的卡页加入GCRoots中一并扫描。
写屏障
存在问题:如何维护卡表元素。
解决方案:Hotspot通过写屏障来维护卡表状态。这个写屏障有点像spring的AOP切面,当发生引用对象的赋值,就会产生一个around环形通知,在通知中完成卡表的状态更新。
深入理解Java虚拟机(四)——HotSpot垃圾收集器详解的更多相关文章
- 《深入理解 Java 虚拟机》学习 -- 垃圾收集器
<深入理解 Java 虚拟机>学习 -- 垃圾收集器 1. Serial 收集器(新生代) 含义: 单线程收集器. 缺点: 进行垃圾收集时,必须暂停其他所有的工作线程. 优点: 简单而高效 ...
- 深入理解JVM(5)——HotSpot垃圾收集器详解
HotSpot虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,没有最好的垃圾收集器,只有最适合的垃圾收集器.根据新生代和老年代各自的特点,我们应该分别为它们选择不同的收集器,以提升垃圾回收效率. ...
- 理解JVM之垃圾收集器详解
前言 垃圾收集器作为内存回收的具体表现,Java虚拟机规范并未对垃圾收集器的实现做规定,因而不同版本的虚拟机有很大区别,因而我们在这里主要讨论基于Sun HotSpot虚拟机1.6版本Update22 ...
- Java 虚拟机运行时数据区详解
本文摘自深入理解 Java 虚拟机第三版 概述 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟 ...
- 深入理解Java虚拟机03--垃圾收集器与内存分配策略
一.概述 哪些内存需要回收? 什么时候回收? 如何回收? 二.对象已死吗 1.引用计数算法 定义:给对象添加一个引用计数器,当增加一个引用时,加1,当一个引用时,减1; 缺陷:当对象之间互相循环 ...
- 深入理解Java虚拟机(二)——HotSpot对象创建、内存、访问
对象的创建 虚拟机遇到一条字节码new指令时,开始对象创建过程. 首先去检查这个指令的参数是否能在常量池定位到一个类的符号引用: 检查这个符号引用代表的类是否已被加载.解析和初始化,如果没有就必须执行 ...
- 深入理解Java虚拟机二:垃圾收集与内存分配
垃圾收集:垃圾收集要完成三件事,包括哪些内存需要回收,什么时候回收及如何回收. 1.需要回收的内存判定:没有引用指向原先分配给某个对象的内存时,则该内存是需要回收的垃圾 Java垃圾收集器在对内存进行 ...
- java - GC垃圾收集器详解(三)
以前收集器的特点 年轻代和老年代是各自独立且连续的内存块 年轻代收集必须使用单个eden+S0+S1进行复制算法 老年代收集扫描整个老年代区域 都是以尽可能少而快速地执行GC为设计原则 G1是什么 G ...
- java - GC垃圾收集器详解(一)
概要 该图标记了在jdk体系中所使用到的垃圾收集器及对应的关系图.图片上方为年轻代的垃圾收集器而图片下方是老年代的垃圾收集器.当选择某一个区域的垃圾收集器时会自动选择另外一个区域的另一个垃圾收集器.例 ...
随机推荐
- Pytest学习(十) - parametrize、fixture、request的混合使用
一.前言 上篇文章有提及pytest.mark.parametrize的使用,这次在此基础上结合fixture和request再做个延伸. 二.传单个参数 即一个参数一个值,示例代码如下: # 传单个 ...
- 服务器虚拟化 - PVE
服务器虚拟化 - Hypervisor 服务器虚拟化软件,也叫 Hypervisor--虚拟机管理程序,有时也称做 Virtual Machine Monitor(VMM),它可以在宿主机上创建并管理 ...
- SQL 查找"存在",别再用 count 了,很耗费时间的!
根据某一条件从数据库表中查询 『有』与『没有』,只有两种状态,那为什么在写SQL的时候,还要SELECT count(*) 呢? 无论是刚入道的程序员新星,还是精湛沙场多年的程序员老白,都是一如既往的 ...
- Java面试必会-微服务权限认证
微服务身份认证方案 1. 单点登录(SSO) 这种方案意味着每个面向用户的服务都必须与认证服务交互,这会产生大量非常琐碎的网络流量和重复的工作,当动辄数十个微应用时,这种方案的弊端会更加明显. 2. ...
- property内置装饰器函数和@name.setter、@name.deleter
# property # 内置装饰器函数 只在面向对象中使用 # 装饰后效果:将类的方法伪装成属性 # 被property装饰后的方法,不能带除了self外的任何参数 from math import ...
- 使用create-react-app 搭建react + ts + antd框架
一.创建项目 使用npx create-react-app (项目名) --template typescript 创建项目 ①如果App.tsx文件有如下报错: (没有报错的请忽略) 需要将tsco ...
- PHP反序列化漏洞-CVE-2016-7124(绕过__wakeup)复现
前言 最近电脑也不知怎么了时不时断网而且我竟然找不出原因!!!很诡异.... 其他设备电脑都OK唯独我的电脑 时好时坏 我仿佛摸清了我电脑断网的时间段所以作息时间都改变了 今天12点多断网刷了会手 ...
- MindManager使用教程:如何导出HTML5交互式导图
Mindmanager思维导图软件有着友好的用户界面以及丰富的思维导图制作功能.再搭配与Microsoft 软件的无缝集成功能,使得这款思维导图软件越来越受到职场人士的喜爱. 不仅是作为制作思维导图的 ...
- python接口测试4-数据库获取参数
首先确定需要传递的参数和接口,使用接口测试工具验证一下,接口和参数没有问题. 编写python接口脚本 import requests import unittest import json impo ...
- 简单好用的TCP/UDP高并发性能测试工具
工具下载地址: 链接:https://pan.baidu.com/s/1fJ6Kz-mfFu_RANrgKqYiyA 提取码:0pyf 最近测试智能设备的远程的性能,思路主要是通过UDP对IP和端口发 ...