java gc

2020/01/17 Java

java的垃圾收集对我们来说是个黑盒,平时开发中,不用像c++一样考虑对象的回收,jvm会自动对没引用的对象进行回收。

程序计数器/虚拟机栈/本地方法栈 随线程而生,随线程而灭,栈帧中所需内存在编译期可得到,我们所说的垃圾回收是发生再堆和方法区

对象已死?

引用计数算法

在对象中添加已给引用计数,每当有一个地方引用的时候,计数器就加1,每当一个引用失效的时候,减1,在计数器为0的时候,就是不在其他地方使用的对象

优点:原理简单/判定效率高 缺点:有很多例外情况需要考虑,比如循环引用,如代码

public class ReferenceCountGC {

    public final static int _1M = 1024 * 1024;
    public Object instance;
    private byte[] bytes = new byte[2 * _1M];

    public static void main(String[] args) {
        ReferenceCountGC objectA = new ReferenceCountGC();
        ReferenceCountGC objectB = new ReferenceCountGC();
        objectA.instance = objectB;
        objectB.instance = objectA;
        objectA = null;
        objectB = null;
        System.gc();
    }
}

可达性分析算法

       通过一系列成为”GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程中所经过的节点称为”引用链”,如果某个对象到GC Roots之间没有任何引用链相连,则认为该对象不可能再被使用

       但在可达性算法中,判定为不可达的对象,也并非是”非死不可”,要宣告一个对象的死亡,还至少要经过两次标记, 在可达性算法中没有与GC Roots相连接的引用链,会被第一次标记,随后再进行一次筛选,筛选条件为该对象是否有必要执行finalize方法。

GC_Roots

引用

引用类型 | 定义 | 适用范围 | 生命周期 ——–| —- | ——- | ——–| 强引用 | 传统引用方式,Object obj = new Object() | 普遍引用方式 | 只要引用关系在,就不会被回收 | 软引用 | SoftReference实现 | 描述有用但非必须的对象 | 在系统发生内存溢出异常时,会把软引用的对象进行二次回收| 弱引用 | WeakReference实现 | 非必须对象,强度比软引用更低 | 下一次gc | 虚引用 | PhantomReference实现 | 当前对象被gc时收到一个系统通知 | 不会对其生存时间够成影响 |

1.GC算法

现在主流jvm都采用分代gc

分代gc理论

分代gc建立在两个假说上,

  1. 分代说(Weak Generational Hypothesis):绝大多对象是朝生夕灭的。
  2. 强分代说(Strong Generational Hypothesis):熬过越多次集过的对象 越难以消亡。

这两个假说奠定了现在主流的垃圾收集器将java堆划分为不同的区域,根据对象的年龄分到不同的区域内。

  • 标记-清除算法

      先标记要回收的对象,标记完成之后,统一回收标记的对象。

      缺点:

      1. 执行效率不稳定。如果Java堆中含有大量的要回收的对象,必须进行大量的标记和清楚,导致 标记和清除的效率随对象的数量增长而下降。

      2. 内存空间碎片化。标记清除之后,会产生大量不连续的空间碎片,太多的空间碎片会导致后续如果分配大对象找不到足够的内存空间而不得不提前进行一个gc

  • 标记-复制算法

      将可用内存按容量分成大小相等的两块,每次使用其中的一块,当这块内存用完之后,将存活对象复制到另一块内存上,并清除该块内存

缺点: 空间浪费大

  • 标记-整理算法

       标记过程同‘标记-清除算法’,标记完之后,将所有存活对象向空间另一端移动,然后直接清理边界以外的空间

  • 分代回收

2.GC收集器

  • CMS

      基于标记-清除算法实现,主要针对老年代的垃圾收集器,致力于最短停顿回收时间。整个运作过程需要4个步骤:

  1. 初始标记(Stop the world)标记GC Roots能关联到的对象,速度很快
  2. 并发标记 从GC Roots的直接关联对象开始遍历整个对象图的过程,耗时长,不需要停顿
  3. 重新标记(Stop the world)修正并发标记期间,因用户线程继续工作导致标记产生变动的标记记录,比初始标记时间长,并发标记时间短
  4. 并发清除 清理删除标记阶段已经判断死亡的对象

      示意图如下:

      java-cms

      优点:并发收集、低停顿

      缺点:1.对处理器资源非常敏感。2. 无法处理“浮动垃圾”

  • G1

      基于Region的堆内存布局,把连续的Java化分成多个大小相等的独立区域(Region)Region为单次GC回收的最小单元,每个Region都可以根据需要,自行扮演新生代的Eden区、survivor区、老年代。G1够对扮演不同角的Region用不同的略去处理

      Humongous区域:专门用来存储大对象,判定原则:大小超过Region容量一半以上为大对象,G1会把Region当作老年代

      Region空间大小设置:-XX:G1HeapRegionSize 取值范围1~32M,大小因为2的n次幂。

      示意图如下:

g1

3.其他

      并不是所有对象一开始都创建在新生代的Eden区,在新创建对象大于等于了-XX: PretenureSizeThreshold(仅对Serial和ParNew两款收集器有效)设置的值的对象直接分配在老年代。这样做是为了避免大对象来回在新生代的Eden区和两个Survivor复制,影响性能。

      虚拟机给每个对象一个年龄寄存器,对象在Eden区诞生,如果经过一个Minor GC后依然存活,并能放到Survivor区,并年龄加1,对象Survivor区中熬过一次 Minor GC,年加1,当它的年加到一定(认为15),会 晋升到老年代中。对象晋升老年代的年阈,可以过参-XX: MaxTenuringThreshold设置。 但并不是所有的对象到一定年龄才晋升到老年代。比如Survivor区中某一个年龄的所有对象的大小总和等于Survivor区的一半,那么所有大于等于该年龄的对象就会晋升到老年代。

Search

    Post Directory