深入理解JVM(二)--垃圾收集算法
一. 概述
说起垃圾收集(Garbage Collection, GC), 大部分人都把这项技术当做Java语言的伴随生产物. 事实上, GC的历史远远比Java久远, 1960年 诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言. 当Lisp还在胚胎时期时,人们就在思考GC需要完成的三件事情:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
现在内存的动态分配与内存回收技术已经相当成熟, 那为什么我们还要去了解GC和内存分配呢? 答案很简单: 当需要排查各种内存溢出, 内存泄漏问题时, 当垃圾收集称为系统达到更高并发量的瓶颈时, 我们就需要对这些"自动化"的技术实施必要的监控和调节.
二. 对象的生与死
堆中几乎存放着Java世界中所有的对象实例, 垃圾收集器在对堆进行回收前, 第一件事情就是要确定这些对象还有哪些还"存活", 哪些已经"死去"(即不可能再被任何途径适用的对象).
- 引用计数算法
概念: 给对象中添加一个引用计数器, 每当有一个地方引用它时, 计数器值+1; 当引用失效时, 计数器值-1; 任何时刻计数器都为0的对象就是不可能再被使用的.
客观地说, 引用计数算法(Reference Counting) 的实现简单, 判定效率也很高, 在大部分情况下它都是一个不错的算法, 也有一些比较著名的应用案例, 例如微软的COM(Component Object Model) 技术, 使用ActionScript 3的FlashPlayer, Python语言以及在游戏脚本领域被广泛引用的Squirrel中都使用了引用计数算法进行内存管理.但是, Java语言没有选用引用计数算法来管理内存, 其中最主要的原因是它很难解决对象之间的相互循环引用的问题.
2. 根搜索算法
概念: 通过一系列的名为"GC Roots" 的对象作为起始点, 从这些节点开始向下搜索, 搜索所走过的路径称为引用链(Reference Chain), 当一个对象到GC Roots 没有任何引用链想连(用图论的话来说就是从GC Roots到这个对象不可达)时, 则证明此对象是不可用的.
在Java和C#, 以及上面提到的古老的Lisp, 都是使用跟搜索算法(GC Roots Tracing) 判断对象是否存活的.
在Java语言里, 可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象.
- 方法区中的类静态属性引用的对象.
- 方法区中的常量引用的对象.
- 本地方法栈中JNI(即一般说的Native方法)的引用的对象.
3. 生存还是死亡
在跟搜索算法中不可达的对象, 也并非是"非死不可"的, 这时候他们暂时处于"缓刑"阶段, 要真正宣告一个对象死亡, 至少要经历两次标记过程: 如果对象在进行根搜索后发现没有与GC Roots相连接的引用链, 那它将会第一次被标记并且进行一次筛选, 筛选的条件是此对象是否有必要进行finalize()方法, 当对象没有覆盖finalize() 方法, 或者finalize()方法已经被虚拟机调用郭, 虚拟机将这两种情况都视为"没有必要执行".
如果这个对象有必要执行finalize()方法, 那么这个对象将会被放置在一个名为F-Queue的队列之中, 并在稍后由一条由虚拟机自动建立的, 低优先级的Finalizer线程去执行. finalize()方法是对象逃脱死亡命运的最后一次机会, 稍后GC将对F-Queue中的对象进行第二次小规模标记, 如果对象要在finalize()中成功拯救自己---只要重新与引用链上的任何一个对象建立关联即可, 譬如把自己赋值给某个类变量或对象的成员变量, 那在第二次标记时它将被移除出"即将回收的集合", 如果对象这时候还没有逃脱, 那它就这的离死不远了.
代码: 一次对象自我拯救的演示
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null; public void isAlive() {
System.out.println("Yes, I'm still alive.");
} @Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
} public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC();
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("No, I'm dead.");
} SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("No, I'm dead.");
} } }
运行结果:
Finalize method executed!
Yes, I'm still alive.
No, I'm dead.
三. 垃圾收集算法
1. 标记-清除算法(Mark-Sweep)
如他的名字一样, 算法分为"标记"和"清除"两个阶段: 首先标记出所有需要回收的对象, 在标记完成后统一会受到所有被标记的对象,它的标记过程在上面讲述对象标记判定时已经基本介绍过了. 它是最基础的收集算法, 是因为后续的书记算法都是基于这种思路对其缺点进行改进而得到的.
它的主要缺点有两个: 一个是效率问题, 标记和清除过程的效率都不高, 另一个是空间问题, 标记清除之后会产生大量不连续的内存碎片, 空间碎片太多可能会导致, 当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作.
2. 复制收集算法(Coping)
为了解决效率问题, 一种称为"复制"的收集算法出现了, 它将可用内存按容量划分为大小相等的两块, 每次只使用其中的一块. 当着一块的内存用完了, 就将还存活着的对象复制到另一块上面, 然后再把已使用过的内存空间一次清理掉, 这样使得每次都是对其中的一块进行内存回收, 内存分配时也不用考虑内存碎片等复杂情况, 只要移动堆顶指针, 按顺序分配内存即可, 实现简单, 运行高效. 只是这种算法的代价是将内存缩小为原来的一半, 未免太高了一点.
现在的商业虚拟机都采用这种收集算法来回收新生代, IBM的专门研究表明, 新生代中的对象98%都是朝生夕死, 所以并不需要按照1:1的比例来话费呢内存空间, 二十将内存分为一块较大的Eden空间和两块较小的Survivor空间, 每次使用Eden和其中的一块Survivor. 当回收时, 将Eden和Survivor中还存活着的对象一次性的拷贝到另一块Survivor空间上, 最后清理掉Eden和刚才用过的Survivor的空间, HotSpot虚拟机默认Eden和Survivor的大小比例是8:1, 也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+ 10%), 只有10%的内存会被"浪费". 当然98%的对象可回收只是一般场景下的数据, 我们没有办法保证每次回收都只有不多余10%的对象存活, 当Survivor空间不够时, 需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion).
3. 标记-整理算法(Mark-Compact)
复制收集算法在对象存活率较高时就要执行较多的复制操作, 效率将会变低, 更关键的是, 如果不想浪费50%的空间, 就需要有额外的空间进行分配担保, 以应对被使用的内存中所有对象都100%存活的极端情况, 所以在老年代一般不能直接选用这种算法.
根据老年代的特点, 于是提出了另一种"标记-整理"(Mark-Compact)算法, 标记过程仍与"标记-清除"算法一样, 但后续步骤不是直接对可回收对象进行清理, 而是让所有存活的对象都向一端移动, 然后直接清理掉边界以外的的内存.
4. 分代收集算法
当前商业虚拟机的垃圾收集都采用"分代收集"(Generation Collection)算法, 这种算法并没有什么新思想, 只是根据对象的存活周期的不同将内存划分为几块. 一般是把Java堆氛围新生代和老年代, 这样就可以根据各个年代的特点采用最适当的收集算法, 在新生代中, 每次垃圾收集时都发现有大批对象死去, 只有少量存货, 那就选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集. 而老年代中因为对象存活率高, 没有额外的空间对它进行分配担保, 就必须使用"标记-清理"或"标记-整理"算法来进行回收.
深入理解JVM(二)--垃圾收集算法的更多相关文章
- 深入理解Java虚拟机 - 垃圾收集算法与垃圾收集器
1. 垃圾收集算法 JVM的垃圾收集算法在不同的JVM实现中有所不同,且在平时工作中一般不会深入到收集算法,因此只对算法做较为简单的介绍. 1.1 标记-清除算法 ...
- 垃圾收集器与内存分配策略 (深入理解JVM二)
1.概述 垃圾收集(Garbage Collection,GC). 当需要排查各种内存溢出.内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调 ...
- 理解JVM之垃圾收集器概述
前言 很多人将垃圾收集(Garbage Collection)视为Java的伴生产物,实际1960年诞生的Lisp是第一门真正使用内存动态分配与垃圾手机技术的语言.在目前看来,内存的动态分配与内存回收 ...
- JVM中垃圾收集算法总结
通过前面的介绍我们了解了对象创建和销毁的过程.那么JVM中垃圾收集器具体对对象回收采用的是什么算法呢?本文主要记录下JVM中垃圾收集的几种算法. JVM的垃圾回收的算法 标记-清除算法(Mark- ...
- JVM笔记-垃圾收集算法与垃圾收集器
1. 一些概念 1.1 垃圾&垃圾收集 垃圾:在 JVM 语境下,"垃圾"指的是死亡的对象所占据的堆空间. 垃圾收集:所谓"垃圾收集",就是将已分配出去 ...
- 深入理解JVM(5)——垃圾收集和内存分配策略
1.垃圾收集对象 垃圾收集主要是针对堆和方法区进行. 程序计数器.虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收. 哪 ...
- 深入理解JVM(三)——垃圾收集策略具体解释
Java虚拟机的内存模型分为五个部分.各自是:程序计数器.Java虚拟机栈.本地方法栈.堆.方法区. 这五个区域既然是存储空间,那么为了避免Java虚拟机在执行期间内存存满的情况,就必须得有一个垃圾收 ...
- 深入浅出JVM之垃圾收集算法
判断哪些对象需要被回收 引用计数算法: 给对象中添加一个引用计数器,每当有一个地方引用时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是不可能再被使用的. 但是JVM没有使 ...
- 深入理解Java虚拟机-垃圾收集算法
一.判断对象是否可进行回收 1.引用计数算法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器为0的对象就是不可能再被使用的.但是主流的 ...
随机推荐
- Python在Windows下列出所有的安装包和模块
1.查看python安装的module python -m pydoc module 或 >>>help('module') 2.用pip查看 pip list
- ArcEngine 创建要素,删除要素,生成网格,渲染图层(VB)
示例代码:https://github.com/yu969890202/ArcEngine/tree/master/WinFrom_ArcEngine_PointDistribution博客后面有两张 ...
- 值得收藏!my.cnf配置文档详解
MySql对于开发人员来说应该都比较熟悉,不管是小白还是老码农应该都能熟练使用.但是要说到的各种参数的配置,我敢说大部分人并不是很熟悉,当我们需要优化mysql,改变某项参数的时候.还是要到处在网上查 ...
- Java类成员之方法
方法含义: 1. 方法是类或对象行为特征的抽象,用来完成某个功能操作. 2.在某些语言中也称为函数或过程. 3.将功能封装为方法的目的是简化代码,可以实现代码重用. 4.在Java里的方法不能独立存在 ...
- GoCenter助力Golang全速前进
一.背景 Go语言是Google开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言.为了方便搜索和识别,有时会将其称为Golang.自2009年11月Google正式宣布推出,成为开放 ...
- Scala实践7
一.类 1.1简单类和无参方法 类的定义通过class关键字实现 scala> class Dog { | private var leg = 4 | def shout(content: St ...
- 从数组中取出n个不同的数组成子集 y 使 x = Σy
/** * 尝试获取arr子集 y 使 x=Σy * @param {Array} arr * @param {number} x * @param {Array} res */ f ...
- 数字金字塔 动态规划(优化版) USACO 一维dp压缩版
1016: 1.5.1 Number Triangles 数字金字塔 时间限制: 1 Sec 内存限制: 128 MB提交: 9 解决: 8[提交] [状态] [讨论版] [命题人:外部导入] 题 ...
- 如何添加.pch文件
1.Create a pch , call name is project+xxx.pch For example: DuoME-PrefixHeader.pch 2.在project——>Bu ...
- RocketMQ 实战之快速入门
原文地址:https://www.jianshu.com/p/824066d70da8 最近 RocketMQ 刚刚上生产环境,闲暇之时在这里做一些分享,主要目的是让初学者能快速上手RocketMQ. ...