美文网首页
JVM系列——垃圾收集(一)

JVM系列——垃圾收集(一)

作者: 阿斯巴甜不太甜 | 来源:发表于2019-08-19 21:38 被阅读0次

垃圾收集

  • 哪些内存需要回收?
  • 什么时候回收?
  • 怎么回收?

为什么需要了解垃圾收集呢?

当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

由于栈是归属于每个独立线程的,因此当线程结束或方法结束时,内存就自然被回收,而堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,而垃圾回收器所关注的就是这部分内存。

确定对象存活状态

垃圾收集器在对堆进行回收前,需要确定堆中哪些对象还“活着”

引用计数算法

常规的思路:给对象中添加一个计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。

然而主流的JVM并没有选用这种算法来管理内存,因为很难解决对象之间相互循环引用的问题。例如:

public class GC{
    public Object instance = null;
    public static void testGC(){
      GC a = new GC();
      GC b = new GC();
      a.instance = b;
      b.instance = a;
      System.gc();//系统的回收方法
    }
}

实际上,在调用了System.gc()后,a和b都可以被回收,这也侧面说明了JVM不是采用的引用计数器算法来判定对象的存活状态的。

可达性分析算法

image

在主流的商用程序语言(JAVA、C#)的主流实现中,都是通过可达性分析来判断对象是否存活。

基本思路:通过一系列的“GC Roots”的对象作为起点,从这些起点向下搜索,如果有对象无法连接到GC Roots集合中的任何一个根的话,则该对象被判定为不可用。

在JAVA中,可作为GC Roots的对象包括:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象

生存还是死亡

即使在可达性分析中不可达的对象,也并非一定要被回收,真正回收一个对象,需要经过两次标记过程。

  • 判断GC Roots是否可达
  • 判断是否有必要执行finalize()方法

如果finalize之前已经被调用过或者当前对象并没有重写该方法,则没有必要执行,直接被回收;如果被覆盖,则该对象会被放在一个F-Queue中,并在之后由一个优先级较低的Finalizer线程去执行它。

如果在执行finalize()的过程中,改对象能够重新与引用连接上,那么改对象就自救成功,不会被回收了。

这里要注意的是:finalize只有一次自救机会,一旦被调用过之后,下一次再第二次标记时就会被回收。

来看这段代码:


public class FinalizeEscapeGC {
    
    public static FinalizeEscapeGC SAVE_HOOK = null;
    
    public void isAlive() {
        System.out.println("yes,i am still alive :)");
    }
    
    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }
    
    public static void main(String[] args) throws Throwable {
        // TODO Auto-generated method stub
        SAVE_HOOK = new FinalizeEscapeGC();
        
        //对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        //因为finalize方法优先级很低,所以暂停0.5s等候他
        Thread.sleep(500);
        if(SAVE_HOOK!=null) {
            SAVE_HOOK.isAlive();
        }else
            System.out.println("no, i am dead :(");
        
        SAVE_HOOK = null;
        System.gc();
        //因为finalize方法优先级很低,所以暂停0.5s等候他
        Thread.sleep(500);
        if(SAVE_HOOK!=null) {
            SAVE_HOOK.isAlive();
        }else
            System.out.println("no, i am dead :(");
    }

}

结果是

finalize method executed!
yes,i am still alive :)
no, i am dead :(

可以看出,SAVE_HOOK对象的finalize确实被执行过,并且成功自救了;同时也说明了finalize调用过一次之后,就无法再进行自救。

回收方法区

方法区中的内存回收主要包括两个部分:废弃常量和无用的类

废弃常量的回收与堆中的对象回收非常相似。只要该常量在当前系统中没有任何变量对其引用,则进行回收。

无用类的判定条件则相对苛刻:

  • 该类的所有实例都已经被回收——堆中不存在任何该类产生的对象
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

总的来说,在堆中进行一次垃圾收集一般可以回收70%-95%的空间,而对方法区的回收效率却远低于此。

相关文章

  • 7种JVM垃圾收集器特点,优劣势、及使用场景

    本系列会持续更新。 今天继续JVM的垃圾回收器详解,如果说垃圾收集算法是JVM内存回收的方法论,那么垃圾收集器就是...

  • JVM系列——垃圾收集(一)

    垃圾收集 哪些内存需要回收? 什么时候回收? 怎么回收? 为什么需要了解垃圾收集呢? 当需要排查各种内存溢出、内存...

  • Jvm之CMS垃圾收集器

      这篇文章主要介绍JVM的CMS垃圾收集器,以及JVM提供的垃圾收集算法。 一、垃圾收集算法   JVM提供的三...

  • JVM源码分析系列

    JVM G1算法系列 G1垃圾收集器介绍 G1垃圾收集器之RSet G1垃圾收集器之SATB G1垃圾收集器之对象...

  • JVM系列——垃圾收集(二)

    垃圾收集算法 1. 标记—清除算法 标记—清除算法就和其名字一样,分为标记和清除两个阶段:首先按上一篇文章中提到的...

  • JVM系列——垃圾收集(三)

    垃圾收集器 上一篇讲的收集算法是内存回收的方法论,而垃圾收集器则是内存回收的具体实现。书中讨论的收集器基于JDK1...

  • JVM系列——垃圾收集(四)

    CMS收集器 CMS收集器是一种获取最短回收停顿时间为目标的收集器,基于的是标记-清除算法。它的运作过程相对于去墙...

  • JVM系列——垃圾收集(五)

    内存分配与回收策略 Java的相较于C++的方便之处在于,可以自动化地解决两个问题:给对象分配内存以及回收分配给对...

  • JVM学习笔记(3)-垃圾收集算法

    JVM学习笔记(1)-内存管理机制 JVM学习笔记(2)-内存分配与回收 垃圾收集算法 JVM垃圾收集算法有四种:...

  • 常见面试题

    JVM JVM 内存结构 垃圾收集策略与算法记回收,标记清除,复制算法 HotSpot 垃圾收集器 cms hos...

网友评论

      本文标题:JVM系列——垃圾收集(一)

      本文链接:https://www.haomeiwen.com/subject/pngesctx.html