Overview

  • 垃圾收集考虑三件事:
    1. 哪些内存需要回收?
    2. 什么时候回收?
    3. 如何回收?
  • 重点考虑Java堆中动态分配和回收的内存。

Is Object alive?

引用计数法

  • 给对象添加一个引用计数器。
  • 该方法实现简单,判定效率高。但是它很难解决对象之间相互循环引用的问题,因此几乎很少有JVM选用该方法。eg:
    public class ReferenceCountingGC {
    public Object instance = null;
    // 占点内存,以便在GC日志中看清楚是否被回收过
    private static final int _1MB = 1024 * 1024;
    private byte[] bigSize = new byte[2 * _1MB]; public static void testGC() {
    ReferenceCountingGC objA = new ReferenceCountingGC();
    ReferenceCountingGC objB = new ReferenceCountingGC();
    objA.instance = objB;
    objB.instance = objA;
    objA = null;
    objB = null; System.gc();
    }
    }

可达性分析算法

  • 在主流商用程序语言(Java, C#, Lisp)的主流实现中,都是通过可达性分析(Reachability Analysis)来判断对象是否存活的。
  • 该算法的基本思路是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索。搜索的路径称为引用链(Reference Chain)。
  • 当一个对象到GC Roots没有任何引用链相连(即不可达)时,则该对象是不可用的。不可达的对象是可回收的。如下:
  • 在Java中,可作为GC Roots的对象包括下面几种:
    • 虚拟机栈(栈帧中的本地变量表)中的引用;
    • 方法区中类静态属性引用的对象;
    • 方法区中常量引用的对象;
    • 本地方法栈中JNI引用的对象。

再谈引用

  • 上面两种方法都是通过“引用”来判定对象是否alive。
  • 但是在JDK1.2之前,Java中的引用定义很传统:若reference类型的数据中存储的数值是另一块内存的起始地址,则称这块内存代表着一个引用。
  • 但是考虑这样的场景:我们希望描述这样的一类对象,当内存空间足够时,则能保留在内存中,否则可抛弃。 <-- 很多系统的缓存功能都符合这样的应用场景。
  • 于是在JDK1.2之后,Java扩充了引用的概念:将引用分为强引用(Strong Reference), 软引用(Soft Reference), 弱引用(Weak Reference), 虚引用(Phantom Reference) 4种,强度依次减弱。
    • 强引用:最常见的,类似Object obj = new Object()
    • 软引用:用以描述一些还有用但是非必须的对象。对于该类对象,系统在将要发生内存溢出异常之前,会回收这些对象。JDK1.2之后提供了SoftReference类。
    • 弱引用:也是用以描述非必须对象,但强度更弱于软引用。被弱引用关联的对象只能生存到下一次GC之前,而无论当前内存是否足够。
    • 虚引用:PhantomReference类。

生存还是死亡

  • 即使是在可达性分析算法中不可达的对象,也并非“非死不可”的。
  • 要真正宣告一个对象死亡,至少要经历两次标记过程:TBD...

回收方法区

  • 一般来说,在方法区进行垃圾收集的"性价比"比较低:在堆中,尤其是新生代中,常规应用一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。
  • 永久代的垃圾收集主要回收两部分:废弃常量和无用的类。
  • 回收废弃常量与回收Java堆中的对象非常相似。假设一个字符串"abc"已经进入常量池,但当前系统中没有任何一个String对象引用常量池中的"abc"常量,则"abc"常量就会被系统清理出常量池。
  • 相比之下判定一个类是否是“无用的类”的条件则相对苛刻很多。需同时满足:
    • 该类所有的实例已经被回收;
    • 加载该类的ClassLoader已经被回收;
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

垃圾收集算法

标记-清除算法

  • 算法分为“标记”和“清除”两个阶段。
  • 该算法是后续收集算法的基础。
  • 不足:
    • 效率问题:标记和清除两个过程的效率都不高;
    • 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次GC。

  

复制算法

  • 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
  • 当使用的块用完时,就将还存活着的对象复制到另一块上面,然后再将已使用过的内存空间一次清理掉。
  • 这样使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片等情况,只用移动堆顶指针,顺序分配即可。
  • 代价是内存缩小为原来的一半。
  • 现在的商业JVM都采用这种收集算法。IBM的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例划分内存空间。而是将内存分为一块较大的Eden空间和两块较小的Survivor空间。

标记-整理算法

  • 复制收集算法在对象存活率较高时就需要较多的复制操作,效率将会变低。更关键的是,若不想浪费一般的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以老年代一般不能采用这种算法。
  • 标记-整理算法:标记过程仍同于标记-清除算法。但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端外边的内存。

分代收集算法

  • 当前商业JVM的GC都采用“分代收集”(Generational Collection)算法。
  • 这种算法就是根据对象存货周期地不同将内存划分为几块。
  • 一般是把Java堆分为新声代和老生代,从而根据各个年代的特点采用最适当的收集算法。

Summary

  • 本篇主要覆盖了GC的两大问题:

    • 哪些对象需要回收:最基础的是引用计数法,简单但无法解决循环引用问题。在此基础上,更常用的是可达性分析算法。
    • 如何回收:最基础的是标记-清除法,即先标记出哪些对象需要回收,然后逐一清除,不足是会产生大量内存碎片,从而导致后续可能触发多次GC。此外更高效的是复制法,该方法确保了连续内存,但是将内存缩小至原内存一般,并且在对象存活率较高时会引入大量复制操作,效率降低。还有标记-整理算法。 另外,商业JVM都会采用分代收集算法,即将内存划分为几块,根据年代选取合适的收集算法。

[深入理解Java虚拟机]<垃圾收集器与内存分配策略>的更多相关文章

  1. 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具

    上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...

  2. 《深入理解 java虚拟机》学习笔记

    java内存区域详解 以下内容参考自<深入理解 java虚拟机 JVM高级特性与最佳实践>,其中图片大多取自网络与本书,以供学习和参考.

  3. (1) 深入理解Java虚拟机到底是什么?

    好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机   作为一个Java程序员,我们每天都在写Java ...

  4. 深入理解java虚拟机(7)---线程安全 & 锁优化

    关于线程安全的话题,足可以使用一本书来讲解这些东西.<Java Concurrency in Practice> 就是讲解这些的,在这里 主要还是分析JVM中关于线程安全这块的内容. 1. ...

  5. 深入理解java虚拟机(6)---内存模型与线程 & Volatile

    其实关于线程的使用,之前已经写过博客讲解过这部分的内容: http://www.cnblogs.com/deman/category/621531.html JVM里面关于多线程的部分,主要是多线程是 ...

  6. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  7. 深入理解java虚拟机(4)---类加载机制

    类加载的过程包括: 加载class到内存,数据校验,转换和解析,初始化,使用using和卸载unloading过程. 除了解析阶段,其他过程的顺序是固定的.解析可以放在初始化之后,目的就是为了支持动态 ...

  8. 深入理解java虚拟机(1)------内存区域与内存溢出

    在C++领域,关于C++的内存存储,结构等等,有一本书:深度探索C++对象模型,讲解的非常透彻. 而Java确把这一工作交给了虚拟机来处理. 我们首先来看看关于内存的问题. 1.问题: 1)java ...

  9. 什么是HotSpot VM & 深入理解Java虚拟机

    参考 http://book.2cto.com/201306/25434.html 另外,这篇文章也是从一个系列中得出的: <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> ...

  10. 【Todo】深入理解Java虚拟机 读书笔记

    有一个在线系列地址 <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> http://book.2cto.com/201306/25426.html 已经下载了这本书(60多M ...

随机推荐

  1. php绝对路径转相对路径

    /** * 绝对路径转成相对 路径 * $path相对于$base的相对路径 * @param string $base * @param string $path * 思路:去除共同部分 */ fu ...

  2. CentOS6.8逻辑卷管理实战

    CentOS6.8逻辑卷管理实战 要求:利用现有的四块磁盘,创建一个有两个PV组成的大小为80G的名为testvg的VG:要求PE大小为16MB, 而后在卷组中创建大小为5G的逻辑卷testlv:挂载 ...

  3. Vasya And The Mushrooms CodeForces - 1016C (前缀和模拟)

    大意: 给定2*n的矩阵, 每个格子有权值, 走到一个格子的贡献为之前走的步数*权值, 每个格子只能走一次, 求走完所有格子最大贡献. 沙茶模拟打了一个小时总算打出来了 #include <io ...

  4. kohana task 编写计划任务

    kohana 框架  我们经常使用gleez作为我们二次开发. 收先我们要把文件建在Task文件夹下,比如新建文件为:testcron <?phpdefined('SYSPATH') or di ...

  5. java的泛型与反射机制

    什么是泛型? 泛型,即“参数化类型”.顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参) ...

  6. using强制对象清理资源 【转】

    转 http://www.cnblogs.com/Legolas/p/detail-of-using.html using肯定所有人都用过,最简单的就是使用using引入命名空间,然后就是引入别名,简 ...

  7. 转-如何使用iTunes制作iPhone铃声

    新版iTunes(iTunes11)推出以后,界面上发生了一些改变,给人带来一种面貌一新的感觉,但也给许多朋友带来一些操作上的不太适应.下面就大家比较关心的iPhone的铃声制作方法,我在iTunes ...

  8. MongoDB 教程(三):MongoDB 的下载、安装和配置

    一.下载 下载地址:https://www.mongodb.com/download-center#community(这里是Windows 版,其他版本也可以在该网页进行下载) 版本选择: Mong ...

  9. windows的网上邻居

    要说以前中学最怕上什么课就是电脑课,要说电脑印象最深的是什么软件就是总摆在桌面又听不懂又点进去什么都没有的“网上邻居”. “网上邻居”指的是同一局域网内在线的的电脑,更简单些就是连在同一路由器中开机的 ...

  10. Linux查看某个命令属于哪个包

    有时修我们需要某个命令但其没有安装,提供该命令的包名也与命令名相差很大直接查找命令名找不到包,如rexec. 此时我们就非常需要这样一个工具:可以根据最终的命令查找提供该命令的软件包. 类型 命令 说 ...