JVM垃圾回收

视频链接:黑马程序员JVM完整教程,全网超高评价,全程干货不拖沓_哔哩哔哩_bilibili

跟着视频做的笔记

1 如何判断对象是否可以回收

1.1 引用计算法

当一个对象被引用时,就将引用对象值+1,当值为 0 时,就表示该对象不被引用,可以被垃圾收集器回收。

缺点:如下图所示,循环引用时,两个对象的计数都为1,导致两个对象都无法被释放。

1.2 可达性分析算法

  • JVM 中的垃圾回收器通过可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看能否沿着 GC Root 对象(根对象,无法被回收的对象)为起点的引用链找到该对象,如果找不到,则表示可以回收

  • 可以作为 GC Root 的对象

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。

    • 方法区中类静态属性引用的对象

    • 方法区中常量引用的对象

    • 本地方法栈中 JNI(即一般说的Native方法)引用的对象

案例

public static void main(String[] args) throws IOException {

        ArrayList<Object> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add(1);
        System.out.println(1);
        System.in.read();

        list = null;
        System.out.println(2);
        System.in.read();
        System.out.println("end");
    }

 对于以上代码,可以使用如下命令将堆内存信息转储成一个文件,然后使用 Eclipse Memory Analyzer 工具(eclipse自带)进行分析。

  • 第一步: 使用 jps 命令,查看程序的进程
  • 第二步:使用 jmap -dump:format=b,live,file=1.bin 16104 命令转储文件
    • dump:转储文件

    • format=b:二进制文件

    • file:文件名

    • 16104:进程的id

  • 第三步:打开 Eclipse Memory Analyzer 对 1.bin 文件进行分析。
    • 分析的 gc root,找到了 ArrayList 对象,然后将 list 置为null,再次转储,那么 list 对象就会被回收。

1.3 四种引用

图中实线表示强引用

  • 强引用

    • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

  • 软引用(SoftReference)

    • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象

    • 可以配合引用队列来释放软引用自身

  • 弱引用(WeakReference)

    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象

    • 可以配合引用队列来释放弱引用自身

  • 虚引用(PhantomReference)

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

  • 终结器引用(FinalReference)

    • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象。

1.3.1 使用强引用

package cn.itcast.jvm.t2;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软引用,内存设置如下:
 * -Xmx20m
 */
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();
        
    }
}

 

抛异常,堆内存不足,因为此方法中的 list 都是强引用

1.3.2 使用软引用

package cn.itcast.jvm.t2;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软引用
 * -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 {
        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());
        }
    }
}

在 list 集合中存放了 软引用对象,当内存不足时,会触发 full gc,将软引用的对象回收

运行结果如下:

"C:\Program Files\Java\jdk1.8.0_241\bin\java.exe" -Xmx20m -XX:+PrintGCDetails -verbose:gc "-javaagent:D:\Idea\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar=52354:D:\Idea\IntelliJ IDEA 2019.2.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;D:\Idea\Code\jvm\out\production\jvm;D:\Idea\Code\jvm\lib\jackson-core-2.3.3.jar;D:\Idea\Code\jvm\lib\jackson-databind-2.3.3.jar;D:\Idea\Code\jvm\lib\jackson-annotations-2.3.3.jar" cn.itcast.jvm.t2.Demo2_3
[B@7f31245a
1
[B@6d6f6e28
2
[B@135fbaa4
3
[GC (Allocation Failure) [PSYoungGen: 1944K->488K(6144K)] 14232K->12976K(19968K), 0.0068100 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]   //内存不足,触发垃圾回收,但是释放的内存不多
[B@45ee12a7
4
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17184K->17288K(19968K), 0.0012401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //内存不足,触发垃圾回收,但是释放的内存不多
[Full GC (Ergonomics) [PSYoungGen: 4696K->4539K(6144K)] [ParOldGen: 12592K->12485K(13824K)] 17288K->17025K(19968K), [Metaspace: 3235K->3235K(1056768K)], 0.0043466 secs] [Times: user=0.16 sys=0.00, real=0.00 secs] //内存不足,触发垃圾回收,但是释放的内存不多
[GC (Allocation Failure) --[PSYoungGen: 4539K->4539K(6144K)] 17025K->17097K(19968K), 0.0006277 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //内存不足,触发垃圾回收,但是释放的内存不多
[Full GC (Allocation Failure) [PSYoungGen: 4539K->0K(6144K)] [ParOldGen: 12557K->622K(8704K)] 17097K->622K(14848K), [Metaspace: 3235K->3235K(1056768K)], 0.0053192 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] //内存不足,触发full gc,将软引用的对象回收,所以下面的循环中前四个软引用对象为null,第五次循环内存足够,则加入到list中。
[B@330bedb4
5
循环结束:5
null
null
null
null
[B@330bedb4
Heap
 PSYoungGen      total 6144K, used 4264K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa2c0,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 8704K, used 622K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
  object space 8704K, 7% used [0x00000000fec00000,0x00000000fec9bb20,0x00000000ff480000)
 Metaspace       used 3242K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 351K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

1.3.3 软引用+队列

上面的代码中,当软引用引用的对象被回收了,但是软引用对象还存在,即4个null,还是会占用内存,所以,一般软引用需要搭配一个引用队列一起使用。

package cn.itcast.jvm.t2;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示软引用, 配合引用队列
 */
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());
        }

    }
}

运行结果如下:  

"C:\Program Files\Java\jdk1.8.0_241\bin\java.exe" -Xmx20m "-javaagent:D:\Idea\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar=52448:D:\Idea\IntelliJ IDEA 2019.2.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;D:\Idea\Code\jvm\out\production\jvm;D:\Idea\Code\jvm\lib\jackson-core-2.3.3.jar;D:\Idea\Code\jvm\lib\jackson-databind-2.3.3.jar;D:\Idea\Code\jvm\lib\jackson-annotations-2.3.3.jar" cn.itcast.jvm.t2.Demo2_4
[B@7f31245a
1
[B@6d6f6e28
2
[B@135fbaa4
3
[B@45ee12a7
4
[B@330bedb4
5
===========================
[B@330bedb4  //此时经过垃圾回收,就只剩下循环第五次的引用对象

Process finished with exit code 0

1.3.4 使用弱引用

package cn.itcast.jvm.t2;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示弱引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
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 < 10; 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());
    }
}

运行结果:  

"C:\Program Files\Java\jdk1.8.0_241\bin\java.exe" -Xmx20m -XX:+PrintGCDetails -verbose:gc "-javaagent:D:\Idea\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar=52490:D:\Idea\IntelliJ IDEA 2019.2.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;D:\Idea\Code\jvm\out\production\jvm;D:\Idea\Code\jvm\lib\jackson-core-2.3.3.jar;D:\Idea\Code\jvm\lib\jackson-databind-2.3.3.jar;D:\Idea\Code\jvm\lib\jackson-annotations-2.3.3.jar" cn.itcast.jvm.t2.Demo2_5
[B@7f31245a 
[B@7f31245a [B@6d6f6e28 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 
[GC (Allocation Failure) [PSYoungGen: 1941K->488K(6144K)] 14229K->12976K(19968K), 0.0010118 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 [B@45ee12a7 
[GC (Allocation Failure) [PSYoungGen: 4696K->496K(6144K)] 17184K->13088K(19968K), 0.0007972 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 null [B@330bedb4 //此时触发垃圾回收,第四次应用对象被垃圾回收,置为null
[GC (Allocation Failure) [PSYoungGen: 4704K->504K(6144K)] 17296K->13112K(19968K), 0.0006395 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 null null [B@2503dbd3 ///此时触发垃圾回收,第4、5次应用对象被垃圾回收,置为null
[GC (Allocation Failure) [PSYoungGen: 4711K->488K(6144K)] 17319K->13128K(19968K), 0.0003999 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 null null null [B@4b67cf4d //此时触发垃圾回收,第4、5、6次应用对象被垃圾回收,置为null
[GC (Allocation Failure) [PSYoungGen: 4694K->504K(6144K)] 17334K->13160K(19968K), 0.0004614 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7f31245a [B@6d6f6e28 [B@135fbaa4 null null null null [B@7ea987ac //此时触发垃圾回收,第4、5、6、7次应用对象被垃圾回收,置为null
[GC (Allocation Failure) --[PSYoungGen: 4710K->4710K(6144K)] 17366K->17374K(19968K), 0.0004966 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4710K->0K(6144K)] [ParOldGen: 12664K->641K(7680K)] 17374K->641K(13824K), [Metaspace: 3233K->3233K(1056768K)], 0.0056856 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
null null null null null null null null [B@12a3a380 //此时触发垃圾回收,第1、2、3、4、5、6、7、8次应用对象被垃圾回收,置为null
null null null null null null null null [B@12a3a380 [B@29453f44 第1、2、3、4、5、6、7、8次应用对象被垃圾回收,置为null
循环结束:10
Heap
 PSYoungGen      total 6144K, used 4319K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 76% used [0x00000000ff980000,0x00000000ffdb7cc8,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7680K, used 4737K [0x00000000fec00000, 0x00000000ff380000, 0x00000000ff980000)
  object space 7680K, 61% used [0x00000000fec00000,0x00000000ff0a0450,0x00000000ff380000)
 Metaspace       used 3240K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 350K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

1.3.5 弱引用+队列

用法如软引用+队列

2 垃圾回收算法

2.1 标记清除(Mark Sweep)

  • 速度较快

  • 会产生内存碎片

2.2 标记整理(Mark Compact)

  • 速度慢

  • 没有内存碎片

2.3 复制(Copy)

  • 不会有内存碎片

  • 需要占用两倍内存空间

实现步骤

  • 第一步:标记

  • 第二步:复制

  • 第三步:清空

  • 第四步:交换

3 分代垃圾回收

  • 新创建的对象首先分配在伊甸园(eden)区

  • 新生代空间不足时,触发 minor gc ,eden 区 和 from 区存活的对象使用 - copy 复制到 to 中,存活的对象年龄加一,然后交换 from to

  • minor gc(新生代发生的gc) 会引发 stop the world(暂停其他线程),等垃圾回收结束后,恢复用户线程运行

  • 当幸存区对象的寿命超过阈值时,会晋升到老年代,最大的寿命是 15(4bit:二进制1111,转换成十进制,即为15)

  • 当老年代空间不足时,会先触发 minor gc,如果空间仍然不足,那么就触发 full fc (老年代发生的gc),停止的时间更长!

3.1 JVM相关参数

晋升阈值:引用对象从新生代晋升到老年代

3.2 GC分析

通过代码,给 list 分配内存,来观察 新生代和老年代的情况,什么时候触发 minor gc,什么时候触发 full gc 等情况,使用前需要设置 jvm 参数。

package cn.itcast.jvm.t2;

import java.util.ArrayList;

/**
 *  演示内存的分配策略
 */
public class Demo2_1 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    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 {
        new Thread(() -> {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);
            list.add(new byte[_8MB]);
        }).start();

        System.out.println("sleep....");
        Thread.sleep(1000L);
    }
}

4 垃圾回收器

4.1 串行

  • 单线程

  • 堆内存较少,适合个人电脑

  • 开启单线程:
    • -XX:+UseSerialGC=serial + serialOld
  • 安全点:让其他线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象 因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态

  • Serial:新生代--->垃圾回收算法:复制

  • serialold:老年代--->垃圾回收算法:标记整理

4.2 吞吐量优先

  • 多线程

  • 堆内存较大,多核 cpu

  • 让单位时间内,STW 的时间最短 0.2 +0.2 = 0.4

-XX:+UseParallelGC ~ -XX:+UsePrallerOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio // 1/(1+radio)
-XX:MaxGCPauseMillis=ms // 200ms
-XX:ParallelGCThreads=n

4.3 响应时间优先

  • 多线程

  • 堆内存较大,多核 cpu

  • 尽可能让 STW 的单次时间最短 0.1+ 0.1 + 0.1+ 0.1+ 0.1 = 0.5

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark

4.4 CMS(垃圾回收工具)

CMS 收集器,Concurrent Mark Sweep,一种以获取最短回收停顿时间为目标的老年代收集器

特点:基于标记-清除算法实现。并发收集、低停顿,但是会产生内存碎片

应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如 web 程序、b/s 服务

CMS 收集器的运行过程分为下列4步:

  • 初始标记:标记 GC Roots 能直接到的对象。速度很快但是仍存在 Stop The World 问题。

  • 并发标记:进行 GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。

  • 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在 Stop The World 问题

  • 并发清除:对标记的对象进行清除回收,清除的过程中,可能任然会有新的垃圾产生,这些垃圾就叫浮动垃圾,如果当用户需要存入一个很大的对象时,新生代放不下去,老年代由于浮动垃圾过多,就会退化为 serial Old 收集器,将老年代垃圾进行标记-整理,当然这也是很耗费时间的!

CMS 收集器的内存回收过程是与用户线程一起并发执行的,可以搭配 ParNew 收集器(多线程,新生代,复制算法)与 Serial Old 收集器(单线程,老年代,标记-整理算法)使用。

4.5 G1(Garbage First)

垃圾回收工具,2017 JDK9默认,取代了GMS

  • 使用场景

    • 同时注重吞吐量和低延迟(响应时间)

    • 超大堆内存(内存大的),会将堆内存划分为多个大小相等的区域

    • 整体上是标记-整理算法,两个区域之间是复制算法

  • 相关参数: JDK8 并不是默认开启的,所需要参数开启,jdk9之后默认开启

    • -XX:+UseG1GC  //开启G1
      -XX:G1HeapRegionSize=size
      -XX:MaxGCPauseMillis=time

4.5.1 G1垃圾回收阶段

  • Young Collection:新生代回收,对新生代垃圾收集

  • Young Collection + Concurrent Mark(新生代回收+并发标记):如果老年代内存到达一定的阈值了,新生代垃圾收集同时会执行一些并发的标记。

  • Mixed Collection:会对新生代 + 老年代 + 幸存区等进行混合收集,然后收集结束,会重新进入新生代收集。

4.5.2 Young Collection(新生代回收)

新生代存在 STW:分代是按对象的生命周期划分,分区则是将堆空间划分连续几个不同小区间,每一个小区间独立回收,可以控制一次回收多少个小区间,方便控制 GC 产生的停顿时间!

E:eden(伊甸园区),S:幸存区,O:老年代 新生代收集会产生 STW !

4.5.3 Young Collection + CM(并发标记)

Young Collection + CM:新生代回收+并发标记

  • 在 Young GC 时会进行 GC Root 的初始化标记

  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的

  • JVM 参数决定 -XX:InitiatingHeapOccupancyPercent=percent (默认45%)

  • E:eden(伊甸园区),S:幸存区,O:老年代

4.5.4 Mixed Collection(混合收集)

会对 E S O 进行全面的回收

  • 最终标记会 STW

  • 拷贝存活会 STW

  • -XX:MaxGCPauseMills=xxms 用于指定最长的停顿时间!

    • 问:为什么有的老年代被拷贝了,有的没拷贝?

    • 因为指定了最大停顿时间,如果对所有老年代都进行回收,耗时可能过高。为了保证时间不超过设定的停顿时间,会回收最有价值的老年代(回收后,能够得到更多内存)

4.5.5 Full GC

  • SerialGC:串行垃圾收集器

    • 新生代内存不足发生的垃圾收集- minor gc

    • 老年代内存不足发生的垃圾收集- full gc

  • ParallelGC:并行垃圾收集器

    • 新生代内存不足发生的垃圾收集- minor gc

    • 老年代内存不足发生的垃圾收集- full gc

  • CMS

    • 新生代内存不足发生的垃圾收集- minor gc

    • 老年代内存不足
  • G1

    • 新生代内存不足发生的垃圾收集- minor gc

    • 老年代内存不足

4.5.6 Young Collection 跨代引用

新生代回收的跨代引用(老年代引用新生代)问题

  • 卡表 与 Remembered Set

    • Remembered Set 存在于E中,用于保存新生代对象对应的脏卡

    • 脏卡:O 被划分为多个区域(一个区域512K),如果该区域引用了新生代对象,则该区域被称为脏卡

  • 在引用变更时通过 post-write barried + dirty card queue

  • concurrent refinement threads 更新 Remembered Set

 4.5.7 remark(重标记)

重新标记阶段
在垃圾回收时,收集器处理对象的过程中

下图中:

  • 黑色:已被处理,需要保留的
  • 灰色:正在处理中的
  • 白色:还未处理的

多线程可能导致的问题,即remark的实现过程

 

问题:

  • 如上图:在并发标记过程中,C未被引用,标记为白色,代表应当被垃圾回收。

  • 如下图:但是整个标记过程还未结束之时,某用户又将C被A引用,A是黑色的,所以C应该保留,而不是被垃圾回收。

  • 二者相互矛盾,所以就诞生了remark。

remark 使用过程如下

  • C 未被引用,这时 A 引用了 C ,就会给 C 加一个写屏障,写屏障(pre-write barrier)的指令会被执行,将 C 放入一个队列(satb_mark_queue)当中,并将 C 变为 处理中状态

  • 在并发标记阶段结束以后,重新标记阶段会 STW ,然后将放在该队列中的对象重新处理,发现有强引用引用它,就会处理它,由灰色变成黑色。

4.5.8 JDK 8u20版本字符串去重

  • 将所有新分配的字符串(底层是 char[] )放入一个队列

  • 当新生代回收时,G1 并发检查是否有重复的字符串

  • 如果字符串的值一样,就让他们引用同一个字符串对象

  • 注意,其与 String.intern() 的区别

    • String.intern() 关注的是字符串对象

    • 字符串去重关注的是 char[]

    • 在 JVM 内部,使用了不同的字符串标

优点与缺点

  • 节省了大量内存

  • 新生代回收时间略微增加,导致略微多占用 CPU

4.5.9 JDK 8u40版本并发标记类卸载

在并发标记阶段结束以后,就能知道哪些类不再被使用。如果一个类加载器的所有类都不在使用,则卸载它所加载的所有类。默认开启

-XX:+ClassUnloadingwithConcurrentMark 默认启用

4.5.10 JDK 8u60 回收巨型对象

  • 一个对象大于region的一半时,就称为巨型对象

  • G1不会对巨型对象进行拷贝

  • 回收时被优先考虑

  • G1会跟踪老年代所有incoming引用,如果老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉

4.5.11 JDK 9 并发标记起始时间的调整

  • 并发标记必须在堆空间占满前完成,否则退化为 FulGC

  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent

  • JDK 9 可以动态调整

    • -XX:InitiatingHeapOccupancyPercent 用来设置初始值

    • 进行数据采样并动态调整

    • 总会添加一个安全的空挡空间

5 垃圾回收调优

查看虚拟机参数命令:前面是jdk路径

"C:\Program Files\J
ava\jdk1.8.0_241\bin\java" -XX:+PrintFlagsFinal -version | findstr "GC"

5.1 调优领域

  • 内存

  • 锁竞争

  • cpu 占用

  • io

  • gc

5.2 确定目标

低延迟【互联网项目】还是高吞吐量【科学运算应用】? 选择合适的回收器GC

  • 低延迟,高响应时间

    • CMS:广泛使用,但是JDK9之后不推荐使用,被G1代替

    • G1 :适合管理超大内存

    • ZGC:超低延迟

  • 高吞吐量

    • ParallelGC

5.3 最快的GC是不发生GC

首先排除减少因为自身编写的代码而引发的内存问题

  • 查看 Full GC 前后的内存占用,考虑以下几个问题

    • 数据是不是太多?

    • resultSet = statement.executeQuery(“select * from 大表 limit n”)

  • 数据表示是否太臃肿

    • 对象图

    • 对象大小 16 Integer 24 int 4

  • 是否存在内存泄漏

    • static Map map =

    • 软引用

    • 弱引用

    • 第三方缓存实现

5.4 新生代调优

  • 新生代的特点

    • 所有的 new 操作分配内存都是非常廉价的

      • TLAB thread-lcoal allocation buffer

    • 死亡对象回收零代价

    • 大部分对象用过即死(朝生夕死)

    • Minor GC 所用时间远小于 Full GC

  • 新生代内存越大越好么?

    • 不是

    • 新生代内存太小:频繁触发 Minor GC ,会 STW ,会使得吞吐量下降

    • 新生代内存太大:老年代内存占比有所降低,会更频繁地触发 Full GC。而且触发 Minor GC 时,清理新生代所花费的时间会更长

    • 新生代内存设置为内容纳[并发量*(请求-响应)]的数据为宜

  • 幸存区需要能够保存 【当前活跃对象+需要晋升的对象】

  • 晋升阈值配置得当,让长时间存活的对象尽快晋升

-XX:MaxTenuringThreshold=threshold
-XX:+PrintTenuringDistrubution

5.5 老年代调优

以 CMS 为例:

  • CMS 的老年代内存越大越好

  • 先尝试不做调优,如果没有 Full GC 那么已经,否者先尝试调优新生代。

  • 观察发现 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3

-XX:CMSInitiatingOccupancyFraction=percent

5.6 案例

案例1:Full GC 和 Minor GC 频繁

案例2:请求高峰期发生 Full GC,单次暂停时间特别长(CMS)

案例3:老年代充裕情况下,发生 Full GC(jdk1.7)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值