浅谈JVM(一)之判断GC回收

125 阅读4分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

1.内存区域

  • 线程隔离:

    • 虚拟机栈:保存执行方法时的局部变量表、操作数栈、动态连接和方法返回地址等信息,方法执行时入栈,执行结束出战;因为有出入栈,所以不需要GC

    • 本地方法栈:与虚拟机栈类似,区别在于,虚拟机栈执行Java方法,本地方法栈为虚拟机执行本地方法,也不需要GC

    • 程序计数器:线程独有的,记录当前线程执行的字节码的行号指示器;

      • 就是作为多线程的时候,CPU轮流切换并分配处理时间时使用;也就是A线程执行,线程被挂起,CPU切换到B线程执行;B执行完了,唤醒A线程,通过程序计数器获知,A线程上次被挂起的位置,并接着执行
      • 程序计数器是唯一一个 在Java虚拟机规范中没有规定任何OOM(内存泄漏)的区域;所以这里也不需要GC
    • 本地内存:线程共享的区域;主要存储类的信息,常量,静态变量,即时编译器编译后代码等;在Java8以后把方法区的实现转移到本地内存的元空间;这样方法区就不受JVM控制,也就不会收到GC

    • 堆:对象实例和数组都是在堆上分配的,GC也主要对这两类数据进行回收

2.GC如何判断回收

2.1引用计数法

  • 对象被引用一次,就记录对象被引用的次数+1,如果没有被引用过,则为0,则可以被回收

    • String ref = new String("Java");

    • 上述代码就被引用了一次;如果 String ref = null ,那就代表没有被引用,可以被回收

    • 但是现在虚拟机一般都不会使用引用计数法来判断对象是否应该被回收,是因为有一个缺陷,无法解决循环引用;

          String a = new String("a")
          String b = new String("b");
          a.instance = b;
          b.instance = a;
          a= null;
          b= null;
      

      虽然最后置空了,但是引用次数并不为0;因为,互相引用了对方;所以被引用次数为1;无法被回收

2.2可达性算法

现代虚拟机基本都是采用可达性算法来判断对象是否存活

原理

  • 以GC Root 的对象作为起点,并引出他们指向的下一个节点;并不断引出下一个节点(形成类似一颗树状的结构);
  • 这样通过GC Root串成的一条线就叫引用链;直至全部遍历完毕;
  • 如果有相关对象不在任意一个 以GC Root为起点的引用链中,则可以判断为 垃圾,并被回收

finalize

引用链没有到达 的相关对象,并不一定会被回收;当发生GC时,会走finalize 方法,在该方法内可以与GC Root关联;执行完finalize方法后,会再次判断GC回收

注意:finalize 只能被执行一次,当执行过finalize 方法后,对象不可回收后;下次再次GC检测到,则忽略finalize 方法,对象会被回收

可以作为GC Root 的对象

  • 虚拟机栈中引用的对象

    存储方法的引用,举个例子; a 是 虚拟机栈中的变量,a = null 时,a 与 new Object 断开了连接,所以 后面new的对象会被回收

    Object a = new Object();
    a = null;
    
  • 方法区中类静态属性引用的对象

    定义的静态变量 赋予了新的引用,a = null,则原来指向的对象会被回收,但是静态变量的s 则充当了 GC Root作用,指向的对象不会被回收

    public static Object s;
    ​
    Object a = new Object();
        a.s = new Object();
        a = null;
    
  • 方法区中常量引用的对象

    虽然 a指向了null 会被回收,但是 常量 s 不会因为a被回收而回收

    public static final Object s = new Object();
    Object a = new Object();
    a = null;
    
  • 本地方法栈中JNI 引用的对象

    也就是 调用Native方法(C等语言)