(ROOT)jvm与垃圾回收总结

6 篇文章 0 订阅

class文件

class文件详解_weixin_38681369的博客-CSDN博客

JMM

JVM-JMM相关-CSDN博客

synchronized实现细节

对象头及sync锁升级详解_weixin_38681369的博客-CSDN博客

对象大小等问题

创建对象的过程:

New 一个对象的过程
先分配内存
再赋默认值
再赋初始值
面试题:DCL(Double Check Lock)单例要不要加Volitile?
需要。因为有指令重排的问题。
初始化一半的时候,单例已经存在了,处于半初始化的状态。此时另外一个线程来了,直接把半初始化的对象取走了,出现值的问题。

package com.mashibing.jvm.c0_basic;

public class C {
    public static void main(String[] args) {
        Object o = new Object();
    }
}

下图4、7可能会发生重排

a8b298771ee546e5bf77858797b26928.png

1.创建一个对象Object(), 并将其引用引用值压入栈顶

2.复制栈顶数值并将复制值压入栈顶(如果不使用 dup 复制,被构造函数指令invokespecial使用栈顶pop出的引用后,栈顶将没有Object()引用,最终无法返回实例引用。)

3.调用 Object 的构造函数 invokespecial<>

4.给对象的引用o赋予对象地址

5.返回

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

对象大小(64位机)

观察虚拟机配置

java -XX:+PrintCommandLineFlags -version

普通对象

  1. 对象头:markword 8

  2. ClassPointer指针:-XX:+UseCompressedClassPointers 为4字节 不开启为8字节

  3. 实例数据

    1. 引用类型:-XX:+UseCompressedOops 为4字节 不开启为8字节 Oops Ordinary Object Pointers

  4. Padding对齐,8的倍数

数组对象

  1. 对象头:markword 8

  2. ClassPointer指针同上

  3. 数组长度:4字节

  4. 数组数据

  5. 对齐 8的倍数

实验

  1. 新建项目ObjectSize (1.8)

  2. 创建文件ObjectSizeAgent

    package com.mashibing.jvm.agent;
    ​
    import java.lang.instrument.Instrumentation;
    ​
    public class ObjectSizeAgent {
        private static Instrumentation inst;
    ​
        public static void premain(String agentArgs, Instrumentation _inst) {
            inst = _inst;
        }
    ​
        public static long sizeOf(Object o) {
            return inst.getObjectSize(o);
        }
    }
  3. src目录下创建META-INF/MANIFEST.MF

    Manifest-Version: 1.0
    Created-By: mashibing.com
    Premain-Class: com.mashibing.jvm.agent.ObjectSizeAgent

    注意Premain-Class这行必须是新的一行(回车 + 换行),确认idea不能有任何错误提示

  4. 打包jar文件

  5. 在需要使用该Agent Jar的项目中引入该Jar包project structure - project settings - library 添加该jar包

  6. 运行时需要该Agent Jar的类,加入参数:

    -javaagent:C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar
  7. 如何使用该类:

    ```java
       package com.mashibing.jvm.c3_jmm;
       
       import com.mashibing.jvm.agent.ObjectSizeAgent;
       
       public class T03_SizeOfAnObject {
           public static void main(String[] args) {
               System.out.println(ObjectSizeAgent.sizeOf(new Object()));
               System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
               System.out.println(ObjectSizeAgent.sizeOf(new P()));
           }
       
           private static class P {
                               //8 _markword
                               //4 _oop指针
               int id;         //4
               String name;    //4
               int age;        //4
       
               byte b1;        //1
               byte b2;        //1
       
               Object o;       //4
               byte b3;        //1
       
           }
       }

Hotspot开启内存压缩的规则(64位机)

  1. 4G以下,直接砍掉高32位

  2. 4G - 32G,默认开启内存压缩 ClassPointers Oops

  3. 32G,压缩无效,使用64位内存并不是越大越好(^-^)

IdentityHashCode的问题

当一个对象计算过identityHashCode之后,不能进入偏向锁状态

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

死磕Synchronized底层实现,面试你还怕什么?-腾讯云开发者社区-腾讯云 面试题深入解析:Synchronized底层实现-腾讯云开发者社区-腾讯云

死磕Synchronized底层实现--重量级锁-腾讯云开发者社区-腾讯云

一文让你读懂Synchronized底层实现,秒杀面试官-腾讯云开发者社区-腾讯云

对象定位

访问对象两种方式--句柄和直接指针_句柄 直接指针_稳重的二哈的博客-CSDN博客

  1. 句柄池

  2. 直接指针

对象头

对象头及sync锁升级详解_weixin_38681369的博客-CSDN博客

java运行时数据区

java运行时数据区_weixin_3868136的博客-CSDN博客

常用JVM参数 

(SUB)常见JVM参数-CSDN博客

垃圾回收(GC)

没有任何引用指向的一个对象或者多个对象(包括根不可达的循环引用)

如何定位垃圾

引用计数(ReferenceCount)(循环引用问题)

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。

优点:简单高效

缺点:会出现循环引用问题(两个对象互相引用导致计数器都不为0无法回收)

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

根可达算法(RootSearching)

可作为 GC Roots 的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

《JVM虚拟机规范》对于根对象的定义,下图右侧:
watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

不可达对象的回收策略:

要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。

被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

如何回收常量和类

如何判断一个常量是废弃常量?

假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。

如何判断一个类是无用的类?

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

浅谈引用:

强引用(StrongReference)

垃圾回收器绝不会回收它。

软引用(SoftReference)

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

弱引用(WeakReference)

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

虚引用(PhantomReference)

与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 

虚引用是给写JVM虚拟机的人管理堆外内存用的

虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

常见的垃圾回收算法

  1. 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)

  2. 拷贝算法 (copying) - 没有碎片,浪费空间

  3. 标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)

标记清除算法 mark-sweep

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

算法相对简单,存活对象比较多的情况下效率比较高。不适合伊甸区,伊甸区的存活对象比较少。

两遍扫描,效率偏低,容易产生碎片。(第一遍标记,第二遍清除)

拷贝算法 copying

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

适用于存活对象比较少的情况。只扫描一次,效率高,没有碎片。
空间浪费,移动复制对象,需要调整对象引用。

标记压缩算法 mark-compacting

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

空间连续,没有碎片,方便对象分配,也不会产生内存减半
需要扫描两次,需要移动对象,效率偏低

垃圾回收机制

部分收集 (Partial GC):

  • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
  • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
  • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。

整堆收集 (Full GC / Major GC ):

收集整个 Java 堆和方法区。

JVM内存分代模型(用于分代垃圾回收算法)

  • 除了 Epsilon,ZGC,Shenandoah (逻辑物理都不分代)
  • G1是逻辑分代,物理不分代(物理分代就是内存里确实有这样一块空间)
  • 除此之外,不仅逻辑分代,而且物理分代。

 Q: HotSpot 为什么要分为新生代和老年代?

 A: 比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

堆内存逻辑分区(针对分代的垃圾回收器)

  1. 新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace(-Xms -Xmx配置堆(新生代 + 老年代)大小,-XX:MetaspaceSize和-XX:MaxMetaspaceSize配置元数据区)(-XX:NewRatio= 参数可以设置Young与Old的大小比例有文档推荐设为Heap总大小的1/4)

    1. 永久代 元数据 - Class

    2. 永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)

    3. 字符串常量 1.7 - 永久代,1.8 - 堆

    4. MethodArea逻辑概念 - 永久代、元数据

  2. 新生代(-Xmn配置新生代大小)= Eden + 2个suvivor(-XX:SurvivorRatio= 参数可以设置Eden与Survivor的比例)

    1. YGC回收之后,大多数的对象会被回收,活着的进入s0

    2. 再次YGC,活着的对象eden + s0 -> s1

    3. 再次YGC,eden + s1 -> s0

    4. 年龄足够 -> 老年代 (因为对象头中4字节控制年龄所以15 CMS 6)(年龄-XX:MaxTenuringThreshold配置)

    5. s区装不下 -> 老年代

  3. 老年代

    1. 顽固分子

    2. 老年代满了FGC Full GC

  4. GC Tuning (Generation)

    1. 尽量减少FGC

    2. MinorGC = YGC

    3. MajorGC = FGC

垃圾回收器

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

   常见组合:

  • Serial+Serial Old
  • ParNew+CMS(响应快)三色标记 + 写屏障 + 增量更新
  • Parallel Scavenge+Parallel Old(吞吐量大)
  • G1(10ms)算法:三色标记 + SATB + WriteBarrier

  • ZGC (1ms) PK C++算法:ColoredPointers + LoadBarrier

  • Shenandoah算法:ColoredPointers + WriteBarrier

  • Eplison

ConcurrentMarkSweep 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收想象一下:PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC

 读写屏障

CMS和G1的漏标问题解决及三色标记算法图解 - 简书 (jianshu.com)

垃圾收集器跟内存大小的关系

  1. Serial 几十兆

  2. PS 上百兆 - 几个G

  3. CMS - 20G

  4. G1 - 上百G

  5. ZGC - 4T - 16T(JDK13)

常见垃圾回收器组合参数设定:(1.8)

  • -XX:+UseSerialGC = Serial New (DefNew) + Serial Old

    • 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器

  • -XX:+UseParNewGC = ParNew + SerialOld

  • -XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old

  • -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)

  • -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old

  • -XX:-UseParallelOldGC = 新生代ParallelScavenge + 老年代SerialOld
  • -XX:+UseG1GC = G1

  • Linux中没找到默认GC的查看方法,而windows中会打印UseParallelGC

    • java +XX:+PrintCommandLineFlags -version

    • 通过GC的日志来分辨

  • Linux下1.8版本默认的垃圾回收器到底是什么?

    • 1.8.0_181 默认(看不出来)Copy MarkCompact

    • 1.8.0_222 默认 PS + PO

垃圾回收中的并行与并发

Parallel Scavenge是并行,回收线程多线程,但工作线程暂停

CMS是并发,回收线程和工作线程同时进行。

Serial 收集器

只会使用一条垃圾收集线程去完成垃圾收集工作,它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。

优:简单高效

缺:Stop The World时间长

新生代采用标记-复制算法,老年代采用标记-整理算法。

Parallel Scavenge 收集器

Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。

ParNew 收集器

Serial 收集器的多线程版本,

JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS 并发垃圾回收是因为无法忍受STW

新生代采用标记-复制算法

CMS

分为4个阶段

  • 初始标记(Stop The World): 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
  • 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记(Stop The World): 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短(此处重新标记策略为重新对所有已标记进行可达性分析防止漏标现象,且没有产生浮动垃圾,但耗时相对G1长;区别于G1回收。G1采用SATB(snapshot-at-the-beginning)算法进行重新标记存在浮动垃圾)
  • 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。清理的过程也会产生新的垃圾“浮动垃圾”,需要等下一次CMS重新运行的时候再次清理

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

优:

并发收集,低停顿

CMS的问题

CPU占用大

只回收old区,young区需要其他回收器配合

Memory Fragmentation 内存碎片问题: 因为标记清除会产生碎片化,如果老年代已经没有地方可以装了(可能在老生代总内存大小足够的情况下,却不能容纳新生代的晋升行为(由于没有连续的内存空间可用),导致触发FullGC)CMS会请出Serial Old让它来进行清理,Serial Old是单线程的,效率很低

-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩

解决方案:

  • 增大Xmx或者减少Xmn
  • 在应用访问量最低的时候,在程序中主动调用System.gc(),比如每天凌晨。
  • 在应用启动并完成所有初始化工作后,主动调用System.gc(),它可以将初始化的数据压缩到一个单独的chunk中,以腾出更多的连续内存空间给新生代晋升使用。
  • 降低-XX:CMSInitiatingOccupancyFraction参数以提早执行CMSGC动作,虽然CMSGC不会进行内存碎片的压缩整理,但它会合并老生代中相邻的free空间。这样就可以容纳更多的新生代晋升行为。

Floating Garbage 浮动垃圾问题(并发清理阶段产生的):老年代满了,浮动垃圾没有清理完。这时会请出Serial Old让它来进行清理

Concurrent Mode Failure
产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped
这个现象在日志里打印出来是PromotionFailed

CMS是如何解决三色标记法漏标问题:

CMS采用写屏障+增量更新(IncrementalUpdate(增量更新)算法)方式

满足一个条件(灰色对象与白色对象断开连接),在并发标记阶段当我们黑色对象(B)引用关联白色对象(E),记录下B黑色对象。
在重新标记阶段(所有用户线程暂停),有将B对象变为灰色对象将整个引用链全部扫描。
缺点:遍历B整个链的效率非常低,有可能会导致用户线程等待的时间非常长。

47f286ebf5ff47b6a877bdcafbb8ca94.png

G1

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

Java Hotspot G1 GC的一些关键技术 - 美团技术团队 (meituan.com)

它具备一下特点:

优:从业务的角度考虑,G1优点不言而喻,分代收集,不容易产生内存碎片化,一次回收比较彻底,不容易Full gc,并发标记,停顿时间相对可控(基于停顿时间和region占用大小判断是否有价值回收)。

  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
  • 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念(逻辑分区)。
  • G1 GC是一个响应时间优先的GC算法,它与CMS最大的不同是,用户可以设定整个GC过程的期望停顿时间,参数-XX:MaxGCPauseMillis指定一个G1收集过程目标停顿时间,默认值200ms,不过它不是硬性条件,只是期望值。

    G1根据这个模型统计计算出来的历史数据来预测本次收集需要选择的Region数量,从而尽量满足用户设定的目标停顿时间。

  • G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。

    • 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
    • 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

垃圾优先的垃圾回收器:
G1把内存空间分为一块一块的region,每一块 region 有自己的逻辑分代。当G1垃圾回收器发现有必要进行垃圾回收的时候,它会优先回收存活对象最少的region,也就是垃圾最多的region。这就是“垃圾优先”。

分区概念(对象分配)

G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂(1,2,4,8,16,32)),默认将整堆划分为2048个分区。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

大对象

Humongous 区域主要是为存储超过 50% 标准 region 大小的对象设计,它用来专门存放巨型对象。如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

G1 也是有 FGC的,对象分配不下的时候,就会产生FGC。

小对象

G1默认启用了UseTLAB优化,创建对象(小对象)时,优先从TLAB中分配内存,如果分配失败,说明当前TLAB的剩余空间不满足分配需求,则调用allocate_new_tlab方法重新申请一块TLAB空间,之前都是从eden区分配,G1需要从eden region中分配,不过也有可能TLAB的剩余空间还比较大,JVM不想就这么浪费掉这些内存,就会从eden region中分配内存。

yongGC

 younggc 会回收所有Eden 、以及 Survivor 区,并且将存活对象复制到 Old 区以及另一部分的 Survivor 区。

第一阶段:扫描根

跟 CMS 类似,Stop the world,扫描 GC Roots 对象;

第二阶段:处理 Dirty card,更新 RSet

处理 dirty card queue 中的 card,更新 RSet。此阶段完成后,RSet 可以准确的反映老年代对所在的内存分段中对象的引用。

第三阶段:扫描 RSet

扫描 RSet 中,所有 old 区->扫描到的 young 区或者 survivor 区的引用;

第四阶段:复制扫描出的存活的对象到 survivor2/old 区

Eden 区内存段中存活的对象会被复制到 Survivor 区中空的内存分段,Survivor 区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到 old 区中空的内存分段。如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间。

第五阶段:处理引用队列、软引用、弱引用、虚引用

处理 Soft,Weak,Phantom,Final,JNI Weak 等引用。

最终 Eden 空间的数据为空,GC 停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

mixGC

对 Old Regions 的收集会同时涉及若干个 Young 和 Old Regions,因此被称为 Mixed GC。

Mixed GC 很多地方都和 Young GC 类似,不同之处是:它还会选择若干最有潜力的 Old Regions(收集垃圾的效率最高的 Regions),这些选出来要被 Evacuate 的 Region 称为本次的 Collection Set (CSet)。

这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些 old region 进行收集,从而可以对垃圾回收的耗时时间进行控制。

结合Region 的设计,只要把每次的 Collection Set 规模控制在一定范围,就能把每次收集的停顿时间软性地控制在 MaxGCPauseMillis 以内。起初这个控制可能不太精准,随着 JVM 的运行估算会越来越准确。

那来不及收集的那些 Region 呢?多来几次就可以了。所以你在 GC 日志中会看到 continue mixed GCs 的字样,代表分批进行的各次收集。这个过程会多次重复,直到垃圾的百分比降到 G1HeapWastePercent 以内,或者到达 G1MixedGCCountTarget 上限。

G1的运行过程分为以下四个步骤:

初始标记(Initial Marking)(Stop The World):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。

并发标记( Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫

描完成以后,并发时有引用变动的对象会产生漏标问题,G1中会使用SATB(snapshot-at-the-beginning)算法来解决,后面会详细介绍。

最终标记(Final Marking)(Stop The World):对用户线程做一个短暂的暂停,用于处理并发标记阶段仍遗留下来的最后那少量的SATB记录(漏标对象)。

筛选回收(Live Data Counting and Evacuation)(Stop The World):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多个收集器线程并行完成的。

FullGC

Serial Old GC

如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的。

Serial Old是Serial收集器的老年代版本,是一个单线程收集器,使用标记-整理算法。

如果G1产生FGC,你应该做什么?

1. 扩内存
2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
3. 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)

TAMS是什么?

要达到GC与用户线程并发运行,必须要解决回收过程中新对象的分配,所以G1为每一个Region区域设计了两个名为TAMS(Top at Mark Start)的指针,从Region区域划出一部分空间用于记录并发回收过程中的新对象。这样的对象认为它们是存活的,不纳入垃圾回收范围。

G1是如何解决三色标记法漏标问题:

采用SATB(snapshot-at-the-beginning)原始快照方式,在初始标记时做一个快照,当B和C之间的引用消失时要把这个引用推到GC的堆栈,保证C还能被GC扫描到,在最终标记阶段扫描STAB记录。

5798e477275c402ea6ee6e8b8e3fe91f.png

好处:如果假设B(黑色对象)引入该白色对象的时候,无需做任何遍历效率是非常高。
缺点:如果假设B(黑色对象) 没有引入该白色对象的时候,该白色对象在本次GC继续存活,只能放在下一次GC在做并发标记的时候清理。
tips:以浮动垃圾(占内存空间)换让我们用户线程能够暂停的时间更加短。

CMS和G1的漏标问题解决及三色标记算法图解 - 简书 (jianshu.com)

记忆集与卡表

跨代引用:堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,那么回收新生代的话,需要扫描所有从老年代到新生代的所有引用,所以要避免每次YGC时扫描整个老年代,减少开销。

记忆集(RSet,Remembered Set):

用来记录从其他Region中的对象到本Region的引用,是一种抽象的数据结构。每一个Region都设有一个RSet,有了这个数据结构,在回收某个Region的时候,就不必对整个堆内存的对象进行扫描了,它使得部分收集成为了可能。

对于年轻代的Region,它的RSet只保存了来自老年代的引用,这是因为年轻代的回收是针对所有年轻代Region的,没必要画蛇添足。所以说年轻代Region的RSet有可能是空的。

而对于老年代的Region来说,它的RSet也只会保存老年代对它的引用。这是因为老年代回收之前,会先对年轻代进行回收。这时,Eden区变空了,而在回收过程中会扫描Survivor分区,所以也没必要保存来自年轻代的引用。

RSet通常会占用很大的空间,大约5%或者更高(最高可能20%)。不仅仅是空间方面,很多计算开销也是比较大的。

RSet究竟是怎么辅助GC的呢?

在做YGC的时候,只需要选定年轻代的RSet作为GC ROOTs,这些RSet记录了old->young的跨代引用,避免了扫描整个老年代。 而mixed gc的时候,老年代中记录了old->old的RSet,young->old的引用从Survivor区获取(老年代回收之前,会先对年轻代进行回收,存活的对象放在Survivor区),这样也不用扫描全部老年代,所以RSet的引入大大减少了GC的工作量。

分代

整个年轻代内存会在初始空间-XX:G1NewSizePercent(默认整堆5%)与最大空间-XX:G1MaxNewSizePercent(默认60%)之间动态变化,且由参数目标暂停时间-XX:MaxGCPauseMillis(默认200ms)、需要扩缩容的大小以及分区的已记忆集合(RSet)计算得到。当然,G1依然可以设置固定的年轻代大小(参数-XX:NewRatio、-Xmn),但同时暂停目标将失去意义。

收集集合 (CSet)

年轻代收集CSet只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到CSet中。

候选老年代分区的CSet准入条件,可以通过活跃度阈值-XX:G1MixedGCLiveThresholdPercent(默认85%)进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比-XX:G1OldCSetRegionThresholdPercent(默认10%)设置数量上限。

安全点与安全区域

用户线程暂停,GC线程要开始工作,但是要确保用户线程暂停的这行字节码指令是不会导致引用关系的变化。所以JVM会在字节码指令中,选一些指令,作为“安全点”,比如方法调用、循环跳转、异常跳转等,一般是这些指令才会产生安全点。

为什么它叫安全点,是这样的,GC时要暂停用户线程,并不是抢占式中断(立马把业务线程中断)而是主动式中断。

主动式中断是设置一个标志,这个标志是中断标志,各用户线程在运行过程中会不停的主动去轮询这个标志,一旦发现中断标志为 True,就会在自己最近的“安全点”上主动中断挂起。

为什么需要安全区域?要是用户线程都不执行(用户线程处于Sleep或者是Blocked状态),那么程序就没办法进入安全点,对于这种情况,就必须引入安全区域。安全区域是指能够确保在某一段代码片段之中, 引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。

当用户线程执行到安全区域里面的代码时,首先会标识自己已经进入了安全区域,这段时间里JVM要发起GC就不必去管这个线程了。

当线程要离开安全区域时,它要检查JVM是否已经完成了根节点枚举或者其他GC中需要暂停用户线程的阶段:

1、如果完成了,那线程就当作没事发生过,继续执行。

2、否则它就必须一直等待,直到收到可以离开安全区域的信号为止。

JVM从入门到精通(十):垃圾回收算法串讲:CMS,G1,三色标记算法_cms remembered set_寒泉Hq的博客-CSDN博客

G1和CMS对比

1、G1 相比较 CMS的改进

  • 算法: G1 基于标记--整理算法, 不会产生空间碎片,在分配大对象时,不会因无法得到连续的空间,而提前触发一次 FULL GC 。
  • 停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
  • 并行与并发:G1 能更充分的利用 CPU 多核环境下的硬件优势,来缩短 stop the world 的停顿时间。

2、CMS 和 G1 的区别

  • G1 在回收内存后,会立即同时做合并空闲内存的工作;而 CMS ,则默认是在 STW(stop the world)的时候做。
  • G1 会在 Young GC 中使用;而 CMS 只能在 O 区使用。
  • CMS的并发回收是与用户线程一起进行的,不需要停顿。如果有垃圾是在标记过程后产生的,那么就会产生一个回收不彻底的问题。设想一下一边打扫房间,一边有人在那扔垃圾,那么最后的打扫效果是不是不好?而G1的筛选回收是需要暂停用户线程,但是是并发回收的,所以速度快,而且回收效率高。

  • G1 可以独立管理整个java堆。而且回收算法是采用的标记整理(局部是复制清除算法,因为young generation中采用复制清除效率更高),不会产生内存碎片的问题。对比CMS只是可怜的标记清除,在一些高qps场景的服务中,内存碎片化很严重,很容易造成Full Gc!

  • java堆在使用这两款回收器的时候,内存布局不同。在使用CMS收集器时堆被分为 PermGen,YoungGen,OldGen ;而 YoungGen 又分了两个 survivo 区域。新生代和老年代是物理隔离的(为两个不同的连续的内存区域)。而用G1是对整个Heap划分成若干个不同的Region,在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。新生代和老年代是相互交叉的内存区域,是逻辑上的分类。这样划分的目的是规避每次在对象进行可达性分析都要在堆的全区域中进行。比如一个Collection Set中的每个region都对应着一个Remembered set,G1可以维护这个Remembered Set,从中挑选出最具回收性价比的region进行回收,G1嘛,就是Grabage First,提高回收效率。

因此通过对比发现,从业务的角度考虑,G1优点不言而喻,分代收集,不容易产生内存碎片化,一次回收比较彻底,不容易Full gc,并发标记,停顿时间相对可控(基于停顿时间和region占用大小判断是否有价值回收)

ZGC

12 张图带你彻底理解 ZGC (qq.com)

GC常用参数

​​​​​​​(SUB)常见JVM参数-CSDN博客

JVM调优

问题定位(jmap jstack,stat)

jstack简单使用,定位死循环、线程阻塞、死锁等问题 - 风一样的码农 - 博客园 (cnblogs.com)

jvm性能监控、故障处理命令行工具详解(jps、jstat、jinfo、jmap、jhat、jstack)(宝藏博文)_sun jdk 监控和故障处理命令有 jps jstat jmap jhat jstack jinf-CSDN博客

调优前的基础概念:

所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量...

问题:

科学计算,吞吐量。数据挖掘,thrput。吞吐量优先的一般:(PS + PO)

响应时间:网站 GUI API (1.8 G1)

什么是调优?

调优,从规划开始

优化环境

解决JVM运行中的问题

一个案例理解常用工具

jconsole远程连接

jvisualvm远程连接

https://www.cnblogs.com/liugh/p/7620336.html (简单做法)

jprofiler (收费)

arthas在线排查工具

案例汇总

OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上面案例)

    1. 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)

    2. 响应时间:STW越短,响应时间越好

    3. 根据需求进行JVM规划和预调优

    4. 优化运行JVM运行环境(慢,卡顿)

    5. 解决JVM运行过程中出现的各种问题(OOM)

    • 调优,从业务场景开始,没有业务场景的调优都是耍流氓

    • 无监控(压力测试,能看到结果),不调优

    • 步骤:

      1. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)

        1. 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)

        2. 吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]

      2. 选择回收器组合

      3. 计算内存需求(经验值 1.5G 16G)

      4. 选定CPU(越高越好)

      5. 设定年代大小、升级年龄

      6. 设定日志参数

        1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause

        2. 或者每天产生一个日志文件

      7. 观察日志情况

    • 案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?

      这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G)

      1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒)

      经验值,

      非要计算:一个订单产生需要多少内存?512K * 1000 500M内存

      专业一点儿问法:要求响应时间100ms

      压测!

    • 案例2:12306遭遇春节大规模抢票应该如何支撑?

      12306应该是中国并发量最大的秒杀网站:

      号称并发量100W最高

      CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器

      普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款

      12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款

      减库存最后还会把压力压到一台服务器

      可以做分布式本地库存 + 单独服务器做库存均衡

      大流量的处理方法:分而治之

    • 怎么得到一个事务会消耗多少内存?

      1. 弄台机器,看能承受多少TPS?是不是达到目标?扩容或调优,让它达到

      2. 用压测来确定

    1. 有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了

      1. 为什么原网站慢?很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢

      2. 为什么会更卡顿?内存越大,FGC时间越长

      3. 咋办?PS -> PN + CMS 或者 G1

    2. 系统CPU经常100%,如何调优?(面试高频)CPU100%那么一定有线程在占用系统资源,

      1. 找出哪个进程cpu高(top)

      2. 该进程中的哪个线程cpu高(top -Hp)

      3. 导出该线程的堆栈 (jstack)

      4. 查找哪个方法(栈帧)消耗时间 (jstack)

      5. 工作线程占比高 | 垃圾回收线程占比高

    3. 系统内存飙高,如何查找问题?(面试高频)

      1. 导出堆内存 (jmap)

      2. 分析 (jhat jvisualvm mat jprofiler ... )

    4. 如何监控JVM

      1. jstat jvisualvm jprofiler arthas top...

    5. 测试代码:

      package com.mashibing.jvm.gc;
      ​
      import java.math.BigDecimal;
      import java.util.ArrayList;
      import java.util.Date;
      import java.util.List;
      import java.util.concurrent.ScheduledThreadPoolExecutor;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      ​
      /**
       * 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
       */
      ​
      public class T15_FullGC_Problem01 {
      ​
          private static class CardInfo {
              BigDecimal price = new BigDecimal(0.0);
              String name = "张三";
              int age = 5;
              Date birthdate = new Date();
      ​
              public void m() {}
          }
      ​
          private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
                  new ThreadPoolExecutor.DiscardOldestPolicy());
      ​
          public static void main(String[] args) throws Exception {
              executor.setMaximumPoolSize(50);
      ​
              for (;;){
                  modelFit();
                  Thread.sleep(100);
              }
          }
      ​
          private static void modelFit(){
              List<CardInfo> taskList = getAllCardInfo();
              taskList.forEach(info -> {
                  // do something
                  executor.scheduleWithFixedDelay(() -> {
                      //do sth with info
                      info.m();
      ​
                  }, 2, 3, TimeUnit.SECONDS);
              });
          }
      ​
          private static List<CardInfo> getAllCardInfo(){
              List<CardInfo> taskList = new ArrayList<>();
      ​
              for (int i = 0; i < 100; i++) {
                  CardInfo ci = new CardInfo();
                  taskList.add(ci);
              }
      ​
              return taskList;
          }
      }
      ​
    6. java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01

    7. 一般是运维团队首先受到报警信息(CPU Memory)

    8. top命令观察到问题:内存不断增长 CPU占用率居高不下

    9. top -Hp 观察进程中的线程,哪个线程CPU和内存占比高

    10. jps定位具体java进程jstack 定位线程状况,重点关注:WAITING BLOCKEDeg.waiting on <0x0000000088ca3310> (a java.lang.Object)假如有一个进程中100个线程,很多线程都在waiting on <xx> ,一定要找到是哪个线程持有这把锁怎么找?搜索jstack dump的信息,找<xx> ,看哪个线程持有这把锁RUNNABLE作业:1:写一个死锁程序,用jstack观察 2 :写一个程序,一个线程持有锁不释放,其他线程等待

    11. 为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称怎么样自定义线程池里的线程名称?(自定义ThreadFactory)

    12. jinfo pid

    13. jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)jstat -gc 4655 500 : 每个500个毫秒打印GC的情况如果面试官问你是怎么定位OOM问题的?如果你回答用图形界面(错误)1:已经上线的系统不用图形界面用什么?(cmdline arthas)2:图形界面到底用在什么地方?测试!测试的时候进行监控!(压测观察)

    14. jmap - histo 4655 | head -20,查找有多少对象产生

    15. jmap -dump:format=b,file=xxx pid :

      线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件2:<font color='red'>很多服务器备份(高可用),停掉这台服务器对其他服务器不影响</font>3:在线定位(一般小点儿公司用不到)

    16. java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01

    17. 使用MAT / jhat /jvisualvm 进行dump文件分析 https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html jhat -J-mx512M xxx.dumphttp://192.168.17.11:7000拉到最后:找到对应链接可以使用OQL查找特定问题对象

    18. 找到代码的问题

    19. 程序启动加入参数:

      java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX
    20. 如果遭遇 Local host name unknown:XXX的错误,修改/etc/hosts文件,把XXX加入进去

      192.168.17.11 basic localhost localhost.localdomain localhost4 localhost4.localdomain4
      ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    21. 关闭linux防火墙(实战中应该打开对应端口)

      service iptables stop
      chkconfig iptables off #永久关闭
    22. windows上打开 jconsole远程连接 192.168.17.11:11111

    • 为什么需要在线排查?在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因。为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出入参,然后重新打包发布,如果打了日志还是没找到问题,继续加日志,重新打包发布。对于上线流程复杂而且审核比较严的公司,从改代码到上线需要层层的流转,会大大影响问题排查的进度。

    • jvm观察jvm信息

    • thread定位线程问题

    • dashboard 观察系统情况

    • heapdump + jhat分析

    • jad反编译动态代理生成类的问题定位第三方的类(观察代码)版本问题(确定自己最新提交的版本是不是被使用)

    • redefine 热替换目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性m() -> mm()

    • sc - search class

    • watch - watch method

    • 没有包含的功能:jmap

    1. 硬件升级系统反而卡顿的问题(见上)

    2. 线程池不当运用产生OOM问题(见上)不断的往List里加对象(实在太LOW)

    3. smile jira问题实际系统不断重启解决问题 加内存 + 更换垃圾回收器 G1真正问题在哪儿?不知道

    4. tomcat http-header-size过大问题(Hector)

    5. lambda表达式导致方法区溢出问题(MethodArea / Perm Metaspace)LambdaGC.java -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails

      "C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\lib\idea_rt.jar=49316:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\work\ijprojects\JVM\out\production\JVM;C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar" com.mashibing.jvm.gc.LambdaGC
      [GC (Metadata GC Threshold) [PSYoungGen: 11341K->1880K(38400K)] 11341K->1888K(125952K), 0.0022190 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
      [Full GC (Metadata GC Threshold) [PSYoungGen: 1880K->0K(38400K)] [ParOldGen: 8K->1777K(35328K)] 1888K->1777K(73728K), [Metaspace: 8164K->8164K(1056768K)], 0.0100681 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
      [GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] 1777K->1777K(73728K), 0.0005698 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
      [Full GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1777K->1629K(67584K)] 1777K->1629K(105984K), [Metaspace: 8164K->8156K(1056768K)], 0.0124299 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
      java.lang.reflect.InvocationTargetException
          at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:388)
          at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:411)
      Caused by: java.lang.OutOfMemoryError: Compressed class space
          at sun.misc.Unsafe.defineClass(Native Method)
          at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63)
          at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
          at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394)
          at java.security.AccessController.doPrivileged(Native Method)
          at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393)
          at sun.reflect.MethodAccessorGenerator.generateSerializationConstructor(MethodAccessorGenerator.java:112)
          at sun.reflect.ReflectionFactory.generateConstructor(ReflectionFactory.java:398)
          at sun.reflect.ReflectionFactory.newConstructorForSerialization(ReflectionFactory.java:360)
          at java.io.ObjectStreamClass.getSerializableConstructor(ObjectStreamClass.java:1574)
          at java.io.ObjectStreamClass.access$1500(ObjectStreamClass.java:79)
          at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:519)
          at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:494)
          at java.security.AccessController.doPrivileged(Native Method)
          at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:494)
          at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:391)
          at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1134)
          at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
          at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
          at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
          at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
          at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
          at javax.management.remote.rmi.RMIConnectorServer.encodeJRMPStub(RMIConnectorServer.java:727)
          at javax.management.remote.rmi.RMIConnectorServer.encodeStub(RMIConnectorServer.java:719)
          at javax.management.remote.rmi.RMIConnectorServer.encodeStubInAddress(RMIConnectorServer.java:690)
          at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java:439)
          at sun.management.jmxremote.ConnectorBootstrap.startLocalConnectorServer(ConnectorBootstrap.java:550)
          at sun.management.Agent.startLocalManagementAgent(Agent.java:137)
      ​
    6. 直接内存溢出问题(少见)《深入理解Java虚拟机》P59,使用Unsafe分配直接内存,或者使用NIO的问题

    7. 栈溢出问题-Xss设定太小

    8. 比较一下这两段程序的异同,分析哪一个是更优的写法:

      Object o = null;
      for(int i=0; i<100; i++) {
          o = new Object();
          //业务处理
      }
      for(int i=0; i<100; i++) {
          Object o = new Object();
      }
    9. 重写finalize引发频繁GC小米云,HBase同步系统,系统通过nginx访问超时报警,最后排查,C++程序员重写finalize引发频繁GC问题为什么C++程序员会重写finalize?(new delete)finalize耗时比较长(200ms)

    10. 如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的?System.gc() (这个比较Low)

    11. Distuptor有个可以设置链的长度,如果过大,然后对象大,消费完不主动释放,会溢出 (来自 死物风情)

    12. 用jvm都会溢出,mycat用崩过,1.6.5某个临时版本解析sql子查询算法有问题,9个exists的联合sql就导致生成几百万的对象(来自 死物风情)

    13. new 大量线程,会产生 native thread OOM,(low)应该用线程池,解决方案:减少堆空间(太TMlow了),预留更多内存产生native threadJVM内存占物理内存比例 50% - 80%

哪些情况会导致Full GC

1、System.gc()方法的调用

2、老年代代空间不足

3、永生区空间不足

5、统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间

6、堆中分配很大的对象

JVM什么情况下会触发Full GC(Major GC)

1. 旧生代空间不足 旧生代空间只有在新生代对 象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

2. Permanet Generation(永久代)空间满 PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息: java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

3. CMS GC时出现promotion failed(担保:s1s2--old)和concurrent mode failure 对于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。 promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。 应对措施为:增大survivorspace、旧生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。

4. 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间 这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。 例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值