深入了解 Java 之垃圾回收

1,907 阅读6分钟
原文链接: itfeifei.win

讲解之前先插一句,对于Java的一些变量的存储简单了解一下:

1、本地变量(局部变量):在方法体, 构造体内部定义的变量,在方法结束的时候就被摧毁(虚拟机栈的局部变量表)

2、静态变量(类变量、全局变量;+ final 就是全局常量):在类里但在方法外, 加了 static 关键字. (静态存储区/全局区、方法区)

3、实例变量:在类里但是不在方法里 在类被载入的时候被实例化(堆,和对象创新时一起分配内存)

4、常量:方法区的常量池、常量存储区

一、Java垃圾回收,它到底回收的是什么?

上篇文章介绍了Java内存运行时数据区的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,所以Java的垃圾回收机制主要是在堆和方法区。

二、有哪些算法去标志这个对象是可以回收的呢?

1. 引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。应用:python、object-c

缺点:它很难解决对象之间相互循环引用的问题

2. 可达性分析算法

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如图对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。应用:Java

什么对象可以作为“GC Roots”呢?

1、虚拟机栈(栈帧中的本地变量表)中引用的对象。

2、方法区中类静态属性引用的对象。

3、方法区中常量引用的对象。

4、本地方法栈中JNI(即一般说的Native方法)引用的对象。

三、有哪些算法去进行垃圾的回收呢?

上面是标志着这个对象是垃圾,接下去就是对这个垃圾进行回收。

1. 标记-清除算法

标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。 缺点:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

2. 复制算法

它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。适用于存活对象少,回收对象多的情况下

缺点:内存缩小为了原来的一半

3. 标记-整理算法

该算法标记阶段和复制算法一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。适用于存活对象多,回收对象少的情况下

4. 分代收集算法

Java的分代:老年代和新生代
新生代又分为:一块较大的Eden空间和两块较小的Survivor空间,比例为8:1:1

为什么新生代要划分为这样的一个空间呢?

新生代采用的是复制算法,新的对象产生时就会先存放在Eden空间里,当遇到第一次GC时,会把Eden里面存活的对象复制到Survivor A,然后清除Eden和Survivor B的对象;第二次GC时,会把Eden和Survivor A里面存活的对象复制到Survivor B中;最后经过几次的GC之后就会把存活的对象转移到老年代。就这样需要三块区域进行复制转移。至于比例为什么是8:1:1,因为Eden区域比较频繁的产生对象而Survivor存放的对象较少。

什么对象会被转移到老年代呢?

1、经过多次GC之后存活的对象

2、在Eden存不下的大的对象

3、存活下来了,但是Survivor存不下的大对象

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—整理”算法来进行回收。

四、小结

垃圾回收可以有效的防止内存泄露,更好的使用内存。
好啦,分析完毕,谢谢大家耐心看完,有什么不对的地方,欢迎指出,谢谢!