[深入理解Java虚拟机]<垃圾收集器与内存分配策略>
Overview
- 垃圾收集考虑三件事:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
- 重点考虑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虚拟机]<垃圾收集器与内存分配策略>的更多相关文章
- 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具
上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...
- 《深入理解 java虚拟机》学习笔记
java内存区域详解 以下内容参考自<深入理解 java虚拟机 JVM高级特性与最佳实践>,其中图片大多取自网络与本书,以供学习和参考.
- (1) 深入理解Java虚拟机到底是什么?
好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机 作为一个Java程序员,我们每天都在写Java ...
- 深入理解java虚拟机(7)---线程安全 & 锁优化
关于线程安全的话题,足可以使用一本书来讲解这些东西.<Java Concurrency in Practice> 就是讲解这些的,在这里 主要还是分析JVM中关于线程安全这块的内容. 1. ...
- 深入理解java虚拟机(6)---内存模型与线程 & Volatile
其实关于线程的使用,之前已经写过博客讲解过这部分的内容: http://www.cnblogs.com/deman/category/621531.html JVM里面关于多线程的部分,主要是多线程是 ...
- 深入理解java虚拟机(5)---字节码执行引擎
字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...
- 深入理解java虚拟机(4)---类加载机制
类加载的过程包括: 加载class到内存,数据校验,转换和解析,初始化,使用using和卸载unloading过程. 除了解析阶段,其他过程的顺序是固定的.解析可以放在初始化之后,目的就是为了支持动态 ...
- 深入理解java虚拟机(1)------内存区域与内存溢出
在C++领域,关于C++的内存存储,结构等等,有一本书:深度探索C++对象模型,讲解的非常透彻. 而Java确把这一工作交给了虚拟机来处理. 我们首先来看看关于内存的问题. 1.问题: 1)java ...
- 什么是HotSpot VM & 深入理解Java虚拟机
参考 http://book.2cto.com/201306/25434.html 另外,这篇文章也是从一个系列中得出的: <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> ...
- 【Todo】深入理解Java虚拟机 读书笔记
有一个在线系列地址 <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> http://book.2cto.com/201306/25426.html 已经下载了这本书(60多M ...
随机推荐
- hdoj5785
题意:略 先用题解的办法,manacher,然后tag,add数组.但是比较难办的是manacher加了新的字符.这样的话cntL和cntR不是实际的值,但是没关系,原本的字符都在奇数位置,这样cnt ...
- 矩阵最优路线DP
母题:矩阵中每个点有权值,每经过一个点就累加权值,求从a点到b点的最优(最大)路线. 题型1: 从左上到右下,只能向下或者向右 for 行 for 列 dp=max dp左,dp上; 扫一遍就行 有时 ...
- Build Castles(构建城堡)
Charlemagne, the King of Frankie, 英文描述 请参考图片中的说明. 中文描述 根据给出的数组确定能够盖多少城堡. 思路和点评 我不能确定我的思路是正确的,也欢迎大家参与 ...
- CSS之透视perspective属性
透视原理: 近大远小 . 浏览器透视:把近大远小的所有图像,透视在屏幕上. 书写方式不同的定义 perspective有两种定义方式,如下 .class{ perspective: 800px; } ...
- sql语句的各种模糊查询语句
一般模糊语句如下: SELECT 字段 FROM 表 WHERE 某字段 Like 条件 其中关于条件,SQL提供了四种匹配模式: 1.%:表示任意0个或多个字符.可匹配任意类型和长度的字符,有些情况 ...
- 2017-6-6&6-8/大型网站架构总结
一.WikiPedia(维基百科) WikiPedia是非盈利网站,因此尽可能地使用免费的软件和廉价的服务器.截止到2012年,这个只有区区数百台服务器和十余个技术人员开发.维护的网站,成为流量全球排 ...
- PostgreSQL导出一张表到MySQL
1. 查看PostgreSQL表结构,数据量,是否有特殊字段值 region_il=# select count(*) from result_basic; count --------- ( row ...
- 关于ORA-06508 , ORA-04068异常的详细说明
参考:程序包调用报ORA-06508: PL/SQL: 无法找到正在调用的程序单元 出现这种情况的原因是因为,对于全局变量,每一个session会生成一个本地copy,如果程序重新编译的话,就会因程序 ...
- Python错误调试-raise、assert
raise: raise语句手工引发一个异常:,这样做程序不会因异常而终止,而是运行报错 1 "raise" [expression ["," expressi ...
- axios全局设置url公共请求头
需求由来:公司项目外链到别公司项目或者网页(通俗的说就是通过别的随意网页跳转至你项目网页),这时公司项目就要区分是从哪个公司或者哪个网页跳转过来的,从而进行不同的接口请求(公司所有接口都要带上请求头) ...