zoukankan      html  css  js  c++  java
  • [深入理解Java虚拟机]<垃圾收集器与内存分配策略>

    Overview

    • 垃圾收集考虑三件事:
      1. 哪些内存需要回收?
      2. 什么时候回收?
      3. 如何回收?
    • 重点考虑Java堆中动态分配和回收的内存。

    Is Object alive?

    引用计数法

    • 给对象添加一个引用计数器。
    • 该方法实现简单,判定效率高。但是它很难解决对象之间相互循环引用的问题,因此几乎很少有JVM选用该方法。eg:
      public class ReferenceCountingGC {
        public Object instance = null;
        // 占点内存,以便在GC日志中看清楚是否被回收过
        private static final int _1MB = 1024 * 1024;
        private byte[] bigSize = new byte[2 * _1MB];
        
        public static void testGC() {
          ReferenceCountingGC objA = new ReferenceCountingGC();
          ReferenceCountingGC objB = new ReferenceCountingGC();
          objA.instance = objB;
          objB.instance = objA;
          objA = null;
          objB = null;
         
          System.gc();
        }
      }

    可达性分析算法

    • 在主流商用程序语言(Java, C#, Lisp)的主流实现中,都是通过可达性分析(Reachability Analysis)来判断对象是否存活的。
    • 该算法的基本思路是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索。搜索的路径称为引用链(Reference Chain)。
    • 当一个对象到GC Roots没有任何引用链相连(即不可达)时,则该对象是不可用的。不可达的对象是可回收的。如下:
    • 在Java中,可作为GC Roots的对象包括下面几种:
      • 虚拟机栈(栈帧中的本地变量表)中的引用;
      • 方法区中类静态属性引用的对象;
      • 方法区中常量引用的对象;
      • 本地方法栈中JNI引用的对象。

    再谈引用

    • 上面两种方法都是通过“引用”来判定对象是否alive。
    • 但是在JDK1.2之前,Java中的引用定义很传统:若reference类型的数据中存储的数值是另一块内存的起始地址,则称这块内存代表着一个引用。
    • 但是考虑这样的场景:我们希望描述这样的一类对象,当内存空间足够时,则能保留在内存中,否则可抛弃。 <-- 很多系统的缓存功能都符合这样的应用场景。
    • 于是在JDK1.2之后,Java扩充了引用的概念:将引用分为强引用(Strong Reference), 软引用(Soft Reference), 弱引用(Weak Reference), 虚引用(Phantom Reference) 4种,强度依次减弱。
      • 强引用:最常见的,类似Object obj = new Object()
      • 软引用:用以描述一些还有用但是非必须的对象。对于该类对象,系统在将要发生内存溢出异常之前,会回收这些对象。JDK1.2之后提供了SoftReference类
      • 弱引用:也是用以描述非必须对象,但强度更弱于软引用。被弱引用关联的对象只能生存到下一次GC之前,而无论当前内存是否足够。
      • 虚引用:PhantomReference类。

    生存还是死亡

    • 即使是在可达性分析算法中不可达的对象,也并非“非死不可”的。
    • 要真正宣告一个对象死亡,至少要经历两次标记过程:TBD...

    回收方法区

    • 一般来说,在方法区进行垃圾收集的"性价比"比较低:在堆中,尤其是新生代中,常规应用一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。
    • 永久代的垃圾收集主要回收两部分:废弃常量和无用的类
    • 回收废弃常量与回收Java堆中的对象非常相似。假设一个字符串"abc"已经进入常量池,但当前系统中没有任何一个String对象引用常量池中的"abc"常量,则"abc"常量就会被系统清理出常量池。
    • 相比之下判定一个类是否是“无用的类”的条件则相对苛刻很多。需同时满足:
      • 该类所有的实例已经被回收;
      • 加载该类的ClassLoader已经被回收;
      • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

    垃圾收集算法

    标记-清除算法

    • 算法分为“标记”和“清除”两个阶段。
    • 该算法是后续收集算法的基础。
    • 不足:
      • 效率问题:标记和清除两个过程的效率都不高;
      • 空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次GC。

      

    复制算法

    • 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
    • 当使用的块用完时,就将还存活着的对象复制到另一块上面,然后再将已使用过的内存空间一次清理掉。
    • 这样使得每次都是对整个半区进行内存回收,内存分配时也不用考虑内存碎片等情况,只用移动堆顶指针,顺序分配即可
    • 代价是内存缩小为原来的一半。
    • 现在的商业JVM都采用这种收集算法。IBM的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例划分内存空间。而是将内存分为一块较大的Eden空间和两块较小的Survivor空间。

    标记-整理算法

    • 复制收集算法在对象存活率较高时就需要较多的复制操作,效率将会变低。更关键的是,若不想浪费一般的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以老年代一般不能采用这种算法。
    • 标记-整理算法:标记过程仍同于标记-清除算法。但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端外边的内存。

    分代收集算法

    • 当前商业JVM的GC都采用“分代收集”(Generational Collection)算法。
    • 这种算法就是根据对象存货周期地不同将内存划分为几块。
    • 一般是把Java堆分为新声代和老生代,从而根据各个年代的特点采用最适当的收集算法。

    Summary

    • 本篇主要覆盖了GC的两大问题:
      • 哪些对象需要回收:最基础的是引用计数法,简单但无法解决循环引用问题。在此基础上,更常用的是可达性分析算法。
      • 如何回收:最基础的是标记-清除法,即先标记出哪些对象需要回收,然后逐一清除,不足是会产生大量内存碎片,从而导致后续可能触发多次GC。此外更高效的是复制法,该方法确保了连续内存,但是将内存缩小至原内存一般,并且在对象存活率较高时会引入大量复制操作,效率降低。还有标记-整理算法。 另外,商业JVM都会采用分代收集算法,即将内存划分为几块,根据年代选取合适的收集算法。
  • 相关阅读:
    部署 AppGlobalResources 到 SharePoint 2010
    还原一个已删除的网站集
    使用仪表板设计器配置级联筛选器 (SharePoint Server 2010 SP1)
    File or arguments not valid for site template
    Pex and Moles Documentation
    Content Query Webpart匿名访问
    Running Moles using NUnit Console from Visual Studio
    Calling a WCF Service using jQuery in SharePoint the correct way
    Updating Content Types and Site Columns That Were Deployed as a Feature
    asp.net中判断传过来的字符串不为空的代码
  • 原文地址:https://www.cnblogs.com/wttttt/p/7290932.html
Copyright © 2011-2022 走看看