为了解JVM的垃圾回收机制,我们要先了解为什么要进行垃圾回收,带我们编写代码的时候,会创建很多的对象,如果这些对象不及时的清除,那么可能会导致,后续我们在创建对象的时候已经没有空间留给我们进行操作,会导致对象创建失败,导致内存泄漏,如果我们不及时清除不需要的对象短时间内内存泄漏问题可能不会对程序产生什么影响甚至不会被发现,但是时间一长,内存空间泄露发生了,可能机会产生重大问题。
在JVM中有许多内存区域,垃圾回收机制主要在堆中进行垃圾回收,java中垃圾回收的基本单位是对象,而堆中又是储存new 出来的对象,因此堆事垃圾回收机制的主要战场,并且在堆中我们只回收已经完全不被使用的对象,如果对象但凡有一丁点被使用的部分我们就不对其进行回收。
垃圾挥回收分为两步 1)找到垃圾位置 2)进行垃圾回收。
1垃圾的位置
找到垃圾的位置有两种方案 引用计数和可达性分析
引用计数:
引用技术简单的来讲就是使用一个计数器,记录一个对象被引用的次数,如果被引用了数值就加一,如果有对一个对象的引用删除了,那么计数器就减一,如果计数器的数值为零了那么就说明这个对象么有被引用,也就说明这个对象没用了,就可以被当成垃圾进行处理了。
这种方法看似比较好其实也有明显缺陷 1)耗费额外的内存空间 2)计数中容易造成“循环”引用这样的问题。
消耗额外的空间这一点不难理解,计数器也是需要内存空间来计数否则如何知到该对象被引用了多少次。
循环引用,这就比较的抽象了,下面我们观看一组代码来解释这个问题:
class Test{Test test = null; } public class csdn {public static void main(String[] args) {//第一步引用Test a = new Test();Test b = new Test();//对象内部的引用a.test = b;b.test = a;//将对象的指向为nulla = null;b = null;} }
在这个代码过程a的计数器在第一步加一,b的计数器也在第一步加一 在第二部中有余对象a和b都被彼此对象中的test再次引用,因此各自的技术器再加一,此时计数器中的数字都等于2,之后将a和b通知置为空,此时两个对象的引用分别减一,但此时两个计数器中的数都为1,这种情况就比较尴尬了,此时这两个对象不会被回收,因为计数其中的数并没有清零,这就造成了循环引用,导致计数器中的数没办法被清零。
由于这种原因因此JVM中并没有使用这种方式来进行查找发记得位置而是使用了另一种方式。
可达性分析:这种方式是用时间换空间,对时间的消耗比较多,但是对空间的要求比较小。
这种方式查找垃圾所在位置类似于“图的遍历”,从root触发尽可能多的遍历到对象,如果该对象可以通过root到达摸个对象,则说明这个对象还在被引用就不必回收,如果没有遍历到那就说明这个对象已经不再被使用可以被回收了。
这里的root 称为 GC root 可以理解为可达性分析的起点有三个部分可以作为GC root
1)栈上的局部变量(引用类型)。
2)元数据区(方法区)上的静态成员变量(医用类型)。
3)常量池引用指向的对象。
其中 GC root可以有多个在查找垃圾的过程中会逐个的对GC root 进行遍历知到将所有的GC
root 遍历(尽可能往下延伸)结束之后才可以确定那些是需要被回收的。
进行垃圾回收也有几种方式:
1)标记清除:直接针对内存中的对象进行释放。
标记清除这种方式会引起‘内存碎片’问题:我们在释放内存的时候是随机的,虽然我们把不用的对象内存释放了,但是释放掉的空间并没有可空闲空间合并在一起,这就导致后续申请空间的时候可能会申请不到,因为我们申请的空间是必须连续的。
2)复制算法:把一块内存分为两部分,同一时刻只使用其中的一部分,当使用的一部分进行可达性分析之后发现有不需要的对象,需要被释放掉,此时我们就把,不需要别释放的对象拷贝到另一半并且尽量保持连续,之后将原本的那一半进行释放,并且不断地循环这个过程。
这个方法也有明显的缺点 1)空间利用率低 2)如果存活下来的对象比较多,那么进行复制拷贝的成本就比较大。
3)标记整理:就类似于顺序表的删除,将部分元素向前移动,但是这样的方式如果数据量比较大的情况下,开销也是不小的。
上述的三种方式都不是JVM垃圾回收的方式但是我们根据上述方式取长补短创建了一种新的方式分代回收:分代回收将内存分为两大类 一类是新生代 另一类是老年代 其中新生代中有分为两类一类是伊甸去另一类是幸存区,其中幸存区有两块,但是这两块都是非常小的。
当我们创建新的类是这个类会出现在新生代中也就是新的类,这块内存是比较大的,根据我们的经验可知,大部分的类都活不过GC,因此保留下来的部分比较少,并且经过GC后会将这些幸存下来的列复制到幸存区中,前面提到幸存区有两部分,在这两部分中也会进行GC这两部分会进行上面讲述的复制算法这两部分会循环的进行这一操作,直到可达性分析达到一定次数之后,会认为这些剩余的类是会长期幸存的,之后会将这些类拷贝到老年区,在老年区中也会进行GC但是GC的频率会减低,因为在老年区中的类默认是会长期幸存的,这样还可以节省一些资源。
上述的这些只是JVM垃圾回收机制的基本思想,真正实现这个过程是通过,“垃圾回收器”来实现的,JVM提供了许多的垃圾回收器。
图中的几种“垃圾回收器”我们只需要知到两种即可 CMS 和 G1这两种是目前比较主流的垃圾回收器,其他的垃圾回收器已经渐渐地被历史淘汰了。