JVM学习笔记二:垃圾收集算法
垃圾回收要解决的问题:
- 哪些内存需要回收?
线程私有区域不需要回收,如PC、Stack、Native Stack;Java 堆和方法区需要
- 什么时候回收?
以后的文章解答
- 如何回收?
首先进行对象存活性的分析,然后利用GC回收算法进行回收,具体算法请看下文。
如何判断对象是否可以回收?
有两种方式:引用计数算法和可达性分析算法,目前主流商业JVM普遍采用可达性分析算法
引用计数算法
引用计数算法顾名思义,为对象的引用计数,每当有一地方引用它时,计数器加1,引用失效(离开作用域时)减1,当计数器值为0时,对象就可以被回收了。引用计数算法优点是判定效率高,缺点是无法解决对象互相引用的问题,使用此技术进行内存管理的技术有微软的COM、Flash、Python等
可达性分析算法
可达性分析算法,基本思路是以GC Roots为根、向下搜索,所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链可达时,就成为此对象不可用,即可以被回收。注意再注意:该算法的本质是通过找出所有活对象来把其余空间认定为“无用”,而不是找出所有死掉的对象并回收它们占用的空间。分代式GC是一种部分收集(partial collection)的做法。在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC roots的一部分。具体到分两代的分代式GC来说,如果第0代叫做young gen,第1代叫做old gen,那么如果有minor GC / young GC只收集young gen里的垃圾,则young gen属于“收集部分”,而old gen属于“非收集部分”,那么从old gen指向young gen的引用就必须作为minor GC / young GC的GC roots的一部分。

可以作为GC Roots的对象包括:
- 虚拟机栈 VM Stack 中局部变量表(Local Variables)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有当前被加载的Java类
- Java类的运行时常量池里的引用类型常量(String或Class类型)
- String常量池(StringTable)里的引用
- VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。
- 本地方法栈中JNI(Native方法)引用的对象
对象引用类型的不同对存活周期的影响:
JDK1.2之后,引用概念得到了扩展,分为强引用Strong、软引用Soft、弱引用Weak、虚引用Phantom
- 强引用:一般未指定的,都是强引用,包括Object o = new Object(),只要满足存活算法,就不会被回收
- 软引用:表示有用但非必须的对象,这些对象在系统将要发生溢出异常之前,进行一次回收,如果回收到还没有足够的空间,再抛出异常。
- 弱引用:其关联的对象只能生存到下一次垃圾回收之前。
- 虚引用:不会影响对象的生存时间,也就是说生存时间与强引用一样,设置虚引用的目的是得到垃圾回收的通知
生存还是死亡?
存活算法之后,不可达的对象被标记,并进行一次筛选,筛选的条件是“是否有必要执行finalize”,当对象没有覆盖此方法或此方法已经被调用过,都将视为没有必要执行,如果有必要执行,该对象将被放在一个称为F--Queue的队列中,并被一个虚拟机建立的线程去执行finalize方法,如果在方法中该对象成功建立了与GC Roots的引用链,他就可以从待回收列表中移除,并继续存活。
回收方法区
方法区采用永久代实现,相比堆,尤其是新生代,方法区回收的性价比极低,永久代的垃圾回收主要回收废弃常量和无用的类,参数-verbose:class可以查看类加载和卸载情况
垃圾回收算法
标记-清除算法Mark Sweep
先标记,再删除,缺点是空间利用率低,清除之后会产生大量不连续的内存碎片,浪费内存空间。
复制算法
将内存分为使用和待用两部分,当使用部分快满时,将还存活的对象复制到另外待用部分,然后堆整个使用区域进行回收,优点是回收不会出现内存碎片、缺点是浪费了待用区域的内存空间。商业虚拟机以这种算法为基础进行了优化,以HotSpot为例,将内存分为较大的Eden空间和两个较小的Survivor空间,比例默认是8:1:1,可以通过参数调节,每次使用Eden和一块Survivor空间,回收时,将这两个空间存活的对象复制到剩余的一块Survivor空间中,并清理掉Eden和第一块Survivor空间,也就是说可用空间达到了90%,只浪费了10%,这里有个问题,如果存活对象的超过Survivor空间怎么办,这时要依赖一种被称为分配担保的机制(Handle Promotion),将多出的对象分配到老年代。
标记整理算法
适用于老年代这种存活率高的空间,相比标记清除算法,标记阶段一致,只不过在标记后,会对可回收对象进行整理,让存活对象向内存一个方向聚集,然后直接清理掉编辑额外的内存。
分代收集算法
根据存活周期的不同将内存划分为几块,一般把java堆分为新生代和老年代,然后根据不同年代的特点选择最适合的算法,比如新生代中的对象朝生夕灭,只有少量存活,比较适合复制算法,而老年代中对象存活率高,没有额外空间提供分配担保,必须使用标记-清除或标记-整理算法进行回收。
HotSpot算法实现
枚举根节点
程序的执行是动态的,与之相关的引用关系也是动态的,所以GC roots的枚举和分析必须在一个能保证一致性的快照下进行,JVM是依赖被称为GC停顿的机制实现,在这个时间点会停顿所有的Java执行线程,即俗称的”Stop The World“,GC Roots的枚举过程并不是在内存中便利所有对象和执行上下文,那样效率无疑会很低,其实在Class加载和解析的工作中,HotSpot已经使用一组称为OopMap的结构对GC Roots进行标记,这样在GC过程中即可直接获得Roots信息。
安全点 SafePoint
那么GC停顿是如何让线程暂停的呢?有一个关键的概念叫安全点,顾名思义是指一些特定的位置,当线程运行到这些位置时,线程的一些状态可以被确定(the thread's representation of it's Java machine state is well described),比如记录OopMap的状态,从而确定GC Root的信息,使JVM可以安全的进行一些操作,比如开始GC。
另一个问题是GC如何让线程在安全点上停顿下来,有两种方式可选,一种是抢先式中断,GC主动中断所有线程,发现有线程中断的地方不在安全点上,就恢复线程,让它继续跑到安全点上,一种是主动式中断,GC不主动中断线程,而是设置一个标志,线程执行时主动去轮询这个标志,发现标志为真就自动挂起,轮询标志和安全点是重合的。
safepoint指的特定位置主要有:
- 循环的末尾 (防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint)
- 方法返回前
- 调用方法的call之后
- 抛出异常的位置
安全区域 SafeRegion
安全点没有解决一个问题,就是当线程不执行的时候(如进入Sleep或Blocked状态),这时候线程无法响应JVM的中断请求,这就需要安全区域这种机制来解决。安全区域是指一段代码范围,这个范围内任何位置引用关系都不会发生变化,也就是说在这些位置进行GC是安全的,你可以把安全区域看成被扩展了的安全点。
线程在进入安全区域后会标识自己已经进入了安全区域,这样GC就会忽略这个线程,同样离开安全区域时,线程要检查GC是否完成了根节点枚举,如果完成,线程继续进行,否则继续等待。
参考资料
本文参考:《深入理解Java虚拟机》
JVM学习笔记二:垃圾收集算法的更多相关文章
- java之jvm学习笔记二(类装载器的体系结构)
java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...
- JVM学习笔记(二):垃圾收集
程序计数器. 虚拟机栈. 本地方法栈3个区域随线程而生,随线程而灭:栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作. 每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这 ...
- 【JVM学习笔记二】垃圾收集器与内存分配策略
1. 概述 1) GC的历史比Java久远 2) GC需要完成的三件事: | 哪些内存需要回收 | 什么时候回收 | 如何回收 3) Java内存运行时区域各个部分: | Java虚拟机栈.计数器.本 ...
- java jvm学习笔记二(类装载器的体系结构)
欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 在了解java虚拟机的类装载器之前,有一个概念我们是必须先知道的,就是java的沙箱,什 ...
- jvm学习笔记二(减少GC开销的建议)
一:触发主GC(Garbage Collector)的条件 JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大.更值得关注的是主GC的触发条件,因为它对系统影响很明显.总 ...
- JVM学习笔记——GC垃圾收集器
GC 垃圾收集器 Java 堆内存采用分代回收算法,因此 JVM 针对新生代和老年代提供了多种垃圾收集器. 1. Serial 收集器 Serial 收集器是单线程收集器,采用复制算法. 是最基本的垃 ...
- JVM学习笔记二:JVM参数
所有线程共享的内存主要有两块:堆内存和方法区. 其中堆内存分为两块:新生代Young generation(Eden区.From Survivor区.To Survivor区).老年代Tenured ...
- JVM 学习笔记二 :JVM内存区域
一.内存分配概述
- 【Java虚拟机】JVM学习笔记之GC
JVM学习笔记二之GC GC即垃圾回收,在C++中垃圾回收由程序员自己来做,例如可以用free和delete来回收对象.而在Java中,JVM替程序员来执行垃圾回收的工作,下面看看GC的详细原理和执行 ...
随机推荐
- redisLock redis分布式锁
redis-lock redis setnx cmmand java object condition queue 条件队列 retrycount 带有重试次数限制 object wait time ...
- sharepoint rest api 创建文档库 文件夹
function createFolder() { var requestHeaders = { "Accept": "application/json;odata=ve ...
- Advice详解
1.前置增强 BeforeAdvice是前置增强的接口,方法前置增强的MethodBeforeAdvice接口是其子类, MethodBeforeAdvice接口仅仅定义了唯一的方法:before(M ...
- 从一道例题谈Arrays.toString()与其他String的转换方法
阅读该篇文章前,请大家事先阅读一下: java.toString(),(String),String.valueOf的区别 有了上述基础后,我接下来谈谈从一道题目中获得的些许收获. 今天在做题是发 ...
- js将字符串转化成函数:eval(logOutCallbackFun+"()");
js将字符串转化成函数:eval(logOutCallbackFun+"()");
- js判断密码强度是否符合
/** 判断密码强度是否符合 */ function check_passwd_intensity(password) { value = $.trim(password); if( value.le ...
- 【Android Developers Training】 36. 设置文件共享
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- HDFS Java API的使用举例
HDFS是Hadoop应用程序使用的主要分布式存储.HDFS集群主要由管理文件系统元数据的NameNode和存储实际数据的DataNodes组成,HDFS架构图描述了NameNode,DataNode ...
- 青出于蓝而胜于蓝 — Vue.js对Angular.js的那些进步
Angular.js与Vue.js是非常有渊源的两款前端框架,据Vue.js的官方网站描述,在其早期开发时,灵感来源就是Angular.js.而在很多方面,Vue.js也正像是中国的那句古话,&quo ...
- div自身高度、屏幕高度
获取元素高度 scrollWidth //显示当前元素的宽度 scrollHeight //显示当前元素的高度 scrollLeft //显示当前元素的左边距左侧的距离 scroll ...