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中使用到的垃圾回收算法. ...
随机推荐
- c99数组稀疏初始化
#include <stdio.h> #include <stdlib.h> int main() { ] = {[]=,,[] =}; ; i < ; i++) { p ...
- StringJoiner,StringBuffer的一些lamada写法
public String friendlyText(List data) { if(CollectionUtils.isEmpty(data)) { return "[]"; } ...
- JAVA 基础编程练习题14 【程序 14 求日期】
14 [程序 14 求日期] 题目:输入某年某月某日,判断这一天是这一年的第几天? 程序分析:以 3 月 5 日为例,应该先把前两个月的加起来,然后再加上 5 天即本年的第几天,特殊情况, 闰年且输入 ...
- 概率dp作业
概率dp特征: 概率DP一般求的是实际结果,在DP过程中,当前状态是由所有子状态的概率共同转移而来,所以概率DP只是利用了DP的动态而没有规划(即只需转移无需决策).-------qkoqhh A - ...
- java-创建对象实例
1.A a = new A(); 2.Class class = B.Class; B b = class .newInstance();
- RestTemplate连接池(转载)
出处:http://zhangzhi19861216.cnblogs.com/ spring-boot RestTemplate 连接池 以前我们项目都是基于Apache HttpClient 连接池 ...
- 【VS开发】【C/C++开发】传递双重指针申请内存,典型用法
传递双重指针申请内存,典型用法 指针参数是如何传递内存的? 如果函数的参数是一个指针,不要指望用该指针去申请动态内存.如下示例中,Test函数的语句GetMemory(str, 100)并没有使str ...
- Java学习笔记-反射机制
Java反射机制实在运行状态时,对于任意一个类,都能够知道这个类的属性和方法,对于任意一个对象,都能够调用他的任意一个属性和方法 获取Class对象的三种方式 Object类中的getClass()方 ...
- e4a 记录
自动点击 浏览框.跳转("javascript:document.getElementById('按钮的ID').clcik();")
- 【转帖】Office的光荣历史(1)
Office的光荣历史(1) https://www.sohu.com/a/201410882_657550 微软的第一版本的office 竟然是 给Mac OS 提供的.. 2017-10-31 1 ...