美文网首页
深入理解 Java 虚拟机读书笔记2

深入理解 Java 虚拟机读书笔记2

作者: jkwen | 来源:发表于2021-01-09 20:00 被阅读0次

    垃圾回收

    通常垃圾回收是针对 Java 堆和方法区所做的操作,其他部分由于线程私有并且本身所占空间不大不用太关心,垃圾回收器要做的三件事:

    • 哪些内存需要回收
      在 Java 堆中,肯定是对那些不再被引用的对象实例进行回收,而如何判断对象不再被任何地方引用是个关键。通常有两种方式:

      1. 引用计数法
        实现简单,高效,但存在着相互循环引用的问题。
      2. 可达性分析
        会有一个起始点,从该点出发进行搜索,能走到的对象就是可达的,也就是有效的引用,否则就是无效可回收的。
        在 Java 中能作为起始点的有:Java 虚拟机栈的本地变量表中的引用对象,方法区中类静态属性引用对象,方法区中常量引用对象,本地方法栈中 JNI 引用对象。

      在方法区中,主要回收废弃常量和无用的类。废弃常量的确定和确定 Java 堆中不再引用的对象实例类似。对于无用的类,则要满足 3 个条件才会被回收:

      1. 该类所有实例都已被回收
      2. 加载该类的 ClassLoader 已被回收
      3. 该类对应的 java.lang.Class 对象没有被任何地方引用,无法通过反射访问该类的任何方法。

      虽说只要没有引用就会被回收,那怎么做到完全没有引用呢?Java 通过划分引用类型,控制着对象引用的不同。

      • 强引用(Strong Reference)
        默认的引用方式,通常用的也最多,回收器永远不会回收这类引用。
      • 软引用(Soft Reference)
        介于强引用和弱引用之间,在内存不足时会优先考虑。这种方式我觉得适用于 MVP 中的 V 层。
      • 弱引用(Weak Reference)
        引用对象只能生存到下次垃圾回收之前,不论内存是否足够,只要触发了垃圾回收,他们就会被回收掉。
      • 虚引用(Phantom Reference)
        用处不大。
    • 什么时候回收
      对象真正可回收需要经过两次标记。第一次是引用计数或可达性分析,第二次是对象如果有必要执行 finalize() 方法会进行第二次标记。如果在执行 finalize() 时没有救活自己,就准备被回收了。

    • 怎么回收
      几种基本的回收算法

      • 标记-清除算法
        算法分为两个步骤,标记和清除,标记的过程就如上面所说,标记完之后,就会对标记区进行内存回收,这样内存空间就出来了。
      • 复制算法
        该算法是针对「标记-清除算法」效率低的问题进行的优化,将内存空间对半,每次仅用一半,当用完一半时,就将活着的对象复制到另一半去,然后回收这一半。
        不过这是算法理论,实际上 IBM 将内存划分为 Eden 和 Survivor(该区域有两块),每次用 Eden 和其中一块 Survivor,另一块用来在回收时保存活着的对象。而HotSpot 虚拟机明确了 Eden 和 Survivor 的比例,8 : 1,即新生代的有效可用空间为 90 %,剩下 10 % 的空间用来保存对象。
        但是 10 % 真的一定够吗?不够了怎么办,系统的设计是让老年代来做担保(类似于透支)
      • 标记-整理算法
        该算法又考虑到「复制算法」在存活率较高的情况下,效率较低,而且很有可能出现担保现象的问题,对此进行了优化。这种算法更适合老年代(不然老年代找谁担保)。
        这个算法分三个步骤,标记,整理,回收。标记过程和前面类似,标记完之后,会对存活的对象进行位置整理,使他们整齐排在一起,然后回收其他部分。

      Java 堆根据对象存活周期的不同,划分为了新生代和老年代,不同代采用不同回收算法是比较合适的。通常新生代用「复制算法」,老年代用「标记-整理算法」或「标记-清除算法」

      HotSpot 的算法实现

      具体的算法实现肯定很复杂,这里仅引入几个概念。

      在做可达性分析时会存在耗时和线程停顿执行的问题。为了解决这两个问题,引入了 OopMap 的数据结构,在类加载完时就存储好对象引用链相关的位置引用,这就大大提高了效率。

      但 OopMap 不会都记下来,只有在「安全点」的时候才会存,「安全点」在我的理解是指线程执行指令到了某个逻辑划分的小任务完结点,此时存储一些数据是比较合适,并且这个时候做 GC 操作也是合适的。

      那如何让线程走到「安全点」是个问题。系统采用「抢断式中断」和「主动式中断」对线程进行控制,现在主要以「主动式中断」为主,即在将要 GC 时,设置一个标记,各线程会主动查询这个标记,如存在这个标记,就把自己挂起。

      「安全点」对醒着的线程是可靠的,但对于那些睡着的或者阻塞的线程,他们没办法知道那个标记点,于是设立一个「安全区域」扩大安全范围,线程进入「安全区域」时标记一下,当要出去时先看下外面安不安全,安全的话才能出去,否则就要等待可以出去的信号。

    相关文章

      网友评论

          本文标题:深入理解 Java 虚拟机读书笔记2

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