JVM_02 垃圾回收篇

x33g5p2x  于2021-12-06 转载在 Java  
字(14.1k)|赞(0)|评价(0)|浏览(275)

JVM(Java Virtual Machine)垃圾回收篇

1、如何判断对象可以回收

1.1、引用计数法

public class GCTest {
    public static void main(String[] args) {
        Student student1 = new Student();
        Student student2 = new Student();
        	
        student1.student = student2 ;		//这种情况GC就无法回收
        student2.student = student1 ;	
    }
}
class Student{
    int id ;
    Student student ;
}
1.2、可达性分析算法

1.2.1 什么类可以被作为GCRoot

//1、t1可以叫虚拟机栈中引用的对象(由于强引用不可回收)
GCRootsDemo t1 = new GCRootsDemo();
//2、方法区中的类静态属性引用的对象
private static GCRootsDemo2 t2 = new GCRootsDemo2();
//3、方法区中常量引用的对象
private static finalGCRootsDemo3 t3 = new GCRootsDemo3(); 		特点:都不会被回收的,可以作为根
1.3、五种引用类型

1、强引用(StrongReference)

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。

Object o=new Object();   // 强引用
o=null;     // 帮助垃圾收集器回收此对象
2、软引用(SoftReference)

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

//当我们内存不够用的时候,soft会被回收的情况
SoftReference<MyObject> softReference = new SoftReference<>(new Object());
3、弱引用(WeakReference)

对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

//垃圾回收机制一运行,会回收该对象占用的内存
WeakReference<MyObject> weakReference = new WeakReference<>(new Object());
4、虚引用(PhantomReference)

顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象。

5、终结器引用(FinalReference)

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

除强引用外,其余四种引用一旦没有被GCRoot引用,当被垃圾回收的时候,引用都会进入引用队列

ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue();
//和引用队列进行关联,当虚引用对象被回收后,会进入ReferenceQueue队列中
PhantomReference<MyObject> phantomReference = new PhantomReference<>(new MyObject(),referenceQueue);
软引用演示
/** * 演示 软引用 * -Xmx20m -XX:+PrintGCDetails -verbose:gc 设置堆Heap内存的大小为20M ,打印详细的GC信息 */
public class Code_08_SoftReferenceTest {

    public static int _4MB = 4 * 1024 * 1024;

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

    // 设置 -Xmx20m , 演示堆内存不足,
    public static void method1() throws IOException {
        ArrayList<byte[]> list = new ArrayList<>();

        for(int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }
        System.in.read();
    }

    // 演示 软引用
    public static void method2() throws IOException {
        ArrayList<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());
        }
    }
}

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

软引用关联一个引用队列

// 演示 软引用 搭配引用队列
    public static void method3() throws IOException {
        ArrayList<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[]> ref : list) {
            System.out.println(ref.get());
        }
    }
弱引用演示
public class Code_09_WeakReferenceTest {
	public static void main(String[] args) {
// method1();
        	method2();
   }
public static int _4MB = 4 * 1024 *1024;

// 演示 弱引用
public static void method1() {
    List<WeakReference<byte[]>> list = new ArrayList<>();
    for(int i = 0; i < 10; i++) {
        WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
        list.add(weakReference);

        for(WeakReference<byte[]> wake : list) {
            System.out.print(wake.get() + ",");
        }
        System.out.println();
    }
}

// 演示 弱引用搭配 引用队列
public static void method2() {
    List<WeakReference<byte[]>> list = new ArrayList<>();
    ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

    for(int i = 0; i < 9; i++) {
        WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], queue);
        list.add(weakReference);
        for(WeakReference<byte[]> wake : list) {
            System.out.print(wake.get() + ",");
        }
        System.out.println();
    }
    System.out.println("===========================================");
    Reference<? extends byte[]> poll = queue.poll();
    while (poll != null) {
        list.remove(poll);
        poll = queue.poll();
    }
    for(WeakReference<byte[]> wake : list) {
        System.out.print(wake.get() + ",");
    }
}

2、垃圾回收算法

1、标记清除算法
  • 标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
  • 清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

第一步:标记出养老区的所有垃圾,第二步:清除所有的垃圾

  • 优点:节省了空间
  • 缺点:会产生内存碎片
2、标记整理算法

在标记清除的步骤后,在将老年代通过整理算法整理一下空间,解决内存碎片问题

  • 优点:解决上述的问题
  • 唯一缺点:耗时
3、复制算法

  • 优点:不会产生内存碎片,
  • 缺点:但是浪费空间,因为始终会存在一个空的幸存区 (浪费是值得的!hh)

3、分代垃圾回收

  • 新创建的对象首先分配在 eden 区
  • 新生代空间不足时,触发 minor gc ,eden 区 和 from 区存活的对象使用 - copy 复制到 to 中,存活的对象年龄加一,然后交换 from to
  • minor gc 会引发 stop the world,暂停其他线程,等垃圾回收结束后,恢复用户线程运行
  • 当幸存区对象的寿命超过阈值时,会晋升到老年代,内存紧张可以直接晋升,最大的寿命是 15(4bit),寿命存储在对象头,占用4bit
  • 当老年代空间不足时,会先触发 minor gc,如果空间仍然不足,那么就触发 full fc ,停止的时间更长!
  • 当MInorGC 和Full GC后还是满的,那么就会抛出异常java.lang.OutOfMemoryError(OOM)
1、相关JVM参数
含义参数
堆初始大小-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
2、GC 分析

大对象处理策略:

当遇到一个较大的对象时,就算新生代的伊甸园为空,也无法容纳该对象时,会将该对象直接晋升为老年代

线程内存溢出:

某个线程的内存溢出了而抛异常(out of memory),不会让其他的线程结束运行。

这是因为当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,进程依然正常。

4、垃圾回收器

正式进入前先看下图解HotSpot虚拟机所包含的收集器:

新生代收集器:Serial、ParNew、Parallel Scavenge

老年代收集器:CMS、Serial Old、Parallel Old

整堆收集器: G1

几个相关概念

并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。

并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一

个CPU上

吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时

间)),也就是。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%。

下面来了解一下垃圾回收器的分类:

1、串行
  • 单线程
  • 适用场景:内存较小,个人电脑(CPU核数较少)。

串行垃圾回收器开启语句:-XX:+UseSerialGC = Serial + SerialOld

Serial:表示新生代,采用复制算法SerialOld:表示老年代,采用的是标记整理算法

安全点:让其他线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象。

因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态。

1.1 Serial 收集器

Serial(新生代)收集器是最基本的、发展历史最悠久的收集器:

特点:单线程、简单高效(与其他收集器的单线程相比),采用复制算法。对于限定单个CPU的环境来说,Serial收集器由于没有线程交

互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束

(Stop The World)

注意:

  • 单线程收集器其意思是说,收集器是单线程工作的只会占用1个cpu核心,
  • 多线程收集器意思则是收集器是多线程工作的,收集器工作的时候是需要多个cpu核心的!
1.2 ParNew 收集器

ParNew(新生代) 收集器其实就是 Serial 收集器的多线程版本

特点:多线程、采用复制算法**、ParNew 收集器默认开启的收集线程数与CPU的数量相同,在 CPU 非常多的环境中,可以使用 -XX:ParallelGCThreads 参

数来限制垃圾收集的线程数。除了使用多线程外其余行为均和Serial收集器一模一样存在 Stop The World 问题,可以和CMS收集器连用

1.3 SerialOld 收集器

Serial Old 是 Serial 收集器的老年代版本

特点:同样是单线程收集器,采用标记-整理算法

2、吞吐量优先
  • 多线程
  • 适用场景:堆内存较大,多核CPU
  • 单位时间内,让STW(stop the world,停掉其他所有工作线程)时间最短
  • JDK1.8默认使用的垃圾回收器
// 1.吞吐量优先垃圾回收器开关:(默认开启)
-XX:+UseParallelGC~-XX:+UseParallelOldGC
// 2.采用自适应的大小调整策略:调整新生代(伊甸园 + 幸存区FROM、TO)内存的大小
-XX:+UseAdaptiveSizePolicy  
// 3.调整吞吐量的目标:吞吐量 = 垃圾回收时间/程序运行总时间 1/(1+ratio) 当ratio取19时,那么吞吐量就是1/20
-XX:GCTimeRatio=ratio
// 4.垃圾收集最大停顿毫秒数:默认值是200ms
-XX:MaxGCPaiseMillis=ms
// 5.控制ParallelGC运行时的线程数
-XX:ParallelGCThreads=n

2.1 Parallel Scavenge 收集器

与吞吐量关系密切,故也称为吞吐量优先收集器:(用尽可能少的时间回收垃圾)

特点:属于新生代收集器也是采用复制算法的收集器(用到了新生代的幸存区),又是并行的多线程收集器(与ParNew收集器类似)。

该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是:GC自适应调节策略(与ParNew收集器最重要的一个区别)。

GC自适应调节策略:Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小

(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚

拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适

应调节策略。

Parallel Scavenge收集器使用两个参数控制吞吐量:

  • XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间。
  • XX:GCRatio 直接设置吞吐量的大小。
2.2 Parallel Old 收集器

是Parallel Scavenge收集器的老年代版本:

特点:多线程,采用标记-整理算法(老年代没有幸存区)。

3、响应时间优先
  • 多线程
  • 适用场景:堆内存较大,多核CPU
  • 尽可能单次STW时间变短(尽量不影响其他线程运行)
// 开关:
-XX:+UseConMarkSweepGC~-XX:+UseParNewGC~SerialOld  
// ParallelGCThreads=n并发线程数 
// ConcGCThreads=threads并行线程数 ,垃圾回收线程一般是并发执行的总线程数的1/4,也就是1个垃圾回收线程和3个用户线程争抢cpu
-XX:ParallelGCThreads=n~-XX:ConcGCThreads=threads
// 执行CMS垃圾回收的内存占比:预留一些空间保存浮动垃圾:因为并发清理时候会产生浮动垃圾,这些浮动垃圾没地方存
-XX:CMSInitiatingOccupancyFraction=percent
// 重新标记之前,对新生代进行垃圾回收,这样会减轻我们重新标记的压力
-XX:+CMSScavengeBeforeRemark

3.1 CMS 收集器

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

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

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

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

  • 初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
  • 并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
  • 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
  • 并发清除:对标记的对象进行清除回收。

CMS收集器的内存回收过程是与用户线程一起并发执行

4、G1

参考视频:新一代垃圾回收器:G1详解

参考文章:垃圾收集器G1详解

适用场景

  • 同时注重吞吐量和低延迟(响应时间)。
  • 超大堆内存(内存大的),会将堆内存划分为多个大小相等的区域。
  • 整体上是标记-整理算法,两个区域之间是复制算法。

相关参数:JDK8 并不是默认开启的,需要参数开启:

// G1开关
-XX:+UseG1GC
// 所划分的每个堆内存大小:,默认分别为2048个去区域
-XX:G1HeapRegionSize=size
// 垃圾回收最大停顿时间(延时时间),默认是250ms
-XX:MaxGCPauseMillis=time

设计的目的是按照Region回收,优先回收价值最大的(回收后所能得到最大的空间),这样每次回收一小块降低等待时间!

为了解决上述的跨代引用问题,我们引入CardTable (卡表)和 RememberSet

CardTable:(卡表)

  • 一个Region分为若干个卡片(Card),所有的卡片累计起来就是CardTable,当其中某个card引用其他Region(只有当执行赋值操作的时候才能引用到),这个card就会被变为脏的标记为dirty

RemeberSet :(记忆集合)

  • 假设Region1中的某个对象,其位于Region1的card1部分,那么当这个对象引用Region2中的对象,那么Region2就会保存这个card,同理其他region引用也会保存其他的card,这些个card就被成为RemberSet。

当我们需要对Region2进行回收的时候,通过Rset可以直接定位到Region的哪个card,无需扫描全部!

4.1、G1垃圾回收阶段

会Stop The World (STW)

新生代伊甸园垃圾回收—–>内存不足,新生代回收+并发标记—–>回收新生代伊甸园、幸存区、老年代内存——>新生代伊甸园垃圾回收(重新开始)。

4.2 Young Collection 新生代垃圾回收

分区算法Region

分代是按对象的生命周期划分,分区则是将堆空间划分连续几个不同小区间,每一个小区间独立回收,可以控制一次回收多少个小区间,

方便控制 GC 产生的停顿时间。虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需

要连续)的动态集合 Collection Set。E:伊甸园 S:幸存区 O:老年代

1、新创建对象放入E:伊甸园区,

2、当我们的Eden区满了以后执行垃圾回收时,会把伊甸园(E)的幸存对象复制到幸存区(S):

3、当幸存区(s)中的对象也比较多触发垃圾回收,且幸存对象寿命超过阈值时,幸存区(S)中的一部分对象(寿命达到阈值)会晋升到老年代(O),寿命未达到阈值的会被再次复制到另一个幸存区(S):

4.3 Young Collection + CM 新生代垃圾回收和并发标记

CM(Concurrent Mark):并发标记!

  • 在 Young GC 时会对 GC Root 进行初始标记。
  • 老年代占用堆内存的比例达到阈值时,进行并发标记(不会STW),阈值可以根据用户来进行设定:
-XX:InitiatingHeapOccupancyPercent=percent // 默认值45%

4.4 Mixed Collection 混合收集

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

  • 最终标记
  • 拷贝存活(采用复制算法)
// 用于指定GC最长的停顿时间
-XX:MaxGCPauseMillis=ms

MixGC的回收过程可以理解为YoungGC后附加的全局concurrent marking,全局的并发标记主要用来处理old区(包含H区)的存活对象

标记,过程如下:1. 初始标记(InitingMark) 2. 根分区扫描(RootRegionScan)3. 并发标记(ConcurrentMark)4. 最终标记

(Remark)5. 清除阶段(Clean UP)

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

因为指定了最大停顿时间,如果对所有老年代都进行回收,耗时可能过高。为了保证时间不超过设定的最大停顿时间,会根据最大停顿时

间,有选择的回收最有价值的老年代(回收后,能够得到更多内存)。

G1在老年代内存不足时(老年代所占内存超过阈值):

  • 如果垃圾产生速度慢于垃圾回收速度,不会触发Full GC,还是并发地进行清理。
  • 如果垃圾产生速度快于垃圾回收速度,便会触发Full GC。
4.5 Full GC
  • SerialGC

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

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

  • ParallelGC

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

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

  • CMS

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

  • 老年代内存不足发生的垃圾收集,需要分2种情况,这里不做详细介绍

  • G1

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

  • 老年代内存不足发生的垃圾收集,需要分2种情况,这里不做详细介绍

G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。FullGC使用的是stop the

world的单线程的Serial Old模式,所以一旦触发FullGC则会STW应用线程,并且执行效率很慢。JDK 8版本的G1是不提供Full gc的处理的。

对于G1 GC的优化,很大的目标就是没有FullGC。

4.6 Young Collection 跨代引用
  • 新生代回收的跨代引用(老年代引用新生代)问题:

  • 卡表与Remembered Set
  • Remembered Set 存在于E中,用于保存新生代对象对应的脏卡:
  • 脏卡:O被划分为多个区域(一个区域512K),如果该区域引用了新生代对象,则该区域被称为脏卡。
  • 在引用变更时通过post-write barried + dirty card queue
  • concurrent refinement threads 更新 Remembered Set。

4.7、Remark(重标记)
  • 重新标记阶段

  • 在垃圾回收时,收集器处理对象的过程中:

  • 黑色:已被处理,需要保留的

  • 灰色:正在处理中的

  • 白色:还未处理的

但是在并发标记过程中,有可能A被处理了以后未引用C,但该处理过程还未结束,在处理过程结束之前A引用了C,这时就会用到

remark。

  • 之前C未被引用,这时A引用了C,就会给C加一个写屏障,写屏障的指令会被执行,将C放入一个队列当中,并将C变为 处理中 状态。
  • 并发标记阶段结束以后,重新标记阶段会STW,然后将放在该队列中的对象重新处理,发现有强引用引用它,就会处理它。

4.8 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'}
  • 将所有新分配的字符串(底层是char[])放入一个队列。

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

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

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

  • intern关注的是字符串对象。

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

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

4.9 JDK 8u40 并发标记类卸载

在所有对象经过并发标记阶段以后,就能知道哪些类不再被使用。如果一个类加载器的所有类都不在使用时,则卸载它所加载的所有类。

并发标记类卸载开启指令:-XX:+ClassUnloadWithConcurrentMark 默认启用。

4.10 JDK 8u60 回收巨型对象
  • H表示巨型对象,当一个对象占用大于region的一半时,就称为巨型对象。
  • G1不会对巨型对象进行拷贝。
  • 回收时被优先考虑。
  • G1会跟踪老年代所有incoming引用,如果老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉。

巨型对象越早回收越好,最好是在新生代的垃圾回收就回收掉~

4.11 JDK 9 并发标记起始时间的动态调整
  • 并发标记必须在堆空间占满前完成,否则就退化为 Full GC。
  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent 设置阈值,默认是 45%。
  • JDK 9 可以动态调整:(目的是尽可能的避免并发标记退化成 Full GC)
  • -XX:InitiatingHeapOccupancyPercent:用来设置初始阈值。
  • 在进行垃圾回收时,会进行数据采样并动态调整阈值。
  • 总会添加一个安全的空挡空间,用来容纳那些浮动的垃圾。
4.12 JDK 9 更高效的回收
G1总结

YoungGC阶段:就是跟我们普通的GC一样,针对新生代,Eden满了然后通过复制算法道Survivor,S满了到Old的过程

MixedGC阶段:类CMS类似,但是他是针对新生代和老年代的全局,按照如下流程:

​ 1、初始标记,标记出所有的GCRoot

​ 2、并发标记,tracing GCroot(对GCroot进行跟踪),找出所有的活跃节点,但是此时是与用户线程并发执行的,所以不准确

​ 3、最终标记(重新标记remark):此时会STW,以及对新对象和队列中的对象(参考remark)进行重新判断,确认

​ 4、复制/清除阶段,这个阶段会优先对可回收空间较大的Region进行回收,即garbage first,这也是G1名称的由来。

这四个步骤

FullGC阶段:G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。FullGC使用的是stop the world的单线程的Serial Old模式,所以一旦触发FullGC则会STW应用线程,并且执行效率很慢。JDK 8版本的G1是不提供Full gc的处理的。对于G1 GC的优化,很大的目标就是没有FullGC。

未来:ZGC 与 Shenandoah

5、垃圾回收调优

查看虚拟机参数命令

D:\JavaJDK1.8\bin\java  -XX:+PrintFlagsFinal -version | findstr "GC"

可以根据参数去查询具体的信息

1)调优领域
  • 内存
  • 锁竞争
  • cpu 占用
  • io
  • gc
2)确定目标

低延迟/高吞吐量? 选择合适的GC

  • CMS G1 ZGC
  • ParallelGC
  • Zing
3)最快的 GC

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

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

  • 数据是不是太多?

resultSet = statement.executeQuery(“select * from 大表 limit n”)
  • 数据表示是否太臃肿 ?

  • 对象图

  • 对象大小 16 Integer 24 int 4

  • 是否存在内存泄漏 ?

  • static Map map …

  • 第三方缓存实现

4)新生代调优
  • 所有的 new 操作分配内存都是非常廉价的

  • 新生代对每个线程都有对应的本地缓存池 :TLAB thread-lcoal allocation buffer

  • 死亡对象回收零代价

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

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

不是

  • 新生代内存太小:频繁触发 Minor GC ,会 STW ,会使得吞吐量下降
  • 新生代内存太大:老年代内存占比有所降低,会更频繁地触发 Full GC。而且触发 Minor GC 时,清理新生代所花费的时间会更长
  • 新生代内存设置为内容纳[并发量*(请求-响应)]的数据为宜
  • 幸存区需要能够保存 当前活跃对象+需要晋升的对象
  • 晋升阈值配置得当,让长时间存活的对象尽快晋升
-XX:MaxTenuringThreshold=threshold
-XX:+PrintTenuringDistrubution
5)老年代调优

以 CMS 为例:

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经,否者先尝试调优新生代。
  • 观察发现 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
-XX:CMSInitiatingOccupancyFraction=percent
6)案例

案例1:Full GC 和 Minor GC 频繁

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

虚拟机参数命令

D:\JavaJDK1.8\bin\java  -XX:+PrintFlagsFinal -version | findstr "GC"

可以根据参数去查询具体的信息

1)调优领域
  • 内存
  • 锁竞争
  • cpu 占用
  • io
  • gc
2)确定目标

低延迟/高吞吐量? 选择合适的GC

  • CMS G1 ZGC
  • ParallelGC
  • Zing
3)最快的 GC

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

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

  • 数据是不是太多?

resultSet = statement.executeQuery(“select * from 大表 limit n”)
  • 数据表示是否太臃肿 ?

  • 对象图

  • 对象大小 16 Integer 24 int 4

  • 是否存在内存泄漏 ?

  • static Map map …

  • 第三方缓存实现

4)新生代调优
  • 所有的 new 操作分配内存都是非常廉价的

  • 新生代对每个线程都有对应的本地缓存池 :TLAB thread-lcoal allocation buffer

  • 死亡对象回收零代价

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

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

不是

  • 新生代内存太小:频繁触发 Minor GC ,会 STW ,会使得吞吐量下降
  • 新生代内存太大:老年代内存占比有所降低,会更频繁地触发 Full GC。而且触发 Minor GC 时,清理新生代所花费的时间会更长
  • 新生代内存设置为内容纳[并发量*(请求-响应)]的数据为宜
  • 幸存区需要能够保存 当前活跃对象+需要晋升的对象
  • 晋升阈值配置得当,让长时间存活的对象尽快晋升
-XX:MaxTenuringThreshold=threshold
-XX:+PrintTenuringDistrubution
5)老年代调优

以 CMS 为例:

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经,否者先尝试调优新生代。
  • 观察发现 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
-XX:CMSInitiatingOccupancyFraction=percent
6)案例

案例1:Full GC 和 Minor GC 频繁

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

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

相关文章

微信公众号

最新文章

更多