深入理解JVM垃圾回收机制
- 格式:doc
- 大小:388.00 KB
- 文档页数:24
Java语言出来之前,大家都在拼命的写C或者C++的程序,而此时存在一个很大的矛盾,C++等语言创建对象要不断的去开辟空间,不用的时候有需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的allocated,然后不停的~析构。于是,有人就提出,能不能写一段程序在实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢? 1960年 基于MIT的Lisp首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时Java还没有出世呢!所以实际上GC并不是Java的专利,GC的历史远远大于Java的历史! 那究竟GC为我们做了什么操作呢?
1、 哪些内存需要回收? 2、 什么时候回收? 3、 如何回收?
这时候有人就会疑惑了,既然GC已经为我们解决了这个矛盾,我们还需要学习GC么?当然当然是肯定的,那究竟什么时候我们还需要用到的呢?
1、 排查内存溢出 2、 排查内存泄漏 3、 性能调优,排查并发瓶颈
我们知道,GC主要处理的是对象的回收操作,那么什么时候会触发一个对象的回收的呢? 1、 对象没有引用 2、 作用域发生未捕获异常 3、 程序在作用域正常执行完毕 4、 程序执行了System.exit() 5、 程序发生意外终止(被杀进程等) 其实,我们最容易想到的就是当对象没有引用的时候会将这个对象标记为可回收对象,那么现在就有一个问题,是不是这个对象被赋值为null以后就一定被标记为可回收对象了呢?我们来看一个例子:
package com.yhj.jvm.gc.objEscape.finalizeEscape; import com.yhj.jvm.gc.objEscape.pojo.FinalizedEscapeTestCase; /** * @Described:逃逸分析测试 * @author YHJ create at 2011-12-24 下午05:08:09 * @FileNmae com.yhj.jvm.gc.finalizeEscape.FinalizedEscape.java */ public class FinalizedEscape { public static void main(String[] args) throwsInterruptedException { System.out.println(FinalizedEscapeTestCase.caseForEscape); FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase(); System.out.println(FinalizedEscapeTestCase.caseForEscape); FinalizedEscapeTestCase.caseForEscape=null; System.gc(); Thread.sleep(100); System.out.println(FinalizedEscapeTestCase.caseForEscape); } } package com.yhj.jvm.gc.objEscape.pojo; /** * @Described:逃逸分析测试用例 * @author YHJ create at 2011-12-24 下午05:07:05 * @FileNmae com.yhj.jvm.gc.pojo.TestCaseForEscape.java */ public class FinalizedEscapeTestCase {
public static FinalizedEscapeTestCase caseForEscape = null; @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("哈哈,我已逃逸!"); caseForEscape = this; } }
程序的运行结果回事什么样子的呢? 我们来看这段代码
1、 System.out.println(FinalizedEscapeTestCase.caseForEscape); 2、 FinalizedEscapeTestCase.caseForEscape = newFinalizedEscapeTestCase(); 3、 System.out.println(FinalizedEscapeTestCase.caseForEscape); 4、 FinalizedEscapeTestCase.caseForEscape=null; 5、 System.gc(); 6、 Thread.sleep(100); 7、
System.out.println(FinalizedEscapeTestCase.caseForEscape);
1、 当程序执行第一行是,因为这个对象没有值,结果肯定是null 2、 程序第二行给该对象赋值为新开辟的一个对象 3、 第三行打印的时候,肯定是第二行对象的hash代码 4、 第四行将该对象重新置为null 5、 第五行触发GC 6、 为了保证GC能够顺利执行完毕,第六行等待100毫秒 7、 第七行打印对应的值,回事null么?一定会是null么? 我们来看一下对应的运行结果
本例中打印了 GC的日志,让我们看的更清晰一点,我们很清晰的看出,最后一句打印的不是null,并且子啊之前,还出现了逃逸的字样。说明这个对象逃逸了,在垃圾回收之前逃逸了,我们再来看这个pojo的写法,就会发现,我们重写了方法finalize,而这个方法就相当于C++中的析构方法,在GC回收之前,会先调用一次这个方法,而这个方法又将this指针指向他自己,因此得以成功逃逸!可见,并不是这个对象被赋值为null之后就一定被标记为可回收,有可能会发生逃逸!
下面我们来看一下几种垃圾收集算法 1、 在JDK1.2之前,使用的是引用计数器算法,即当这个类被加载到内存以后,就会产生方法区,堆栈、程序计数器等一系列信息,当创建对象的时候,为这个对象在堆栈空间中分配对象,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用的时候,引用计数器继续+1,而当其中一个引用销毁的时候,引用计数器-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了!这种算法在JDK1.2之前的版本被广泛使用,但是随着业务的发展,很快出现了一个问题 当我们的代码出现下面的情形时,该算法将无法适应 a) ObjA.obj = ObjB b) ObjB.obj - ObjA 这样的代码会产生如下引用情形 objA指向objB,而objB又指向objA,这样当其他所有的引用都消失了之后,objA和objB还有一个相互的引用,也就是说两个对象的引用计数器各为1,而实际上这两个对象都已经没有额外的引用,已经是垃圾了。
2、 根搜索算法 根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
目前java中可作为GC Root的对象有 1、 虚拟机栈中引用的对象(本地变量表) 2、 方法区中静态属性引用的对象 3、 方法区中常量引用的对象 4、 本地方法栈中引用的对象(Native对象) 说了这么多,其实我们可以看到,所有的垃圾回收机制都是和引用相关的,那我们来具体的来看一下引用的分类,到底有哪些类型的引用?每种引用都是做什么的呢? Java中存在四种引用,每种引用如下: 1、 强引用 只要引用存在,垃圾回收器永远不会回收 Object obj = new Object(); //可直接通过obj取得对应的对象 如obj.equels(new Object()); 而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。 2、 软引用 非必须引用,内存溢出之前进行回收,可以通过以下代码实现 Object obj = new Object(); SoftReference sf = new SoftReference(obj); obj = null; sf.get();//有时候会返回null 这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null; 软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。 3、 弱引用 第二次垃圾回收时回收,可以通过如下代码实现 Object obj = new Object(); WeakReference wf = new WeakReference(obj); obj = null; wf.get();//有时候会返回null wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾 弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。 弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器 4、 虚引用(幽灵/幻影引用) 垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现 Object obj = new Object(); PhantomReference pf = new PhantomReference(obj); obj=null; pf.get();//永远返回null pf.isEnQueued();//返回从内存中已经删除 虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。 虚引用主要用于检测对象是否已经从内存中删除。 在上文中已经提到了,我们的对象在内存中会被划分为5块区域,而每块数据的回收比例是不同的,根据IBM的统计,数据如下图所示: