java基础-13 jvm垃圾回收机制

 jvm垃圾回收机制

1. 如何判断对象是否可以被回收或者说如何判断对象是否是垃圾?

  • 引用计数算法(Reference Counting)  早期策略
  • 可达性分析算法(Rearchability Analysis)

2. 垃圾回收算法有哪些?

  • 标记-清除(Mark-Sweep)算法:两个阶段:标记阶段和清除阶段。
  • 标记-整理算法:应用在老年代。
  • 复制算法

3. 分代垃圾回收

  • 分代收集算法(Generational Collection)是融合上述 3 种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。

4. 垃圾回收器

分类:

  • 串行
  • 吞吐量优先
  • 响应时间优先

5. 垃圾回收调优

  • 垃圾回收机制的意义

java  语言中一个显著的特点就是引入了java回收机制,是c++程序员最头疼的内存管理的问题迎刃而解,它使得java程序员在编写程序的时候不在考虑内存管理。由于有个垃圾回收机制,java中的额对象不在有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存;

  •  内存泄露:指该内存空间使用完毕后未回收,在不涉及复杂数据结构的一般情况下,java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有是也将其称为“对象游离”;

 

 

  • 如何判断对象可以回收

引用计数法(引用计数法是垃圾收集器中的早期策略。现在已不再采用该方法)

引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象内存磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法。

未经优化的引用计数相比跟踪式垃圾回收有两个主要缺点,都需要引入附加机制予以修复:

  • 频繁更新引用计数会降低运行效率。

  • 原始的引用计数无法解决循环引用问题。

 

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。

缺点: 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

引用计数算法无法解决循环引用问题,例如:

1

2

3

4

5

6

7

8

9

10

11

12

public class Main {

    public static void main(String[] args) {

        MyObject object1 = new MyObject();

        MyObject object2 = new MyObject();

          

        object1.object = object2;

        object2.object = object1;

          

        object1 = null;

        object2 = null;

    }

}

最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。

 

  • 可达性分析算法(Rearchability Analysis

  1. Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  2. 扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以 回收
  3. 哪些对象可以作为 GC Root ?

常说的GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象。

一个对象可以属于多个root,GC root有以下几种:

  • Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots,.
  • SystemClass:java的系统核心类
  • Natice Stack:操作系统本地方法引用的对象
  • Thread :活动的线程
  • Busy Monitor-:被锁定的对象(被同步锁枷锁的对象)

 

  • 四种引用

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

2. 软引用(SoftReference) 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用 对象 可以配合引用队列来释放软引用自身

3. 弱引用(WeakReference) 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象 可以配合引用队列来释放弱引用自身

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

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

 

  • 案例

将堆内存设置为20M,强引用抛出异常

/**
 * 演示软引用
 * -Xmx20m
 */
public class Demo2_3 {

    private static final int _4MB = 4 * 1024 * 1024;



    public static void main(String[] args) throws IOException {
        //强引用
        strong();
    }

    public static void strong() throws IOException{
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }
}

/*
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at cn.itcast.jvm.t2.Demo2_3.strong(Demo2_3.java:28)
	at cn.itcast.jvm.t2.Demo2_3.main(Demo2_3.java:20)

Process finished with exit code 1
*/

将堆内存设置为20M,软引用正常运行,前四个软引用对象被回收

/**
 * 演示软引用
 * -Xmx20m
 */
public class Demo2_3 {

    private static final int _4MB = 4 * 1024 * 1024;



    public static void main(String[] args) throws IOException {
        //强引用
        //strong();
        //软引用
        soft();
    }

    public static void strong() throws IOException{
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }

    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@1540e19d
1
[B@677327b6
2
[B@14ae5a5
3
[B@7f31245a
4
[B@6d6f6e28
5
循环结束:5
null
null
null
null
[B@6d6f6e28

Process finished with exit code 0
*/

回收过程

        第三次循环完成后执行了一次垃圾回收 新生代内存减小:PSYoungGen: 2168K->504K(6144K),第四次循环完成之后又执行了一次垃圾回收,但是效果不明显,又执行了一次更全面的垃圾回收:Full GC,内存依然不够,触发软引用垃圾回收:在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用 对象

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

    public static void strong() throws IOException{
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }

    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@1540e19d
1
[B@677327b6
2
[B@14ae5a5
3
[GC (Allocation Failure) [PSYoungGen: 2168K->504K(6144K)] 14456K->13012K(19968K), 0.0006794 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@7f31245a
4
[GC (Allocation Failure) --[PSYoungGen: 4712K->4712K(6144K)] 17220K->17332K(19968K), 0.0006657 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4712K->4475K(6144K)] [ParOldGen: 12620K->12568K(13824K)] 17332K->17044K(19968K), [Metaspace: 3482K->3482K(1056768K)], 0.0044557 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 4475K->4475K(6144K)] 17044K->17068K(19968K), 0.0012415 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 4475K->0K(6144K)] [ParOldGen: 12592K->641K(8704K)] 17068K->641K(14848K), [Metaspace: 3482K->3482K(1056768K)], 0.0052074 secs] [Times: user=0.06 sys=0.05, real=0.00 secs] 
[B@6d6f6e28
5
循环结束:5
null
null
null
null
[B@6d6f6e28
Heap
 PSYoungGen      total 6144K, used 4264K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa088,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 8704K, used 641K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
  object space 8704K, 7% used [0x00000000fec00000,0x00000000feca0530,0x00000000ff480000)
 Metaspace       used 3489K, capacity 4502K, committed 4864K, reserved 1056768K
  class space    used 388K, capacity 390K, committed 512K, reserved 1048576K

Process finished with exit code 0
*/

 

  • 对软引用本身做清理:配合引用队列来释放软引用自身

/**
 * 演示软引用, 配合引用队列
 * -Xmx20m
 */
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@1540e19d
1
[B@677327b6
2
[B@14ae5a5
3
[B@7f31245a
4
[B@6d6f6e28
5
===========================
[B@6d6f6e28
*/

 

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


/*
[B@1540e19d 
[B@1540e19d [B@677327b6 
[B@1540e19d [B@677327b6 [B@14ae5a5 
[GC (Allocation Failure) [PSYoungGen: 2168K->488K(6144K)] 14456K->12976K(19968K), 0.0005762 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1540e19d [B@677327b6 [B@14ae5a5 [B@7f31245a 
[GC (Allocation Failure) [PSYoungGen: 4696K->480K(6144K)] 17184K->13080K(19968K), 0.0005539 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1540e19d [B@677327b6 [B@14ae5a5 null [B@6d6f6e28 
[GC (Allocation Failure) [PSYoungGen: 4687K->504K(6144K)] 17287K->13104K(19968K), 0.0003626 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1540e19d [B@677327b6 [B@14ae5a5 null null [B@135fbaa4 
[GC (Allocation Failure) [PSYoungGen: 4710K->488K(6144K)] 17310K->13136K(19968K), 0.0004311 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1540e19d [B@677327b6 [B@14ae5a5 null null null [B@45ee12a7 
[GC (Allocation Failure) [PSYoungGen: 4694K->488K(6144K)] 17342K->13136K(19968K), 0.0003491 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1540e19d [B@677327b6 [B@14ae5a5 null null null null [B@330bedb4 
[GC (Allocation Failure) [PSYoungGen: 4694K->472K(5120K)] 17342K->13120K(18944K), 0.0004430 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[B@1540e19d [B@677327b6 [B@14ae5a5 null null null null null [B@2503dbd3 
[GC (Allocation Failure) [PSYoungGen: 4658K->32K(5632K)] 17306K->13144K(19456K), 0.0003784 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(5632K)] [ParOldGen: 13112K->660K(8704K)] 13144K->660K(14336K), [Metaspace: 3481K->3481K(1056768K)], 0.0056348 secs] [Times: user=0.14 sys=0.00, real=0.01 secs] 
null null null null null null null null null [B@4b67cf4d 
循环结束:10
Heap
 PSYoungGen      total 5632K, used 4278K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
  eden space 4608K, 92% used [0x00000000ff980000,0x00000000ffdadae8,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 8704K, used 660K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
  object space 8704K, 7% used [0x00000000fec00000,0x00000000feca5018,0x00000000ff480000)
 Metaspace       used 3489K, capacity 4502K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

Process finished with exit code 0
*/

弱引用本身也会占用一定的内存,当添第十个数组时,前三个也被回收,弱引用本身做清理可参考软引用

  • 垃圾回收算法

  1. 标记清除
  2. 标记整理
  3. 复制
  • 标记清除

定义: Mark Sweep

标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如上图所示。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

 

  • 标记整理

定义:Mark Compact

标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

  • 复制

定义:Copy

不会有内存碎片

需要占用双倍内存空间

 该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行。

  • 分代垃圾回收

  • 对象首先分配在伊甸园区域
  • 新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的 对象年龄加 1并且交换 from to
  • minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
  • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
  • 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时 间更长

注意:当对象比较大时,会直接晋升为老年代,例如当新生代的内存不足以存放大对象时,该对象会直接放入老年代

设置一下参数: -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc 生成一个 8M的对象,会被直接放入老年代

/**
 *  演示内存的分配策略
 * -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
 */

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:+PrintGCDetails -verbose:gc
    public static void main(String[] args) throws InterruptedException {
        ArrayList<byte[]> lists = new ArrayList<>();
        lists.add(new byte[_8MB]);
    }
}

 

 

  • 相关 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

 当存入一个7M的对象时触发了一次垃圾回收

/**
 *  演示内存的分配策略
 * -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc
 */

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:+PrintGCDetails -verbose:gc
    public static void main(String[] args) throws InterruptedException {
        ArrayList<byte[]> lists = new ArrayList<>();
        lists.add(new byte[_7MB]);
        //lists.add(new byte[_512KB]);
        //lists.add(new byte[_512KB]);
    }
}

/*
[GC (Allocation Failure) [DefNew: 2179K->633K(9216K), 0.0012630 secs] 2179K->633K(19456K), 0.0012976 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 8211K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  92% used [0x00000000fec00000, 0x00000000ff366848, 0x00000000ff400000)
  from space 1024K,  61% used [0x00000000ff500000, 0x00000000ff59e468, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
 Metaspace       used 3485K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

Process finished with exit code 0
*/

 当再存入两个512KB的对象时,大对象被移至老年代

/**
 *  演示内存的分配策略
 * -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -verbose:gc
 */

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:+PrintGCDetails -verbose:gc
    public static void main(String[] args) throws InterruptedException {
        ArrayList<byte[]> lists = new ArrayList<>();
        lists.add(new byte[_7MB]);
        lists.add(new byte[_512KB]);
        lists.add(new byte[_512KB]);
    }
}

/*
[GC (Allocation Failure) [DefNew: 2179K->631K(9216K), 0.0011348 secs] 2179K->631K(19456K), 0.0011675 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 8639K->540K(9216K), 0.0042637 secs] 8639K->8336K(19456K), 0.0042848 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 1134K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,   7% used [0x00000000fec00000, 0x00000000fec94930, 0x00000000ff400000)
  from space 1024K,  52% used [0x00000000ff400000, 0x00000000ff4872a8, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 7795K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  76% used [0x00000000ff600000, 0x00000000ffd9cd88, 0x00000000ffd9ce00, 0x0000000100000000)
 Metaspace       used 3486K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K

Process finished with exit code 0
*/

 

注意:当子线程堆内存溢出时,主线程依旧可以正常运行

/**
 *  演示内存的分配策略
 * -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
 */

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:+PrintGCDetails -verbose:gc
    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);
    }
}

/*
sleep....
[GC (Allocation Failure) [PSYoungGen: 4218K->1000K(9216K)] 12410K->9216K(19456K), 0.0008774 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1000K->952K(9216K)] 9216K->9176K(19456K), 0.0007663 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 952K->0K(9216K)] [ParOldGen: 8224K->9053K(10240K)] 9176K->9053K(19456K), [Metaspace: 4372K->4372K(1056768K)], 0.0057745 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] 9053K->9053K(19456K), 0.0002972 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] [ParOldGen: 9053K->8997K(10240K)] 9053K->8997K(19456K), [Metaspace: 4372K->4366K(1056768K)], 0.0087193 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] 
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
	at cn.itcast.jvm.t2.Demo2_1.lambda$main$0(Demo2_1.java:22)
	at cn.itcast.jvm.t2.Demo2_1$$Lambda$1/990368553.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)
[[B@6d311334]
Heap
 PSYoungGen      total 9216K, used 7575K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 92% used [0x00000000ff600000,0x00000000ffd65fc0,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 8997K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 87% used [0x00000000fec00000,0x00000000ff4c9608,0x00000000ff600000)
 Metaspace       used 4896K, capacity 4978K, committed 5248K, reserved 1056768K
  class space    used 550K, capacity 591K, committed 640K, reserved 1048576K

Process finished with exit code 0

*/
  • 垃圾回收器

  • 串行

单线程

堆内存较小,适合个人电脑

  • 吞吐量优先

多线程堆内存较大,多核 cpu 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高

  • 响应时间优先

多线程

堆内存较大,多核 cpu

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

 

  • 串行

XX:+UseSerialGC = Serial + SerialOld

  • 吞吐量优先

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC

-XX:GCTimeRatio=ratio             垃圾回收占总时间的比例,增大堆内存可以减小该值

-XX:MaxGCPauseMillis=ms       每次垃圾回收消耗的时间,减小对内存可以减小该值

-XX:ParallelGCThreads=n

 

  • 响应时间优先

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld

-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads

-XX:CMSInitiatingOccupancyFraction=percent

-XX:+CMSScavengeBeforeRemark

 

 

  • G1(Garbage First)

 

2004 论文发布

2009 JDK 6u14 体验

2012 JDK 7u4 官方支持

2017 JDK 9 默认

  • 适用场景
  1. 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
  2. 超大堆内存,会将堆划分为多个大小相等的 Region
  3. 整体上是 标记+整理 算法,两个区域之间是 复制 算法
  • 相关 JVM 参数
  1. -XX:+UseG1GC      (JDK9以前需要次参数,JDK9以后就是默认的了
  2. -XX:G1HeapRegionSize=size
  3. -XX:MaxGCPauseMillis=time
  • G1 垃圾回收阶段

  • Young Collection

会 STW

  • Young Collection + CM
  1. 在 Young GC 时会进行 GC Root 的初始标记
  2. 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定

  • Mixed Collection

会对 E、S、O 进行全面垃圾回收

  1. 最终标记(Remark)会 STW
  2. 拷贝存活(Evacuation)会 STW

XX:MaxGCPauseMillis=ms

 

  • Full GC

  • SerialGC
  1. 新生代内存不足发生的垃圾收集 - minor gc
  2. 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC
  1. 新生代内存不足发生的垃圾收集 - minor gc
  2. 老年代内存不足发生的垃圾收集 - full gc
  • CMS
  1. 新生代内存不足发生的垃圾收集 - minor gc
  2. 老年代内存不足
  • G1
  1. 新生代内存不足发生的垃圾收集 - minor gc
  2. 老年代内存不足

 

  • Young Collection 跨代引用

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

  • 卡表与 Remembered Set
  • 在引用变更时通过 post-write barrier + dirty card queue
  • concurrent refinement threads 更新 Remembered Set

  • Remark

  • pre-write barrier + satb_mark_queue

  • JDK 8u20 字符串去重

优点:节省大量内存

缺点:略微多占用了 cpu 时间,新生代回收时间略微增加

-XX:+UseStringDeduplication
String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
  • 将所有新分配的字符串放入一个队列
  • 当新生代回收时,G1并发检查是否有字符串重复
  • 如果它们值一样,让它们引用同一个 char[]
  • 注意,与 String.intern() 不一样
  1. String.intern() 关注的是字符串对象
  2. 而字符串去重关注的是 char[] 在
  3. JVM 内部,使用了不同的字符串表
  • JDK 8u40 并发标记类卸载

所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸 载它所加载的所有类 -XX:+ClassUnloadingWithConcurrentMark 默认启用

  • JDK 8u60 回收巨型对象

  1. 一个对象大于 region 的一半时,称之为巨型对象
  2. G1 不会对巨型对象进行拷贝
  3. 回收时被优先考虑
  4. G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生 代垃圾回收时处理掉
  • JDK 9 并发标记起始时间的调整

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

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

JDK 9 可以动态调整

  • -XX:InitiatingHeapOccupancyPercent 用来设置初始值
  • 进行数据采样并动态调整
  • 总会添加一个安全的空档空间
  • JDK 9 更高效的回收

250+增强

180+bug修复

https://docs.oracle.com/en/java/javase/12/gctuning

预备知识

  • 掌握 GC 相关的 VM 参数,会基本的空间调整
  • 掌握相关工具
  • 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则
  • 调优领域

  1. 内存
  2. 锁竞争
  3. cpu 占用
  4. io
  • 确定目标

【低延迟】还是【高吞吐量】,选择合适的回收器

【低延迟】: CMS,G1,ZGC

【高吞吐量】: ParallelGC

【低延迟】【高吞吐量】 Zing

  • 最快的 GC

答案是不发生 GC

查看 FullGC 前后的内存占用,考虑下面几个问题

数据是不是太多?

resultSet = statement.executeQuery("select * from 大表 limit n")

数据表示是否太臃肿?

  • 对象图
  • 对象大小 16 Integer 24 int 4

是否存在内存泄漏?

static Map map =

  1. 第三方缓存实现
  • 新生代调优

新生代的特点

  1. 所有的 new 操作的内存分配非常廉价 TLAB thread-local allocation buffer
  2. 死亡对象的回收代价是零
  3. 大部分对象用过即死
  4. Minor GC 的时间远远低于 Full GC

新生代内存越大越好吗?

-Xmn Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). GC is performed in this region more often than in other regions. If the size for the young generation is too small, then a lot of minor garbage collections are performed. If the size is too large, then only full garbage collections are performed, which can take a long time to complete. Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size.

  • 新生代能容纳所有【并发量 * (请求-响应)】的数据
  • 幸存区大到能保留【当前活跃对象+需要晋升对象】
  • 晋升阈值配置得当,让长时间存活对象尽快晋升

 

-XX:MaxTenuringThreshold=threshold
-XX:+PrintTenuringDistribution
  • 老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经...,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3

-XX:CMSInitiatingOccupancyFraction=percent

  • 案例

案例1 Full GC 和 Minor GC频繁

        GC频繁说明堆内存紧张,可以尝试增大新生代内存

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

        以CMS为例,可能是重新标记时间太长,解决方法可以在重新标记之前进行新生代的垃圾回收,配置参数:

-XX:+CMSScavengeBeforeRemark

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

        老年代发生Full Gc可能是因为内存碎片过多或者老年代内存不足,老年代充裕情况下,发生 Full GC,可能是JDK7及以前的版本,永久代在内存不足也会引发Full GC

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Toroidals

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值