深入JAVA虚拟机之垃圾收集

前言:

说起垃圾收集器,JAVA开发者肯定是听得耳朵都起茧子了。如果让你设计一个JAVA垃圾收集器,那么你关注那些点呢?

// 1.哪些内存需要回收?
// 2.什么时候回收?
// 3.如何回收?

这篇博文就是记录这些问题答案的。闲言碎语不多讲,开始写。


那些内存需要回收?

我们先来回顾一下“运行时数据区”的知识点。我们都知道程序计数器虚拟机栈本地方法栈都是与线程同生共死的。栈中的栈帧分配多少内存在类结构确定下来时就已经确定了。所以它们的内存分配和回收都是非常确定性的,因为方法结束或者线程结束内存自然就回收了。
但是Java堆方法区就不一样了,一个接口中的多个实现类需要的内存可能不一样,一个方法的多个分支需要的内存也可能不一样,只有在程序执行期间才能知道会创建哪些对象。这部分内存的分配和回收都是动态的,垃圾收集器关注的就是这部分内存。

什么时候回收?

java堆中几乎存着所有的java世界中的对象实例,垃圾收集器对堆进行回收前,首先要做的事情就是:确定不可能再被任何途径使用的对象。这件事当然是交给算法去做,引用计数算法根搜索算法

  1. 引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1,当引用失效时,计数器值减1.如果任何时候计数器都为0的对象就是不可能再被使用的。书中说实际上java虚拟机没有使用这种算法去管理内存,因为它很难解决对象间相互循环引用的问题。比如说:A对象,B对象,他们相互引用,那么他们的引用计数器就不会为0,那么GC就永远没法回收它们,实际上他们已经可以被回收了。这个案例也可以java虚拟机到底是不是使用的引用计数器算法。

2.根搜索算法
算法基本思路是:通过一系列的名为“GC Roots”的对象为起点,从这些节点开始向下搜素,搜素所有走过的路径称为引用链。当一个对象到GC Roots没有任何链路时,则说明该对象是不可用的。

3.引用
如果reference类型的数据中存放的数值是代表另一块内存的起始地址,我们就说这块内存代表着一个引用。引用分类如下:
强引用:(如Object obj = new Object())、永远不会被垃圾收集器掉引用的对象。

软引用:描述还有用,但非必须的对象。在系统面临内存溢出风险前,将把这些加入到回收范围进行二次回收。

弱引用:描述非必需的对象,只能生存到下一次垃圾收集发生之前。

虚引用:幽灵引用,不会对生存时间构成影响,也无法通过虚引用获取对象实例。唯一目的就是在回收时,能够收到一个系统通知。

如何回收

垃圾收集算法
上面讲了通过算法判断是否将对象列入回收名单中,那么接下来就是讲解垃圾收集算法。介绍几种算法的思路和发展过程,具体实现不在此详细说明。
标记-清除算法
分两个阶段:标记、清除。将所需要回收的对象进行标记,在标记完成后,统一回收掉所有被标记的对象。所谓标记也就是前面说说的内容,如何判断一个对象已经“死了”。这个算法效率不高,因为两个阶段效率都不高,而且在回收完成后,会产生大量不连续的内存碎片,当需要给大对象进行内存分配时不得不触发另一次垃圾回收动作。

复制算法
为了解决效率问题,一种称为复制的收集算法出现了,它将可用内存按照容量划分为两个大小相等的区域,每次只使用其中一块,当一块用完以后,就将还存活的对象复制到另外一块上,然后一次性清理掉当前满了的区块。

标记-整理算法
复制算法当对象的存活率很高时,需要大量的复制对象,从而降低效率,而且该种方式降低了可用内存大小。标记-清理算法和标记-整理算法前面阶段是相同的,在标记完成以后,不做清理,而是将存活对象,往一边移动,使得会搜狐的内存能够连续在一起。

备注:
没有完美的算法,只有适用场景的算法。上述的算法都有各自的优缺点,只用一种算法都无法很好的覆盖虚拟机中内存回收的场景。所以出现了分代回收算法。其实这个也没有什么新意,就是把对象根据存活周期划分不同的内存区域,从而针对不同的内存区域进行不同算法的垃圾收集。

分代回收算法
一般java堆中分为新生代和老年代。根据各个分代的特点采用合适的算法进行回收。新生代中对象的创建率很高,死亡率也很高,所以适合采用复制算法,只要付出少量对象的复制成本就可以完成回收。而老年代中对象创建率低,而且存活率极高,所以使用标记-整理算法是在合适不过了。

备注:
java堆划分内存区域进行内存回收的方式可以更加细致一些,Eden Space、Survivor Space、Tenured Gen;
下面这里是一种比较形象容易记住的方式进行说明,来自分代回收通俗解释
1、一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。
2、 大对象直接近入老年代-养老区:有些世界土豪出生。 他父母直接砸了几百亿, 身份显赫, 进入老年代,有钱就是嚣张!不用去Eden Space(伊甸园)。
3、Survivor Space(幸存者区有两个区域:生活区和无人区)为什么有两个区 from Survivor(生活区) ,to Survivor(无人区) .每次GC想要去幸存者区敲诈 ,会去from Survivor(生活区的)所有人带到to Survivor(无人区) ,然后开始敲诈, 被敲诈包括本次15次 的土豪,进入养老区,交不起保护费 的杀死, 没满足15 次,但是手里还有点钱的就生活在无人区,这个无人区就变成了生活区, 以前的生活区(人都被移走了) 又变成了无人区
4、并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。
GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。
(每经过一次Minor GC,会给这个富翁添加一次记录,当某些富翁连续给了大概15 年保护费,就可以去养老区了)万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。
5、进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。

扩展:垃圾收集器

算法是内存回收的方法论,而垃圾收集器就是内存回收的具体实现了,JAVA虚拟机规范中没有任何关于垃圾收集器该如何实现的规定,因此不同厂商,不同版本实现的垃圾收集器有很大差别。下图是1.6HotSpot JVM的垃圾收集器。

备注:
如果两个垃圾收集器之间有联系说明,它们可以配合使用。各个垃圾收集器的实现内容自行查阅资料,我就不再这里码字。下面是关于垃圾收集相关的一些参数,对于调优具有很好的作用。

原文地址:http://blog.51cto.com/4837471/2161943

时间: 2024-10-26 11:33:18

深入JAVA虚拟机之垃圾收集的相关文章

深入理解Java虚拟机笔记---垃圾收集算法

当对象判定为"已死"状态,虚拟就要采取一定的手段将这些对象从内存中移除,即回收垃圾,回收过程有采用一定的算法.如下是一些主要的垃圾收集算法: 1.标记-清除算法 该算法是最基础的算法,分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象.之所有说它是最基础的算法是因为后续的收集算法都是基于这种思路并对其缺点进行改进得到的.它的缺点主要有两个:一个是效率问题,标记和清除过程效率都不高:另外一个是空间问

深入理解Java虚拟机笔记---垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.Java虚拟机规范中对象垃圾收集器应该如何实现并没有任何规定,因此不同的厂商,不同版本的虚拟机所提供的收集器可能会有很的差别,并且一般会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器.下面是Sun HotSpot虚拟机1.6版本Update22包含的所有收集器: 上图中,如果两个收集器之间存在连线,就说明它们可以搭配使用. 1.Serial收集器 Serial收集器是最基本.历史最悠久的收集器,曾经(在J

Java虚拟机(五)——垃圾收集机制

垃圾回收介绍 ?? Java虚拟机内存划分讲到了Java 内存运行时区域的各个部分,其中程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出有条不紊地执行着出栈和入栈操作.每一个栈帧中分配多少内存基本上是在类结构确定下来是就已知了.因此这几个区域的内存分配和回收都具有确定性,在这几个区域就需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了.而Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个

Java虚拟机之垃圾收集器(7)

一.关于Java垃圾回收的简介 (1)Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程而生,随线程而灭:栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作. (2)每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由 JIT 编译器进行一些优化),因此这几个区域的内存分配和回收都具备确定性.在这几个区域内不需要过多考虑回收的问题,因为方法结束或线程结束时,内存自然就跟随着回收了. (3)而 Java 堆和方法区则不一样,一

深入理解Java虚拟机之垃圾收集一

"生存还是死亡" 如何来判定对象是否存活?针对这个问题书中给出了两种算法,分别是引用计数算法和可达性分析算法 引用计数算法 该算法的思路简单并且易于实现.我们给对象中添加一个引用计数器,当有一个地方引用它时,引用计数器就加一,当引用失效时,计数器减一,当计数器为0时就说明该对象不可能再被引用. 客观的评价,该算法判定效率很高,在很多情况下都是一种不错的算法,但是,至少主流的Java虚拟机并没有采用采用这种算法.原因是该算法无法解决对象之间的循环引用问题. 什么是循环引用呢?笔者认为就是

java虚拟机之垃圾收集器与内存分配策略

哪些内存需要回收? java内存运行时区域的各个部分,其中程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不絮的执行着出栈和入栈操作.每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的.因此这几个区域的内存分配和回收都具有确定性,所以这部分不需要过多考虑内存回收.但是方法区和堆不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也不一样,我们只有在程序处于运行期间才能知道会创建哪些对象,这部分的分配和回收都

《深入理解Java虚拟机》——垃圾收集器与内存分配策略

GC需要完成: 哪些内存需要回收 什么时候回收 如何回收 如何确定对象不再使用 引用计数算法 给对象添加一个引用计数器,当有一个地方引用它时,计数器值进行加1操作:当引用失效时,计数器值进行减1操作:当计数器值为0,则说明对象不可能再被使用.但是它无法解决循环引用的问题. public class ReferenceCountingGC { public Object instance = null; public static void testGC(){ ReferenceCountingG

深入理解Java虚拟机之垃圾收集二

垃圾收集器 图示为JDK1.7 Update14之后的HotSpot虚拟机所使用的所有垃圾收集器. 如果两个收集器存在连线,那么说明这两个收集器可以搭配使用.收集器所处的区域说明它是新生代收集器还是老年代收集器. Serial收集器 主要看图↑ 上图演示了Serial/Serial Old收集器运行示意图. Serial收集器是一个单线程收集器,该收集器在进行垃圾收集的同时,必须暂停(Stop the World!)其它工作线程(这就好比你妈妈在打扫房间的同时,肯定也会让你乖乖待在一边,如果她一

深入理解java虚拟机(3)垃圾收集器与内存分配策略

一.根搜索算法: (1)定义:通过一系列名为"GC Roots"的对象作为起点,从这些起点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象不可用 (2)GC Roots对象包括这几种:虚拟机栈中引用的对象:方法区中的类静态属性引用的对象:方法区中常量的引用对象:本地方法中JNI的引用对象 二.垃圾收集算法 1.标记-清楚算法: (1)基本思想:首先标记出要回收的对象,在标记完后回收掉所有被标记的对象 (2)缺点:第一是标记和清楚