CODE大全
版权声明:本文为博主原创文章,未经博主允许不得转载。

Jvm垃圾收集器

发布时间:『 2017-07-30 11:28』  博客类别:编程语言  阅读(1626) 评论(0)

目前内存的动态分配和内存的回收技术已经相当成熟,一切看起来都已经进入了“自动化”时代,为什么还要去了解GC和内存分配呢?原因很简单:当需要排查各种内存泄漏、内存溢出问题时,当垃圾收集器成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

之前的博客讲到了Java虚拟机运行时内存的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,栈中的栈帧随方法的进入和退出执行着入栈和出栈操作,因此方法结束或者线程结束时,内存就自然跟随着回收了。而Java堆和方法区组不同,我们在运行时才知道会创建哪些对象,这部分的内存和回收都是动态的,垃圾回收器所关注的也是这一部分内存。后文所讲的内存分配和回收也是仅指这一部分内存。

判断对象“死活”的方法

  • 引用计数法

  • 可达性分析算法

  • java引用

  • 对象不可达

  • 回收方法区

下面我们将分别介绍它们。

引用计数法

很多教科书或者开发人员,对于回答“如何判断对象死活?”这个问题的答案大多是引用计数法。实现起来是这样的:给对象添加一个引用计数器,每当一个地方引用它时就+1,当引用失效时就-1,任何时刻计数器为0的对象就是不肯能再被引用的。

客观的说,引用计数法实现简单,判定效率也很高,但是至少主流的Java虚拟机都没有采用引用计数法来管理内存,最主要的原因是它很难解决对象之间互相循环引用的问题。

可达性分析算法

可达性算法(Reachability Anlysis)是目前Java、C#的主流实现中来判定对象是否存活的。主要思路是:通过一些类成为“GC Roots”的对象做为起点,从这些结点开始往下搜索,搜索所走过的路称为引用链,当一个对象到GC Roots没有任何引用链时,则证明这个对象是不可用的。

可达性分析算法

Object1-Object4均有引用链和GC Roots相连接,而Object5-Oject7没有引用链相连,所以他们被判定为可以回收的对象

在Java中,可以做为GC Roots结点的有一下四种

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

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈中JNI(即Native方法)

java引用

在JDK1.2之前,Java中的引用定义为:如果reference类型的数据存储的是数值代表的是另一块内存的起始地址,就称为这块内存代表着一个引用。在JDK1.2之后,Java对引用概念进行了扩充,分为下面四种:

  • 强引用(Strong Reference):代码中普遍存在的,类似Object obj=new Object(),这类的引用,只要强引用还存在,GC就永远不会回收掉被引用的对象。

  • 软引用(Soft Reference):用来描述一些还有用但并非必需的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收之后还没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。

  • 弱引用(Weak Reference):也是用来描述非必需的对象,但强度比软引用要弱,被弱引用关联的对象只能生存到下一次垃圾回收器之前,当垃圾回收器工作时,一定会回收只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。

  • 虚引用(Phantom Reference):也称为幽灵引用或者幻影引用,是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象实例。为一个对象设置虚引用关联的唯一目的:在这个对象被垃圾回收器回收时收到一个系统通知。

对象不可达

即使在可达性分析算法中不可达的对象也不是“非死不可”的,这时候他们处于“缓刑”的阶段,真正要宣告一个对象死亡,至少要经过两次标记的过程。

如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那么它将被第一次标记并且执行一次筛选。筛选的条件是:这个对象是否有必要执行finalize()方法(若对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过(虚拟机只能调用一次),则视为“没有必要执行”)。

若被判定为有必要执行finalize()方法,那么该对象会被放置在一个叫做F-Queue的队列中,并在稍后有一个虚拟机自动建立的、低优先级的Finalizer线程去执行它(即虚拟机触发finalize()方法,但不承诺会保证等待它运行结束,因为如果一个对象的finalize()方法执行缓慢,或者发生了死循环,将很可能会导致F-Queue队列中的其他对象永远处于等待状态,甚至整个内存回收系统崩溃)。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次标记。如果对象要在这里拯救自己,就要重新与引用链上的任意一个对象建立关联即可。譬如,把自己复制给某个类变量或者对象的成员变量上,那么在第二次标记它时将被移除F-Queue队列。如果对象还没有逃脱,则被真正的回收了。

下面代码显示一次对象的自我拯救:

//代码演示了两点
//1.对象可以实现自救
//2.这种自救只能一次,因为finalize方法只会被执行一次
public class jvmtest1 {
    public static jvmtest1 obj = null;
    public void isAlive() {
        System.out.println("I am alive");
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        obj = this;
    }
    public static void main(String[] args) throws Exception {
        obj = new jvmtest1();
        obj = null;
        System.gc();
        // 因为finalize方法优先级很低,所以暂停0.5s执行
        Thread.sleep(500);
        if (obj != null) {
            obj.isAlive();
        } else {
            System.out.println("I am dead");
        }
        // 与上面的代码完全相同,但是自救失败了
        obj = null;
        System.gc();
        // 因为finalize方法优先级很低,所以暂停0.5s执行
        Thread.sleep(500);
        if (obj != null) {
            obj.isAlive();
        } else {
            System.out.println("I am dead");
        }
    }
}

可以看出obj的finalize()方法,确实被GC触发过,而且成功自救了一次。要注意的是下半部分的代码和上半部分的代码相同,但是自救失败,这是因为一个对象的finalize()方法只能被系统自动调用一次,如果对象面临第二次回收,其finalize()方法不会被调用,因此第二次自救失败。

回收方法区

方法区的垃圾收集“性价比”很低,在堆中,尤其是新生代中,常规应用进行一次垃圾收集一般可以回收70%-95%的空间,而方法区(HatSport虚拟机中的永久代)的回收效率远低于此。

永久代中的垃圾收集主要为两种:废弃常量和无用的类

回收废弃常量与回收Java堆中的对象非常类似。以常量池中字面量的回收为例:假如“abc”已经进入了常量池,但是没有任何String对象引用这个“abc”常量,也没有其他地方引用这个字面量,如果这时发生内存回收,而且有必要的话,这个“abc”常量就会被清出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似

判断一个类是否是无用类条件要复杂很多。类需要同时满足下面三个条件才是无用类:

  1. 该类的所有实例都已经被回收,也就是堆中不存在该类的任何实例

  2. 加载该类的ClassLoader已经被回收

  3. 该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

对于满足上面三个条件的类,也是可以回收,不是必然回收。在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。


——— 全文完 ———
如有版权问题,请联系532009913@qq.com。
关键字:   Jvm     内存泄漏     垃圾回收  
评论信息
暂无评论
发表评论
验证码: 
Powered by CODE大全 | 鄂ICP备14009759号-2 | 网站留言 Copyright © 2014-2016 CODE大全 版权所有