一、为什么要进行垃圾回收
因为没有足够的内存一直进行内存分配,对垃圾的回收是必须的。
二、那些垃圾需要回收
肯定是一些已经没有用的对象。这就产生了一个问题,如何去找到这些没有用的对象呢?
- 引用计数法:
引用计数算法听起来就很好理解,无非就是通过给对象添加引用计数器,当这个对象被引用时计数器加1,当引用失效时计数器减1,直到当计数器为0时就可以把这个对象进行回收,但是java虚拟机却没有应用这种方法,主要时因为它不能解决对象之间的相互引用。 - 可达性分析法:
这个算法的基本思想就是通过以GC-root为对象根节点向下进行搜索,搜索走过的路径称为引用链,如果一个对象到gc-root没有任何引用链就说明这个对象不可用就可以回收了。附上自己亲手画的图:
如上图,obj9,obj10,ojb11三个对象都和root没有引用链,即使obj10和obj11直接存在引用关系也会被当作垃圾进行回收。
那么又有一个问题,那些对象可以被当作gc-root呢?
(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
(2). 方法区中的类静态属性引用的对象。
(3). 方法区中常量引用的对象。
(4). 本地方法栈中JNI(Native方法)引用的对象。三、四种引用类型
1、强引用:
new出的这类引用,只要强引用还在,垃圾收集器永远都不会回收掉被引用的对象
2、软引用:
只有在将要发生内存溢出之前,将会把这些对象列为回收范围进行回收对象进行二次回收,如果此时内存不够,将会抛出内训溢出异常。Java中SoftReference表示软引用。
3、弱引用:
被弱引用关联的对象只能生存到下一次的垃圾回收之前,垃圾回收开始,无论当前的内存是否充足,对象都会被回收。java中的weekreference表示弱引用。
4、虚引用:
被虚引用关联的对象其生命和时间没有关系,Java中的类PhantomReference表示虚引用。
对可达性分析而言,没有引用链和gc-root关联的对象未必非死不可,这时他们暂时处于缓刑期,要真正宣布一个对象的死亡最少要经过两次标记。具体描述详见深入理解java虚拟机3.24节。
四、方法区的垃圾回收
方法区的垃圾回收主要回收1、没有用的常量和2、废弃的类。
如何判断废弃常量呢?以字面量回收为例,如果一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
如何判断无用的类呢?需要满足以下三个条件
- 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
满足以上三个条件的类可以进行垃圾回收,但是并不是无用就被回收,虚拟机提供了一些参数供我们配置。
五、垃圾回收算法(图解)
- 标记清除算法:
标记-清除算法时最基本的垃圾回收算法,如同算法名字一样,算法有标记和清楚两个过程。
标记:标记过程就是可达性分析过程,遍历从gc-roots可达的每一个对象都打上标记,一般在对象的header中,将其记录为可达对象。
清除:清除过程是对堆内存进行遍历,发现那个对象没有可达对象标记对其进行回收。
算法缺点:
(1)时间问题:遍历内存次数过多,效率太低
(2)空间问题:清除后产生产生零散空间,空间利用率不高。
2、 复制算法:
为了解决效率问题,引入复制算法。复制算法的原理就是,把一块内存分成大小相等的两块,每次只使用其中一块,当内存用完后把其直接复制到另一块,然后把原来的内存全部清空。如下图:
回收前:
回收后:
缺点:
(1)将内存分成大小两块,浪费一半的内存空间,代价太高。
(2)极端情况,加入对象100%存活,复制也同样需要大量时间。
3. 标记-整理算法:
和标记-清除类似,只是标记后不会清除而是让所有的存活对象向一端移动,然后清除掉端边线以外的内存
回收前:
回收后:
这样既解决了标记-清除算法内存零散的问题,也弥补了复制算法内存减半的高昂代价。但是人无完人,标记-整理算法的效率也不高,不仅要标记存活对像还要整理所有存活对象的引用地址,在效率上不如复制算法。
4.终极算法-分代回收算法:
现在的虚拟机大多采用分代回收算法,无非也就是几种回收算法的结合使用。新生带采用复制算法,老年代采用标记-清除或者标记-整理算法。附带一张学习时看到的图片说明问题:
垃圾收集器在这里先不进行罗列了,深入了解之后加以补充。