JVM学习(三):垃圾回收算法
局部性原理和分代回收思想
大学学习操作系统或者计算机组成原理的时候都提到一个重要概念,叫局部性原理。
后来发现,这个原理说的存储器不只是高速缓存(Cache),访问内存(RAM)、磁盘(ROM)时都有局部性。其实在项目中使用redis、memcache这样的缓存也体现了局部性原理的重要性。
在Java虚拟机的堆空间中,活跃的对象往往是小部分,大多数对象创建若干次后就会被回收,这就让Java虚拟机引入了分代回收的算法。我不清楚Sun公司的研究人员是不是受到了局部性原理的启发,不过我确实是通过局部性原理理解了分代思想。
分代回收就是将堆空间分为新生代和老年代。新生代用来存储新创建的对象。当对象存活时间很长时将其移动到老年代。根据新生代和老年代的特性,Java虚拟机可以采用不同的回收算法。
新生代的对象大多数存活时间很短,因此可以采用耗时短的回收算法,新生代触发的GC一般叫做Minor GC。
老年代的对象往往可以长时间存活,因此老年代的垃圾回收频率不高,往往是堆空间用完的时候才会针对老年代进行垃圾回收。但老年代的回收一般是进行全堆扫描,找出能被回收的空间,这会耗费大量时间。现代的垃圾回收器会用各种手段避免进行全堆扫描。老年代的GC叫做 Full GC。
本篇我们重点关注的是Minor GC,也就是针对新生代的垃圾回收。
新生代的内存划分和回收机制
上一篇提到了复制算法,他的思想是把堆空间分为1:1两部分,并维持两个from和to指针。由于Java虚拟机中活跃的对象是小部分,因此实际上复制算法并不需要保持1:1的比例(Sun公司给出的理论上是98%的对象都是用几次就回收了的)。假定按照我们预测的,新生代的大部分对象会死亡,那么使用复制算法仅需要复制少量的数据,算法效果也会很好。因此新生代又被划分为 Eden区和两个大小相同的 Survivor区。
Survivor区的大小默认是自动调节的,也可以手动调节。需要注意的是,Survivor区分配的内存越多,堆空间的使用效率越低。

我们使用new指令时,new指令会在Eden区中划分出一块作为存储对象的内存。如果new新对象时Eden 区满了,这时候会触发一次Minor GC。Minor GC存活下来的对象会移动到Survivor区。Java虚拟机会记录Survivor区中的对象被复制了多少次。
跟上篇说到的复制算法一样,to指针指向一个空的Survivor区。进行Minor GC时,Eden区和from中的存活对象会被复制到to指向的区域中,然后交换from和to指针。这样当下次有Minor GC的时候保证to中的内容是空的。
什么时机晋升老年代
有两种情况会让新生代的对象晋升到老年代。第一种是对象复制次数达到设定值。如果一个对象复制次数为15(默认为15,可使用 -XX:+MaxTenuringThreshold修改),则这个对象会被晋升到老年代。第二种是Survivor区占用超过50%(可使用 -XX:TargetSurvivorRatio修改)时,虚拟机会将复制次数较高的对象复制到老年代。
Minor GC如何避免全堆扫描
我们希望Minor GC时只扫描新生代的GC Roots,然而老年代对象有可能引用了新生代对象。这种情况我们无法预期,所以Minor GC的时候必须要考虑老年代对新生代对象的引用,把老年代对新生代的引用加入GC Roots里面。你会发现,新生代的GC要扫描老年代,这岂不是跟全堆扫描一样了吗?
Hotspot虚拟机使用了卡表(Card Table)技术去解决这个问题。具体操作是Java虚拟机把整个堆分成一个个512字节的卡,并且维护一张表用来存储每张卡的标识位。这个标识位代表这张卡是否包含对新生代对象的引用。如果存在就认为这张卡是脏的。
这样在Minor GC的时候就可以不进行全堆扫描,而是从卡表中寻找脏卡,并将脏卡中的对象加入到GC Roots中。脏卡扫描结束后则清空标识位。
写屏障(write barrier)
在解释执行器中,Java虚拟机需要截获每个可能更新引用的操作,并把对应位置的标识位标记为脏。这样来保证每个可能指向新生代对象的卡都被标记到。
但是在即时编译器生成的机器码中,这块代码并不在Java虚拟机管理之下。因此这部分代码需要插入额外的逻辑,这就是所谓的写屏障。
写屏障不会判断是否指向了新生代对象,而是把这块区域认为已经指向了新生代。这样就简化了指令,变为一个简单的移位操作。写屏障虽然带来了性能上的开销,但是它加大了Minor GC的吞吐率。因此这些代价还是值得的。
虚共享(false sharing)
假设CPU缓存行大小为64字节,由于一个卡表项占1个字节,这代表一共有64张卡。HotSpot每个卡页为512字节,那么一个缓存行将对应64个卡页一共64*512=32KB。
如果不同线程对对象引用的更新操作,恰好位于同一个32KB区域内,这将导致同时更新卡表的同一个缓存行,从而造成缓存行的写回、无效化或者同步操作,间接影响程序性能。
Hotspot引入了新的参数-XX:+UseCondCardMark,来减少写卡表的操作。在执行写屏障之前,先简单的做一下判断。如果卡页已被标识过,则不再进行标识。伪代码如下:
if (CARD_TABLE [this address >> 9] != 0)
CARD_TABLE [this address >> 9] = 0;
总结
总结一下,本篇从局部性原理出发引出了 Java 虚拟机分代回收的思想。分代回收是指,堆内存分为新生代和老年代,并采用不同的垃圾回收算法。
其中把新生代分为 Eden区和两个大小相同的 Survivor 区。新生代的GC 成为 Minor GC。Minor GC 时 Eden 区和 from 指向的 Survivor 区的存活对象会被存储到 to 指向的 Survivor 区。当 Survivor 区对象复制次数到一定值或者Survivor区空间使用超过一定值的时候,会把对象晋升到老年代。
针对Minor GC 可能出现的老年代对象包含新生代对象引用的问题,Hotspot虚拟机是用卡表技术来解决的。
参考文章
JVM之卡表(Card Table)
郑雨迪:深入拆解虚拟机
深入理解Java虚拟机:JVM高级特性与最佳实践
JVM学习(三):垃圾回收算法的更多相关文章
- JVM学习--(四)垃圾回收算法
我们都知道java语言与C语言最大的区别就是内存自动回收,那么JVM是怎么控制内存回收的,这篇文章将介绍JVM垃圾回收的几种算法,从而了解内存回收的基本原理. stop the world 在介绍垃圾 ...
- JVM学习记录-垃圾回收算法
简述 因为各个平台的虚拟机的垃圾收集器的实现各有不同,所以只介绍几个常见的垃圾收集算法. JVM中常见的垃圾收集算法有以下四种: 标记-清除算法(Mark-Sweep). 复制算法(Copying). ...
- JVM学习笔记——垃圾回收篇
JVM学习笔记--垃圾回收篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分 我们会分为以下几部分进行介绍: 判断垃圾回收对象 垃圾回收算法 分代垃圾回收 垃圾回收器 ...
- JVM中的垃圾回收算法GC
GC是分代收集算法:因为Young区,需要回收垃圾对象的次数操作频繁:Old区次数上较少收集:基本不动Perm区.每个区特点不一样,所以就没有通用的最好算法,只有合适的算法. GC的4大算法 1.引用 ...
- jvm系列三垃圾回收
三.垃圾回收 1.如何判断对象可以回收 引用计数法 弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放 可达性分析算法 JVM中的垃圾回收器通过可达性分析来探索所有存活的对象 扫描堆中的 ...
- JVM虚拟机和垃圾回收算法
类加载机制 双亲委派模型 垃圾回收算法 CMS G1 类加载机制 双亲委派模型 双亲委派模型: 需要加载一个类,先委托父类加载,父类找父类,依次递归加载;加载不到再由自己加载 垃圾回收算法 JVM的内 ...
- @JVM新一代的垃圾回收算法
垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要 ...
- 【JVM】JVM中的垃圾回收算法
1.标记 -清除算法 "标记-清除"(Mark-Sweep)算法,如它的名字一样,算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收 ...
- 谈谈JVM垃圾回收机制及垃圾回收算法
一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理.由于有个垃圾回收机制 ...
- Java中的垃圾回收算法详解
一.前言 前段时间大致看了一下<深入理解Java虚拟机>这本书,对相关的基础知识有了一定的了解,准备写一写JVM的系列博客,这是第二篇.这篇博客就来谈一谈JVM中使用到的垃圾回收算法. ...
随机推荐
- 小D课堂 - 新版本微服务springcloud+Docker教程_4-06 Feign核心源码解读和服务调用方式ribbon和Feign选择
笔记 6.Feign核心源码解读和服务调用方式ribbon和Feign选择 简介: 讲解Feign核心源码解读和 服务间的调用方式ribbon.feign选择 ...
- Spring-Kafka —— KafkaListener禁止自启动
应用服务启动时,KafkaListener默认会自动启动进行消费,如果想不自动消费,可以设置AutoStartup属性值为false @Override @KafkaListener(id = Con ...
- vue项目windowServer nginx下部署
https://www.cnblogs.com/jiangwangxiang/p/8481661.html nginx下载地址 http://nginx.org/en/download.html 下载 ...
- DS18b20温度传感器基础使用
认识管脚 认识唯一标示的64位地址序列号 寄存器数据译码成温度值(下面只针对12位转化的,还有9..10等其他位的转化方式,不同位的转化,其精度也不同) 传感器存储器 配置寄存器使用说明 DS18b2 ...
- LeetCode.1200-最小绝对值差(Minimum Absolute Difference)
这是小川的第次更新,第篇原创 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第268题(顺位题号是1200).给定一个由不同的整数组成的数组arr,找到所有对元素,其中任意两个元素的绝 ...
- Element ui 2.8版本中的table树不能默认全展开解决方法
方案一:这个方案有问题 <el-table ref="tableTreeRef" :data="tableDate" ...... </el-tab ...
- Prettier格式化配置
HTML/CSS/JS/LESS 文件的 prettier 格式化规则 { // 使能每一种语言默认格式化规则 "[html]": { "editor.defaultFo ...
- elasticsearch 7.1 401 Unauthorized
1.执行curl -XGET 'localhost:9200/_cat/indices?v&pretty' 报401 2.修改配置xpack.security.enabled: false ...
- BiLSTM-CRF模型理解
适用任务 中文分词.词性标注.命名实体识别是自然语言理解中,基础性的工作,同时也是非常重要的工作. 在很多NLP的项目中,工作开始之前都要经过这三者中的一到多项工作的处理. 在深度学习中,有一种模型可 ...
- [计蒜客T2237]魔法_树
魔法 题目大意: 数据范围: 题解: 这个题挺好玩的 可以用反证法,发现所有叶子必须都得选而且所有叶子都选了合法. 故此我们就是要使得,一次操作之后使得叶子的个数最少. 这怎么弄呢? 我们发现,如果一 ...