读 - 深入理解java虚拟机 - 笔记(六-1) - 垃圾收集器和内存分配策略(3章)-对象已死吗

时间:2023-01-02 09:18:50

虚拟机这本书我还只能停留在翻译的边缘,没有能力去结合实际工作经验分享自己的体会,因为自身的编程工作仍然是业务驱动,并且也没有很大的并发量,这是致命的,自己根本碰不到需要深入了解虚拟机去解决问题的程度,也是很想去接触这种项目,但是高大上的公司又没有机会,只能是读书去了解知识了。

判断对象是否已死。主要有两种判断方法。

1.引用计数法

基本思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就+1,当引用失效时,计数器的值就-1,任何时刻计数器为0的对象就是不可能再被使用的。

这种方式判断效率高,但是不能解决一个问题,那就是循环引用的问题。对于循环引用,应该都不陌生吧。

2.可达性分析算法

基本思想:通过可达性分析来判定对象是否存活。也就是通过一系列成为“GC Roots”的对象作为起始点,从这些节点开始向下搜素,所搜所走过的路劲称为引用链,当一个对象到GC Roots没有任何引用链相连,图论里 的话来说就是 GC Roots到这个对象不可达时,则证明此对象是不可用的。如下图:虽然Object5,6,7是相连的,但是GC Roots是不可达的,所以它们会被认定为可回收对象。

读 - 深入理解java虚拟机 - 笔记(六-1) - 垃圾收集器和内存分配策略(3章)-对象已死吗

那么Java语言中,能被作为GC Roots的对象有以下几种:

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

2.方法区中类静态属性引用的对象。

3.方法区中常量引用的对象

4.本地方法栈中JNI(Native)引用的对象。

以上是虚拟机中书中提到的,但是我去阅读了一下其他人的博客,发现还有另外的一些解释,

GC Roots
博客中提到了一些:
1.Class:由系统类加载器加载的类,但是不包括由自定义的类加载器加载的类不是Root,除非相应的实例是其他类型的Root。

2.Thread:活着的线程

3.Stack Local:java方法的参数或者本地变量

4.JNI Local:JNI方法的参数或者本地变量

5.Monitor Used:同步用的监控器

6.Held by JVM:JVM自己持有的对象,比如类加载器,异常

3.再谈引用
我们知道,引用其实就是存储在栈中的一个地址,里面存储的是堆中的一块具体对象的实际起始地址,这是一般的看法。

后来对引用的概念进行了扩充,分为4种引用类型:

1.强引用---强引用普遍存在,就是我们new出来的对象,因此只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象。

2.软引用---一些有用但是并非必须的对象,当系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收依然没有足够的内存,才会抛出异常,提供SoftReference类来实现。

3.弱引用---也是用来描述非必须对象,强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉指被弱引用关联的对象,提供了WeakReference类实现弱引用。

4.虚引用---称为幽灵引用或者幻影引用,是最弱的一种引用关系,无法通过虚引用来取得一个对象实例。设置的唯一目的就是能在这个对象被垃圾回收器回收时收到一个系统通知。提供PhantomReference实现

在试验引用之前,还需要说下对象的死亡过程,即使在可达性算法中的不可达对象,也并不是立即死亡,此时这个对象处于“缓刑”阶段,当它真正死亡时,需要两次标记过程:

1.当对象在进行可达性算法分析后发现没有与Gc Roots相连接的引用链,它将会被第一次标记并且进行一次删选,删选的条件是此对象是否有必要执行finalize()方法。

2.当对象没有覆盖finalize()方法,或者此方法已经执行过,虚拟机都将认为“没有必要执行”。

3.如果对象被判定需要执行此方法,那么次对象会被放在一个F-Queue队列中,并且会被虚拟机自动创建的,低优先级的Finalizer线程去执行,稍后GC将会对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize()方法中成功拯救,也就是重新与引用链上任何一个对象建立关联,那么在第二次标记时它将被移除“即将回收”的集合。

public class Person {


    public static Person person;


    public static void isAlive(){
        System.out.println("I am alive");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        person = this;
    }
}
public class Test {

    public static void main(String[] args)throws Exception {

       Person.person = new Person();

       //第一次拯救
       Person.person = null;
       System.gc();
        System.out.println("start gc one");
       Thread.sleep(5000);
       if(Person.person != null){
           Person.isAlive();
       }else {
           System.out.println("I am dead");
       }
       //第一次拯救
       Person.person = null;
       System.gc();
        System.out.println("start gc two");
       Thread.sleep(5000);
       if(Person.person != null){
           Person.isAlive();
       }else {
           System.out.println("I am dead");
       }

    }
}
start gc one
save self
I am alive
start gc two
I am dead

弱引用测试
public class Person {

    private int num;
    private String name;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Person(int num, String name) {
        this.num = num;
        this.name = name;
    }
public class Test {

    public static void main(String[] args)throws Exception {

        Person p = new Person(1,"sdas");

        ReferenceQueue<Person> rp = new ReferenceQueue<Person>();

        WeakReference<Person> sp = new WeakReference<Person>(p,rp);

        System.out.println("p1: "+ p);
        System.out.println("sp1: "+ sp.get());
        System.out.println("rp1: "+rp.poll());
        System.out.println("start gc");
        p = null;
        System.gc();
        System.gc();
        System.gc();
        Thread.sleep(5000);
        System.out.println("p2: "+ p);
        System.out.println("sp2: "+ sp.get());
        System.out.println("rp2: "+rp.poll());
    }
}
p1: com.wayne.personal.Person@771b16a7
sp1: com.wayne.personal.Person@771b16a7
rp1: null
start gc
p2: null
sp2: null
rp2: java.lang.ref.WeakReference@1ef1257f
主要关注当时构造这个弱引用时传入的队列,这个队列记录的是哪一个弱引用被回收了。

主要的测试其实都是围绕着引用队列和引用实例来的,这边不一一测试了。

4.回收方法区

Java虚拟机规范里说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区进行垃圾收集的性价比一般比较低,在新生代中,进行一次垃圾收集可以回收70-95%的空间,而在永久代中,效率远低于此。

永久代的垃圾收集主要回收两部分内容,废弃常量和无用的类。

废弃常量的判断比较简单,不做叙述,大致就是没有引用它的对象了。

无用的类定义比较苛刻,需要满足3个条件:

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

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

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

满足以上条件的无用的类可以进行回收。