JVM初探(二):垃圾回收机制
一、概述我们知道自动的垃圾回收机制是Java语言一个特点,它让我们在写程序的时候不再需要考虑内存管理问题。内存管理实际上就是分配内存和回收内存这两个问题,在上一篇文章我大概介绍了jvm是如何划分内存空间以合理的分配内存的,而这篇文章就介绍一下jvm是如何回收内存的。 对于线程私有的程序计数器,虚拟机栈和本地方法栈三块数据区域而言,生命周期是和线程绑定的,线程结束时自然就回收内存了;而对于栈,每一个方法代表每一个栈帧,方法结束的时候就出栈,这时内存也跟着回收了。这些区域的内存回收都是具有确定性的,而堆就不同。 我们知道,堆主要用与存放对象实例,而只有运行时才知道要创建那些对象,而只有对象完全不被使用时才能回收其占用的内存空间。对于这块内容,我们需要明确三个问题:
二、判断对象是否可回收我们要判断对象是否可以回收,最有效的方式就是判断这个对象是否正在被别的对象引用。针对这个问题,有两种算法:引用计数算法和可达性分析算法。 1.引用计数算法引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。 简单的来说,就是为每一个对象实例配置一个计数器:
引用计数器实现简单而且效率高,但是无法解决循环引用问题:
当A实例中引用了B实例,而B实例中又引用了A实例,他们的极速器就永远不能为0,也就无法回收。 2.可达性算法可达性算法通过判断对象的引用链是否可达来判断对象是否可以被回收。 通过一系列的名为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。 在java中,可以作为GC Roots的对象包括:
3.强引用、软引用、弱引用、虚引用我们知道以上两种算法都需要判断对象是否被引用,实际上,如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用,因此对象往往只有两种状态:被引用或者未被引用。 如果我们希望有这样一类对象:当内存空间足够时,能保留在内存中;如果内存空间在进行垃圾回收后还是非常紧张,就抛弃这些对象。比如一些系统缓存。 因此为了做出区分,JDK1.2之后Java的引用被分为强引用、软引用、弱引用、虚引用4种。这4种引用强度依次逐渐减弱。
这块内容具体可以参考:Java 的强引用、弱引用、软引用、虚引用 二、垃圾收集算法要理解垃圾回收时机,我们需要理解分代算法,在这之前我们需要对四种垃圾收集算法有大概的印象: 1.标记清除算法
标记清除算法有两个问题:
2.复制算法
复制算法常用于回收新生代。 如我们之前在介绍堆的内存结构的时候,jvm会将堆分外新生代和老年代。 而将新生代内存又分为一块较大的 3.标记-整理算法
标记-整理算法常用于老年代。 复制算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。 4.分代收集算法
不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率:
三、分代收集算法的内存回收策略正如之前所说,由于java对象实例存储于堆中,所以堆就是GC的主要场所。 根据分代收集算法,堆会分为新生代和老年代。 1.新生代和老年代
新生代内存按照 在进行垃圾回收时:
2.分代收集算法的内存分配策略这里再提一下内存分配策略:
四、垃圾收集器1.Serial收集器新生代收集器,单线程。 2.ParNew收集器Serial收集器的多线程版本. 3. Serial Old收集器Serial收集器用于老年代的多线程版本。 4.Parallel Scavenge收集器新生代收集器,多线程。它的关注点与其他收集器不同,其他的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标则是达到一个高吞吐量。 5.Parallel Old收集器Parallel Scavenge收集器的老年代版本。 6.CMS收集器老年代并行,以获取最短回收停顿时间为特点的收集器。 7.G1收集器G1收集器是JDK7提供的一个新收集器。 G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |