局部性原理和分代回收思想

大学学习操作系统或者计算机组成原理的时候都提到一个重要概念,叫局部性原理。

局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。

后来发现,这个原理说的存储器不只是高速缓存(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学习(三):垃圾回收算法的更多相关文章

  1. JVM学习--(四)垃圾回收算法

    我们都知道java语言与C语言最大的区别就是内存自动回收,那么JVM是怎么控制内存回收的,这篇文章将介绍JVM垃圾回收的几种算法,从而了解内存回收的基本原理. stop the world 在介绍垃圾 ...

  2. JVM学习记录-垃圾回收算法

    简述 因为各个平台的虚拟机的垃圾收集器的实现各有不同,所以只介绍几个常见的垃圾收集算法. JVM中常见的垃圾收集算法有以下四种: 标记-清除算法(Mark-Sweep). 复制算法(Copying). ...

  3. JVM学习笔记——垃圾回收篇

    JVM学习笔记--垃圾回收篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分 我们会分为以下几部分进行介绍: 判断垃圾回收对象 垃圾回收算法 分代垃圾回收 垃圾回收器 ...

  4. JVM中的垃圾回收算法GC

    GC是分代收集算法:因为Young区,需要回收垃圾对象的次数操作频繁:Old区次数上较少收集:基本不动Perm区.每个区特点不一样,所以就没有通用的最好算法,只有合适的算法. GC的4大算法 1.引用 ...

  5. jvm系列三垃圾回收

    三.垃圾回收 1.如何判断对象可以回收 引用计数法 弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放 可达性分析算法 JVM中的垃圾回收器通过可达性分析来探索所有存活的对象 扫描堆中的 ...

  6. JVM虚拟机和垃圾回收算法

    类加载机制 双亲委派模型 垃圾回收算法 CMS G1 类加载机制 双亲委派模型 双亲委派模型: 需要加载一个类,先委托父类加载,父类找父类,依次递归加载;加载不到再由自己加载 垃圾回收算法 JVM的内 ...

  7. @JVM新一代的垃圾回收算法

    垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要 ...

  8. 【JVM】JVM中的垃圾回收算法

    1.标记 -清除算法 "标记-清除"(Mark-Sweep)算法,如它的名字一样,算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收 ...

  9. 谈谈JVM垃圾回收机制及垃圾回收算法

    一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理.由于有个垃圾回收机制 ...

  10. Java中的垃圾回收算法详解

    一.前言   前段时间大致看了一下<深入理解Java虚拟机>这本书,对相关的基础知识有了一定的了解,准备写一写JVM的系列博客,这是第二篇.这篇博客就来谈一谈JVM中使用到的垃圾回收算法. ...

随机推荐

  1. Spring Security登录超时,angular ajax请求出错自动跳转至登录页(jQuery也适用)

    公司开发采用Spring Security+AngualerJS框架,在session过期之后,ajax请求会直接出错.本文介绍如何实现出错情况下自动跳转至登录页. 整体思路是,session过期后, ...

  2. powerdesigner使用遇到的一些问题

    1.数据库逆向生成er图时,连接数据库问题 由于powerdesigner版本是32位,可能就导致不兼容64位的机器,导致连接mysql失败: 解决方法:方法1.重新配置32位jdk 方法2.mysq ...

  3. SSM整合junit单元测试之org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):

    想用SSM做一点小测试,项目整合完毕,直接使用junit测试mybatis,出现如下错误(SuperTest类中进行了spring运行环境加载): 解决思路: 检查mapper接口与mapper.xm ...

  4. 树莓派上安装Samba实现树莓派与Windows间的文件共享

    我是参考这篇文章的: https://www.jianshu.com/p/ead92b06318e 安装samba和common-bin库(具有smbpasswd效用) sudo apt-get in ...

  5. python基础知识(循环语句)

    for循环.while循环.循环嵌套 for 迭代变量 In 对象: 循环体 range(start,end,step) 第一个和第三个可以省略生成一系列的连续整数 start 包括起始值 end  ...

  6. STS MVC与MyBatis的结合

    1. MVC关键点在于Controller 1.1 Controller通过返回两种类型的数据完成用户端请求的回复:一种是模型(视图),另一种是JSON数据. 1.2 Controller类采用@Co ...

  7. 【POJ - 3045】Cow Acrobats (贪心)

    Cow Acrobats Descriptions 农夫的N只牛(1<=n<=50,000)决定练习特技表演. 特技表演如下:站在对方的头顶上,形成一个垂直的高度. 每头牛都有重量(1 & ...

  8. Data Exfiltration with DNS in MSSQL SQLi attacks

    DNS解析过程 DNS解析过程 DNS 查询的过程如下图1所示. 图1 文字举例说明: 假定浏览器想知道域名xprp8i.dnslog.cn的IP地址. 1.浏览器先向本地DNS服务器进行递归查询. ...

  9. Git速成学习第一课:创建版本库与版本回退

    Git速成学习笔记整理于廖雪峰老师的官网网站:https://www.liaoxuefeng.com/ 我太困了0.0精神点再写...... /*我来啦!以后会陆续更新自己的学习笔记*/ Git是分布 ...

  10. ZOJ Problem Set - 1007

    1.参考这个吧,一道数学公式题,还没看懂...好像需要把公式变形出来,先略过. http://dengbaoleng.iteye.com/blog/1504940