JVM系列笔记--垃圾回收

一、对象回收

引用计数法

  • 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的
    public class ReferenceCoutingGC {
          
          
        public Object instance = null;
    
        private static final int _1MB = 1024*1024;
    
        private byte[] bigSize = new byte[2 * _1MB];
    
        public static void testGC(){
          
          
            ReferenceCoutingGC objA = new ReferenceCoutingGC();
            ReferenceCoutingGC objB = new ReferenceCoutingGC();
    
            objA.instance = objB;
            objB.instance = objA;
    
            objA = null;
            objB = null;
    
            System.gc();
        }
    
        public static void main(String[] args) {
          
          
            testGC();
        }
    
    }
    
    在这里插入图片描述
  • 意味着虚拟机并没有因为这两个对象互相引用就放弃回收它们,这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。

可达性分析算法

  • 如果从GC Roots到某个对象是不可达的,则证明该对象不可能再被使用
    在这里插入图片描述
  • 固定可作为GC Roots的对象有:
    1. 在方法区中类静态属性引用的对象
    2. 在方法区中常量引用的对象。如字符串常量池里的引用
    3. 在本地方法栈中引用的对象
    4. Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepitonOutOfMemoryError)等,还有系统类加载器
    5. 所有被同步锁(synchronzied关键字)所持有的对象
    6. 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

二、四种引用

在这里插入图片描述

强引用

  • 指代码间的引用赋值(Object obj = new Object()),只有所有的GC Roots对象都不引用该对象,才能被垃圾回收

软引用

  • 软引用用来描述一些还有用但非必须的对象
  • 只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常
    //-Xmx20m -XX:+PrintGCDetails -verbose:gc
    public class Demo2_3 {
          
          
    
        private static final int _4MB = 4 * 1024 * 1024;
    
        public static void main(String[] args) throws IOException {
          
          
        	//没有使用软引用对象
            /*List<byte[]> list = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
                list.add(new byte[_4MB]);
            }
    
            System.in.read();*/
            
            soft();
        }
    
        public static void soft() {
          
          
            // list -->(强) SoftReference -->(软) byte[]
    
            List<SoftReference<byte[]>> list = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
          
          
            	//构造软引用对象
                SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
                System.out.println(ref.get());
                list.add(ref);
                System.out.println(list.size());
    
            }
            System.out.println("循环结束:" + list.size());
            for (SoftReference<byte[]> ref : list) {
          
          
                System.out.println(ref.get());
            }
        }
    }
    
    //运行结果
    [B@45ee12a7
    1
    [B@330bedb4
    2
    [B@2503dbd3
    3
    //第三次循环结束时,内存已经紧张,触发一次新生代回收
    [GC (Allocation Failure) [PSYoungGen: 2351K->488K(6144K)] 14639K->13130K(19968K), 0.0022554 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [B@4b67cf4d
    4
    //一次新生代回收仍不够,触发全面回收
    [GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17338K->17338K(19968K), 0.0022631 secs] [Times: user=0.05 sys=0.00, real=0.00 secs] 
    [Full GC (Ergonomics) [PSYoungGen: 4696K->4508K(6144K)] [ParOldGen: 12642K->12612K(13824K)] 17338K->17121K(19968K), [Metaspace: 3513K->3513K(1056768K)], 0.0058797 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    //仍内存不够,触发软引用回收
    [GC (Allocation Failure) --[PSYoungGen: 4508K->4508K(6144K)] 17121K->17121K(19968K), 0.0007782 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [Full GC (Allocation Failure) [PSYoungGen: 4508K->0K(6144K)] [ParOldGen: 12612K->719K(8704K)] 17121K->719K(14848K), [Metaspace: 3513K->3513K(1056768K)], 0.0078229 secs] [Times: user=0.00 sys=0.02, real=0.01 secs] 
    [B@7ea987ac
    5
    循环结束:5
    null
    null
    null
    null
    [B@7ea987ac
    //null值即为软引用,占用内存,可配合引用队列释放
    Heap
     PSYoungGen      total 6144K, used 4264K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
      eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa100,0x00000000fff00000)
      from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
      to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
     ParOldGen       total 8704K, used 719K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
      object space 8704K, 8% used [0x00000000fec00000,0x00000000fecb3c48,0x00000000ff480000)
     Metaspace       used 3520K, capacity 4506K, committed 4864K, reserved 1056768K
      class space    used 391K, capacity 394K, committed 512K, reserved 1048576K
    
    Process finished with exit code 0
    
  • 软引用本身也会占用少量内存,可配合引用队列来释放软引用本身
    public class Demo2_4 {
          
          
        private static final int _4MB = 4 * 1024 * 1024;
    
        public static void main(String[] args) {
          
          
            List<SoftReference<byte[]>> list = new ArrayList<>();
    
            // 引用队列
            ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
    
            for (int i = 0; i < 5; i++) {
          
          
                // 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
                SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
                System.out.println(ref.get());
                list.add(ref);
                System.out.println(list.size());
            }
    
            // 从队列中获取无用的 软引用对象,并移除
            Reference<? extends byte[]> poll = queue.poll();
            while( poll != null) {
          
          
                list.remove(poll);
                poll = queue.poll();
            }
    
            System.out.println("===========================");
            for (SoftReference<byte[]> reference : list) {
          
          
                System.out.println(reference.get());
            }
        }
    }
    
    [B@45ee12a7
    1
    [B@330bedb4
    2
    [B@2503dbd3
    3
    [B@4b67cf4d
    4
    [B@7ea987ac
    5
    ===========================
    [B@7ea987ac
    

弱引用

  • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    public class Demo2_5 {
          
          
        private static final int _4MB = 4 * 1024 * 1024;
    
        public static void main(String[] args) {
          
          
            //  list --> WeakReference --> byte[]
            List<WeakReference<byte[]>> list = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
          
          
                WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
                list.add(ref);
                for (WeakReference<byte[]> w : list) {
          
          
                    System.out.print(w.get()+" ");
                }
                System.out.println();
    
            }
            System.out.println("循环结束:" + list.size());
        }
    }
    
    [B@45ee12a7 
    [B@45ee12a7 [B@330bedb4 
    [B@45ee12a7 [B@330bedb4 [B@2503dbd3 
    [GC (Allocation Failure) [PSYoungGen: 2351K->488K(6144K)] 14639K->13062K(19968K), 0.0015034 secs] [Times: user=0.06 sys=0.02, real=0.00 secs] 
    [B@45ee12a7 [B@330bedb4 [B@2503dbd3 [B@4b67cf4d 
    [GC (Allocation Failure) [PSYoungGen: 4696K->504K(6144K)] 17270K->13102K(19968K), 0.0012889 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [B@45ee12a7 [B@330bedb4 [B@2503dbd3 null [B@7ea987ac //回收第四个,才放得下第五个
    循环结束:5
    Heap
     PSYoungGen      total 6144K, used 4768K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
      eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa130,0x00000000fff00000)
      from space 512K, 98% used [0x00000000fff80000,0x00000000ffffe010,0x0000000100000000)
      to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
     ParOldGen       total 13824K, used 12598K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
      object space 13824K, 91% used [0x00000000fec00000,0x00000000ff84d8d0,0x00000000ff980000)
     Metaspace       used 3518K, capacity 4504K, committed 4864K, reserved 1056768K
      class space    used 390K, capacity 392K, committed 512K, reserved 1048576K
    
    Process finished with exit code 0
    

虚引用

  • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

三、垃圾回收算法

标记-清除算法

  • 首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象
    在这里插入图片描述
  • 缺点:会导致内存空间碎片化

标记-复制算法

  • 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
    在这里插入图片描述

标记-整理算法

  • 让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存
    在这里插入图片描述

四、分代垃圾回收

在这里插入图片描述

  • 分代回收过程:

    1. 对象在新生代Eden区中分配
    2. 新生代内存不足,会触发minor gc,Eden区和From幸存区存活的对象使用copy复制到To幸存区,存活对象年龄加一,并且交换From区和To区
    3. 当对象寿命超过阈值,会晋升至老年代,最大寿命15(4bit)
    4. 当老年代空间不足,先触发minor gc,若空间仍不足,触发full gc,最后空间若仍不足,报错OutOfMemoryError
    5. minor gc 会引发STW(stop the world),暂停其他用户线程,等垃圾回收结束,用户线程才恢复运行
  • 大对象直接进入老年代

    public class Demo2_1 {
          
          
        private static final int _8MB = 8 * 1024 * 1024;
    
        // -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
        public static void main(String[] args) throws InterruptedException {
          
          
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);
            list.add(new byte[_8MB]);
        }
    }
    

    在这里插入图片描述
    可以看到新生代中几乎没有内存占用,老年代占用了8MB,然而集合中加了两个8MB,最后一个加不进去,所以报了OutOfMemoryError错误

  • VM参数

    含义 参数
    堆初始大小 -Xms
    堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
    新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
    幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
    幸存区比例 -XX:SurvivorRatio=ratio
    晋升阈值 -XX:MaxTenuringThreshold=threshold
    晋升详情 -XX:+PrintTenuringDistribution
    GC详情 -XX:+PrintGCDetails -verbose:gc
    FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC

    幸存区中的ratio,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10

    在这里插入图片描述

五、垃圾回收器

串行

  • 单线程垃圾回收器,堆内存较小
  • -XX:+UseSerialGC = Serial + SerialOld,新生代回收器Serial采用标记-复制算法,老年代回收器SerialOld采用标记-整理算法
    在这里插入图片描述

吞吐量优先

  • Parallel Scavenge新生代回收器与Parallel Old老年代回收器配合使用
  • 吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间(运行代码与垃圾回收之和)的比值
    在这里插入图片描述
    参数 含义
    -XX:+UseParallelGC ~ -XX:+UseParallelOldGC 开关
    -XX:GCTimeRatio=ratio 垃圾收集时间占总时间的比率:1/(1+ratio),默认ratio为99
    -XX:MaxGCPauseMillis 最大垃圾回收停顿时间
    -XX:+UseAdaptiveSizePolicy 自适应调整策略
    -XX:ParallelGCThreads=n 指定并发线程数目

响应时间优先

  • 新生代ParNew和老年代CMS配合使用,当CMS并发失败,则将多线程退化到单线程,使用SerialOld
  • 初始标记:仅仅标记以下GC Roots能直接关联到的对象,速度很快
  • 并发标记:从GC Roots的直接关联对象开始遍历整个对象图,耗时较长但不需要停顿用户线程,可与垃圾回收线程并发运行
  • 重新标记:为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那部分对象的标记记录
  • 并发清除:清理删除掉标记阶段判断的已经死亡的对象,可与用户线程并发执行
    在这里插入图片描述

六、Garbage First垃圾回收器

  • G1可以面向堆内存任何部分来组成回收集,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式

回收过程

在这里插入图片描述

  • Young Collection:新生代垃圾收集,会STW
    在这里插入图片描述
  • Young Collection + CM:在 Young GC 时会进行 GC Root 的初始标记,老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW)
    在这里插入图片描述
  • Mixed Collection:对E、S、O进行全面垃圾回收
    在这里插入图片描述

跨代引用

  • 在寻找GC Roots过程中,老年代中有很多对象,查找效率低
  • 利用卡表技术,将老年代分为一个个的卡,当该卡引用了新生代则将其标记为脏卡
  • 每次引用更新时,执行写屏障,将该操作加入到队列中,异步执行

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44863537/article/details/109500870