深入理解java虚拟机-垃圾回收机制(1)

垃圾回收机制(1)

java中取消了指针操作,采用java虚拟机自动管理对象。Garbage Collection(GC)也就是所谓的垃圾回收器在java之前就得到了使用,1960年诞生的Lisp是第一门使用内存动态分配和垃圾回收的语言。

如何判断对象是否存活。

(1)引用计数算法。(Jvm不用这个算法)
给对象添加一个引用计数器,用一个地方引用它,计数器值就+1,引用失效一个,计数器就减1.当对象的引用计数器值为0则表示该对象已死亡,需要进行回收。但是JVM不用这个算法,当循环引用时,会产生问题。
例:

public class A{
public Object instance = null;
}
A a = new A();
A b = new A();
a.instance = b;
b. instance = a;
a = null;
b = null;
System.gc();

此时a对象跟b对象的引用计数值都为1,引用计数算法就失效了。
(2)可达性分析算法
Java、C#等都采用这个算法判断对象是否存活。将实例化的对象挂到GC Roots的路径上,当一个对象到GC Roots没有引用链时则被判断为对象已死亡(不可用)。这样如果两个对象互相引用,但是没有达到GC roots的路径,这两个对象还是能对回收。java中,可作为GC Roots的对象包括:堆栈中的本地变量表中引用的对象,方法去中静态属性引用的对象,常量引用的对象,本地方法栈中JNI引用的对象。

引用

我们知道reference是种引用类型,其中存着对象的内存地址。
java中分为四种引用:强引用、软引用、弱引用、虚引用。
(1)强引用:Object o = new Object()类似这种的就是强引用,只要强引用还在,那么垃圾回收器就不会回收被引用的对象。
(2)软引用:软引用用来描述一些还有用但并必需的对象。在系统将要发生内存溢出异常之前,也就是内存不足时,会将这些对象列入回收范围进行回收。jdk1.2之后,可以通过java.lang.ref.SoftReference类来表示软引用。
例如:

SoftReference<Object> o = new SoftReference<Object>(new Object());

应用:可以应用到一些缓存上,当内存不够时,对这些对象进行回收。
(3)弱引用:用来描述非必要对象,当下一次垃圾回收器进行回收时,就会被回收掉,不管内存是否足够。通过java.lang.ref.WeakReference来引用对象。
例如:

WeakReference<Object> o = new WeakReference<Object>(new Object());

(4)虚引用:这是一种最弱的引用,无法获得实例对象,唯一目的是当这个对象被回收时收到一个系统通知。用PhantomReference来表示,虚引用必须与引用队列ReferenceQueue关联
例如

        ReferenceQueue<String> queue = new ReferenceQueue<String>();
        PhantomReference<String> pr = new PhantomReference<String>(new String("lalal"), queue);
        System.out.println(pr.get());

当这个字符串对象将要被回收时,如果是个虚引用,那么这个虚引用会被加到引用队列中。如果发现引用队列中有虚引用,那么就知道这个对象要被回收了。

回收过程

如果一个对象是一个不可达对象,这个对象最终不一定会被回收。将要被回收的对象会有一个回收过程。真正宣告这个对象将被虚拟机回收会有两个标记过程。先将这些可以被回收的对象进行第一次标记,然后进行一次删选,将覆盖了finalize()方法且这个方法没有被调用过的对象放置到F-Queue队列中,然后由一个低优先级的线程去执行这个队列中对象的finalize()方法。在finalize这个方法中我们可以拯救这个对象,将这个对象挂到GC roots的路径上,那么这个对象就不会被回收。接着在第二次标记时将被拯救的对象移除要被回收的集合,然后还剩下的对象就会被回收。有一点要注意的是这个finalize方法只能被调用一次,也就是说只有一次被拯救的机会。而且我们需要避免去使用这个方法,这个方法的运行代价大,而且有不确定因素。

垃圾回收区的范围

垃圾回收的范围包括了,方法区跟堆区。虽然方法区被称为永久代,但是方法区中还含了常量池跟类的数据,方法区中无用的常量跟无用的类会被垃圾回收器回收。
满足三个条件则被称为无用的类:
(1)该类所有的实例以及被回收。
(2)加载该类的ClassLoader已经被回收。
(3)该类的Class对象没有被引用。

垃圾收集算法

(1)标记-清除算法
这是JVM中最基础的收集算法。
顾名思义,该算法分为标记与清除两个步骤。
标记就是之前说的回收过程中的两个标记过程,然后将可回收的内存空间进行回收。如下图所示,我们可以看见在清除垃圾后,会留下许多内存碎片,所谓的内存碎片就是很小的内存空间。当需要给较大的对象分配内存空间时,则找不到足够大的空间,那么就又需要执行垃圾回收。标记算法清除垃圾的效率不高,因为需要经过大量的标记,然后逐个将垃圾清除,同时内存碎片的问题还会经常触发垃圾回收。
这里写图片描述
(2)复制算法
复制算法是为了解决“标记-清除算法”效率不高的问题,这个算法将内存容量划分为大小相等的两块,一块作为保留区域,一块分配对象。当分配对象的区域不足以分配对象是,那么将这块区域存活的对象复制到保留区域上,然后将已使用过的内存空间一次清理掉。
这里写图片描述
这种算法得缺点就时内存空间的利用率不高,因为需要留下一半的空间来作为保留区域。
现在的商业虚拟机采用这种垃圾收集算法来回收新生代。IBM公司研究表明,新生代中的对象98%是很快死亡,所以讲新生代的内存分为较大的Eden空间和两块较小的Servivor空间。HotSpot虚拟机的默认Eden区和Survivor区的比例是8:1,也就是说有两块Survivor区。回收时是将Eden区和Survivor区存活的对象复制到另一块Survivor区,所以只有10%的内存空间被浪费。如果另一块Survivor区不足以存放这些存活下来的对象,那么将这些对象通过担保机制存入老年代。
(3)标记-整理算法
复制算法会在对象存活率高的时候进行较多的复制复制操作,那么效率会不高,而且如果不浪费50%的精简就需要有额外的空间进行分配担保。老年代对象的存活率较高,则提出了“标记-整理”算法。标记-清除算法和标记-整理算法的区别在于标记整理算法在回收的过程中不是对可回收对象进行清理,而是让存活对象向前面移动,然后清理掉边界以外的内存,这样大大提升了清理的效率,也减少了内存碎片。
(4)分代收集算法
所谓的分代收集算法就是将java堆划分为新生代和老年代,新生代由于对象的产生和消亡很快,存活率不高,所以采用复制算法,老年代因为对象存活率高,采用“标记-清理”或者“标记-整理”算法。

GC时刻

这里主要讲述HotSpot虚拟机的算法实现。
我们在做可达性分析时需要找出GC Roots的节点,GC Roots节点主要在全局性的引用与栈帧中的本地变量表内。虚拟机使用OopMap这个数据结构将对象引用的位置记录下来。这样我们可以根据OopMap中引用的信息来进行垃圾回收的可达性分析。
所以GC的时候,我们需要让OopMap中的信息就是当前引用的信息,如果引用变化快,那么OopMap中的内容就变得快,GC的空间成本会很高。这时候安全点就出现了。
(1)安全点:在特定的位置记录下OopMap中的信息,这个点叫安全点,GC也是在这个时候进行执行。因为在GC的时候需要所有引用不能变,所以需要停止线程,那么这个安全点之间的距离不能太近,也不能太远。选定是以“是否具有让程序长时间执行的特征”去判断,程序的指令执行时间不会太长。那么长时间的指令的特征就是指令序列复用,如:方法调用、循环跳转、异常跳转,在这些时候会产生安全点。
(2)线程中断:GC时需要线程中断,那么中断的方式有两种:一种是抢占式中断:就是GC发生时,主动将所有线程中断,第二种是主动式中断:GC发生时,如果有线程不在安全点上,那么就让线程跑到安全点上,再去中断。
(3)安全区域:安全区域是一块引用关系不会变化的区域,在这块区域GC是安全的。如果线程在被挂起的时候,线程是无法跑到安全点的。所有才有了安全区域。只有完成了GC或者根节点枚举,线程才能离开安全区。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值