Marvel-Site Marvel-Site
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)

Marvel

吾必当乘此羽葆盖车
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)
  • Java

    • Java基础

    • Java进阶

    • Java容器

    • Java并发编程

    • Java虚拟机

      • JVM与Java体系结构
      • JVM内存区域详解
      • JVM类的加载过程详解
      • JVM创建对象过程
      • JVM垃圾回收详解
        • 对象死亡
          • 判断对象死亡的方法
          • 对象可以被回收,就代表一定被回收吗?
          • 如何判断一个常量是废弃常量?
          • 如何判断一个类是无用的类?
        • 垃圾回收算法
          • 垃圾收集有哪些算法,各自的特点?
          • HotSpot 为什么要分为新生代和老年代?
          • 并行收集、并发收集、吞吐量
        • 垃圾回收器
          • 常见的垃圾回收器
          • 查看默认垃圾收集器
          • JVM垃圾回收器参数说明
          • Serial收集器
          • ParNew收集器
          • Parallel Scavenge收集器
          • Serial Old收集器
          • Parallel Old收集器
          • CMS收集器
          • G1收集器
          • G1和CMS比较
          • JDK默认的垃圾收集器
          • 垃圾收集器组合选择
        • 垃圾回收过程
          • JVM GC + SpringBoot 微服务的生产部署和调参优化
          • Minor GC 、Major GC、 Full GC 有什么不同呢?
          • 对象如何晋升到老年代?
          • JVM中一次完整的GC流程
          • Full GC 会导致什么?
          • JVM什么时候触发 GC,如何减少 Full GC 的次数?
          • 如何减少 GC 出现的次数?
          • 方法区会进行垃圾回收吗?
      • JVM参数设置
      • JVM性能调优工具
      • 内存泄漏与内存溢出
    • 常见面试题

  • 计算机基础

  • 框架|中间件

  • 架构

  • 后端
  • Java
  • Java虚拟机
Marvel
2022-07-13
目录

JVM垃圾回收详解

# JVM垃圾回收

# 对象死亡

# 判断对象死亡的方法

  • 引用计数法:给对象添加一个计数器,每当有一个地方引用它就加 1,当引用失效就减 1。任何时刻计数器为零的对象就不再能被使用了。该方法简单、效率高。但主流的虚拟机都没有使用它,因为它有一个致命的缺点,很难解决对象循环引用的问题。
  • 可达性分析算法:就是通过一系列的”GC Roots“作为对象引用的起点,不断的向下搜索,这些节点组成的路径成为引用链,当一个对象不在任何一条引用链上时,就说明这个对象是不可用的,需要被回收。GC Roots 主要包含:
    • 虚拟机栈(栈中的局部变量表)引用的对象
    • 本地方法栈(Native 方法)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 所有被同步锁(synchronaized 关键字)持有的对象

# 对象可以被回收,就代表一定被回收吗?

即使可达性分析法中不可达的对象,也不会被立刻回收,真正要回收还要经历两次标记过程。第一次标记时会进行筛选,筛选的条件是此对象是否要执行 finalize 方法,当对象没有覆盖 finalize 方法或 finalize 方法已经被调用过时,虚拟机将不执行筛选。

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

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

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

运行时常量池在方法区内,方法区在元空间内;

JDK1.6:字符串常量池在方法区中 JDK1.7:字符串常量池在堆中 JDK1.8:字符串常量池在方法去中,但是此时方法区的实现为元空间。

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

需要满足以下条件:

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

满足上述3个条件的无用类可以被回收,但是并不一定被回收。

# 垃圾回收算法

# 垃圾收集有哪些算法,各自的特点?

  1. 标记-清除算法:首先标记出不需要回收的对象,统一回收掉没有被标记的对象。
    • 标记清除后会产生大量不连续的碎片,空间碎片太多可能会导致分配较大对象的时候,无法找到足够的连续内存而不得不触发一次垃圾收集。
    • 适合在老年代进行垃圾回收,如CMS就采用该方法。
  2. 标记-复制算法:将内存分为大小相同的两块,每次只使用其中一块。这一块内存使用完后,将存活的对象复制到另一块去,再把使用的空间清理掉。
    • 没有内存碎片,继续移动堆顶指针,按顺序分配内存。
    • 适合新生代去进行垃圾回收,serial new、parallel new采用该算法。
  3. 标记-整理算法:根据老年代的特点提出的算法。标记不需要回收的对象,让所有存活的对象移动到一端,然后直接清理掉端边界以外的内存。
    • 不会产生空间碎片,但会花一定时间整理。
    • 适合在老年代进行垃圾回收,如Parallel scanvange、Serial old就采用该算法。
  4. 分代收集算法:java堆分为新生代和老年代,根据各个年代的特点选择合适的垃圾收集算法。新生代,每次收集都要大量对象死去,可以使用标记-复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。老年代,对象生成几率比较高,而且没有额外的空间对他们进行分配担保,所以使用标记-清除或标记-整理算法进行垃圾回收。

标记-复制算法正常要使用两个内存相等的区域,但新生代将其划分为一块较大的Eden区和两块较小的Survivor。当垃圾回收的时候,将Eden和其中一个Survivor中还存活的对象一次性的复制到另一块Survivor空间,最后清理掉Eden和刚使用过的Survivor空间。Eden:Survivor的大小比例为8:1,所以需要老年代进行分配担保。

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

java堆分为新生代和老年代,根据各个年代的特点选择合适的垃圾收集算法,提高垃圾回收的效率。新生代,每次收集都要大量对象死去,可以使用标记-复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。老年代,对象生存几率比较高,而且没有额外的空间对他们进行分配担保,所以使用标记-清除或标记-整理算法进行垃圾回收。

# 并行收集、并发收集、吞吐量

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

并发收集:指用户线程与垃圾线程同时工作(不一定是并行,可能是交替进行)。

吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值。 $$ 吞吐量=\frac{运行用户代码时间}{运行用户代码时间+垃圾收集时间} $$

# 垃圾回收器

# 常见的垃圾回收器

image-20220622195629074

可以分为四大类:串行、并行、CMS、G1

新生代垃圾回收器:Serial、ParNew、Parallel Scavenge

老年代垃圾回收器:CMS、Serial Old、Parallel Old

整堆垃圾回收器:G1

在源码里可以看到6种(没有 SerialOld,被淘汰了)

image-20220622192316366

# 查看默认垃圾收集器

命令行输入:

>java -XX:+PrintCommandLineFlags -version
1

输出:

-XX:InitialHeapSize=267755904 -XX:MaxHeapSize=4284094464 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_321"
Java(TM) SE Runtime Environment (build 1.8.0_321-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.321-b07, mixed mode)
1
2
3
4

可以看到使用的是-XX:+UseParallelGC

# JVM垃圾回收器参数说明

  • DefNew:Default New Generation

  • Tenured:Old

  • ParNew:Parallel New Generation

  • PSYoungGen:Parallel Scavenge

  • ParOldGen:Parallel Old Generation

# Serial收集器

单线程垃圾收集器,在进行垃圾收集工作时必须要暂停其他所有工作线程(Stop The World),直到收集结束。

  • 优点:简单高效,单线程,没有线程交互的开销。
  • 缺点:Stop The World带来不良用户体验;
  • 应用场景:适用于Client模式下的虚拟机
  • JVM参数:-XX:+UseSerialGC,开启后会使用Serial + Serial Old收集器组合

img

# ParNew收集器

Serial收集器的多线程版本。除了使用多线程进行垃圾回收,其余行为与Serial收集器一样。

  • 特点:多线程
  • 应用场景:Server模式下的虚拟机
  • 与CMS收集器配合工作
  • JVM参数:-XX:+UserParNewGC,开启后会使用ParNew + Serial Old收集器组合,但是Java8不推荐这么使用
  • ParNew

img

# Parallel Scavenge收集器

关注吞吐量,也称吞吐量优先收集器。

  • 特点:采用复制算法,并行的多线程收集器
  • 可控制吞吐量。高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务。
  • 自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,可自动设置新生代空间比例、晋升老年对象年龄,动态设置这些参数以提供最优的停顿时间和最高的吞吐量。
  • JDK1.8默认收集器,Parallel + Parallel Old收集器组合
  • JVM参数:-XX:+UseParallelGC 或 -XX:+UseParallelOldGC (可互相激活) ,-XX:ParallelGCThread=N,启动N个GC线程

# Serial Old收集器

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

  • 特点:单线程收集器,采用标记-整理算法。

# Parallel Old收集器

Parallel Old,Parallel Scavenge收集器的老年代版本。

  • 特点:多线程,采用标记-整理算法。
  • 应用场景:注重高吞吐量以及CPU资源敏感的场合。

# CMS收集器

CMS(Concurrent Mark Sweep)收集器

  • 以获取最短停顿时间为目标。非常符合在注重用户体验的应用上使用。

  • 第一款真正意义上的并发收集器,让垃圾收集线程与用户线程同时工作。

  • 适合应用在互联网站或BS系统的服务器上,这类应用重视服务器的响应速度,希望系统停顿时间最短。

  • 适合堆内存大、CPU核数多的服务器端应用。

  • 使用标记-清除算法,分为四个步骤:

    image-20220622212928619
    • 初始标记:暂停所有其他线程,并记录下直接与GC Roots相关联的对象,速度很快。
    • 并发标记:同时开启GC和用户线程,去记录可达对象。但在这个阶段结束,并不能保证记录当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性,但这个算法会跟踪记录引用更新变化的地方。
    • 重新标记:修正并发标记期间,因为用户程序运行而导致标记变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,比并发标记阶段的时间短。
    • 并发清除:开启用户线程,同时GC线程开始对未标记的区域做清扫。
  • 优点:并发收集、低停顿

  • 缺点:

    • 对CPU资源非常敏感,在并发阶段,它虽然不会导致用户线程停顿,但会因为占用一部分线程而导致应用程序变慢,总吞吐量会降低。
    • 无法处理浮动垃圾,CMS并发清理阶段用户线程还在运行,伴随着程序运行自然会有新的垃圾不断产生,这部分垃圾产生在标记之后,CMS无法处理他们,只能留到下一次GC中再清理,这就是浮动垃圾。
    • 使用标记-清除算法,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
  • JVM参:-XX:+UserConcMarkSweepGc 开启该参数会自动将-XX:+UserParNewGC打开。使用ParNew(Young区)+CMS(Old区)+Serial Old(CMS出错后备收集器)的收集器组合。

# G1收集器

G1(Garbage-First),是面向服务器的垃圾收集器,抛弃分代的概念,将堆内存划分为大小固定的几个独立区域,并维护一个优先级列表,在垃圾回收过程中根据允许的最长垃圾回收时间,优先回收垃圾最多的区域。

image-20220622220521799

H:专门存放大对象

优点:

  • 基于标记整理算法,不会产生空间碎片
  • 可精确的控制停顿时间,在不牺牲吞吐量的前提下实现短停顿垃圾回收。

JVM参数:-XX:+UseG1GC,可通过参数-XX:G1HeapRegionSize=n指定分区大小(1MB~32MB,必须是2的幂),默认2047个分区

设置停顿时间:-XX:MaxGCPauseMillis=100,最大GC停顿时间(毫秒),JVM将尽可能停顿小于这个时间

针对于Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集 + 形成连续的内存块,避免内存碎片

  • Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会晋升到Old区
  • Survivor区数据移动到新的Survivor区,部分晋升到Old区
  • 最后Eden区收拾干净,GC结束,用户应用程序继续执行。
image-20220622221203748 image-20220622221219368

回收的四个步骤:

  • 初始标记:只标记GC Roots能直接关联到的对象
  • 并发标记:进行GC Roots Tracing的过程
  • 最终标记:修正并发标记期间,因程序运行导致标记发生变化的那部分对象
  • 筛选回收:根据时间来进行价值最大化的回收
image-20220622221410676

G1为什么能建立可预测的停顿时间模型?

因为它有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。这样就保证了在有限的时间内可以获取尽可能高的收集效率。

G1与其他收集器的区别?

其他收集器的工作范围是整个新生代或老年代,G1收集器的工作范围是整个Java堆。使用G1收集器时,它将整个Java堆划分成多个大小相等的独立区域Region。虽然也保留了新生代、老年代的概念,但新生代和老年代不再是相互隔离的,他们都是一部分Region的集合。

G1收集器存在的问题?

Region不可能是孤立的,分配在Region中的对象可以与Java堆中的任意对象发生引用关系。在采用可达性分析算法来判断对象是否存活时,得扫描整个Java堆才能保证准确性。其他收集器也存在这种问题,G1更加突出而已。这回导致Minor GC效率下降。

# G1和CMS比较

没有垃圾碎片、G1可以精确的控制停顿时间

# JDK默认的垃圾收集器

jdk1.6:Parallel Scavenge(新生代) + Serial Old (老年代)

jdk1.7:Parallel Scavenge(新生代) + Parallel Old(老年代)

jdk1.8:Parallel Scavenge(新生代) + Parallel Old(老年代)

jdk1.9:G1

# 垃圾收集器组合选择

  • 单CPU或小内存,单机程序

    -XX:+UseSerialGC

  • 多CPU,需要最大吞吐量,如后天计算型应用

    -XX+UseParallelGC 或者 -XX:+UseParallelOldGC

  • 多CPU,最求低停顿时间,需快速响应,如互联网应用

    -XX:+UseConcMarkSweepGC 或者 -XX:+ParNewGC

参数 新生代垃圾收集器 新生代算法 老年代垃圾收集器 老年代算法
-XX:+UseSerialGC SerialGC 标记复制 SerialOldGC 标记整理
-XX:+UseParNewGC ParNew 标记复制 SerialOldGC 标记整理
-XX:+UseParallelGC/-XX:+UseParallelOldGC Parallel Scavenge 标记复制 Parallel Old 标记整理
-XX:+UseConcMarkSweepGC ParNew 标记复制 CMS + Serial Old(CMS出错后备收集器) 标记清除
-XX:+UseG1GC 整体采用标记-整理算法 局部同复制算法,不会产生内存碎片

# 垃圾回收过程

# JVM GC + SpringBoot 微服务的生产部署和调参优化

内部调优:IDEA里设置Configurations的VM参数

外部调优:java -server 各种参数 -jar jar包

# Minor GC 、Major GC、 Full GC 有什么不同呢?

  • Minor GC:清理新生代。
  • Major GC:清理老年代。
  • Full GC:清理整个堆空间,包含新生代和老年。

Minor GC:清理新生代

  1. 当JVM无法为一个新的对象分配空间时会触发Minor GC,比如Eden区满了,Survivor区满不会触发。
  2. 使用标记-复制算法。
  3. Minor GC操作时应用程序停顿导致的延迟可以忽略不计。
  4. Java对象大多具有朝生夕灭的特性,所以MInor GC非常频繁,一般回收速度也比较快。
  5. 虚拟机会给每个对象定一个对象年龄计数器,如果对象Eden出生并且第一次Minor GC后仍然存活,并且能被Survivor容纳的话,就被移动到Survivor空间,并将对象年龄设为1,对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当达到一定程度(默认为15),就会被晋升到老年代中。
  6. 在正式Minor GC前,JVM会先检查新生代中对象,是比老年代中剩余空间大还是小。
    • 老年代大的话,就直接Minor GC,survivor不够放时,老年代也够放;
    • 老年代小的时候,启用老年代分配担保规则,如果老年代中剩余空间大小,大于历次Minor GC之后剩余对象的大小,那就允许进行Minor GC,因为从概率上讲,以前放的下,这次也放的下;如果小于历次Minor GC之后剩余的大小,进行Full GC,把老年代空出来再检查。

Major GC:清理老年代

  1. 老年代满了会触发Major GC;
  2. 只有CMS收集器会单独收集老年代;其他收集器都不支持单独回收老年代。
  3. 会比Minor GC慢10倍。

Full GC:清理新生代、老年代和方法区,产生条件如下:

  1. 调用 System.gc 时,系统建议执行 Full GC,但不一定执行。
  2. 老年代空间不足。
  3. 方法区空间不足,类卸载
  4. 通过 Minor GC 后进入老年代的空间大于老年代的可用内存。
  5. 内存空间担保。

# 对象如何晋升到老年代?

虚拟机给每个对象定义了一个对象年龄 Age 计数器,存储在对象头中。对象通常在 Eden 区里诞生,经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,该对象会被移动到Survivor空间中,并且将其对象年龄设为1岁。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,年龄增加到默认值,就会晋升到老年代中。

如果 Survivor 区中相同年龄的对象大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象直接进入老年去。

# JVM中一次完整的GC流程

新创建的对象会被分配到新生代中,常用的新生代垃圾回收器是 ParNew 垃圾回收器,它按照 8:1:1 将新生代分为 Eden 区以及两个Survivor 区。某一时刻,创建的对象将Eden区全部挤满,此时就触发了Minor GC。

在 Minor GC 之前,JVM 会先检查新生代中的对象,是比老年代中剩余空间大还是小。

  • 老年代剩余空间大于新生代中的对象,直接 Minor GC,GC 完 survivor 不够放,老年代也够放。
  • 老年代剩余空间小于新生代中的对象,开启老年代空间分配担保规则(要设置启用这个规则)。
    • 老年代剩余空间大于历次 Minor GC之后剩余对象的大小,进行 Minor GC。(以前放的下,这次也应该放的下)
    • 老年代剩余空间小于历次 Minor GC之后剩余对象的大小,进行 Full GC,把老年代空出来再检查。

开启老年代空间分配担保规则后,进行了 Minor GC 会出现三种情况:

  • Minor GC之后的对象够放在 Survivor 区,GC 结束。
  • Minor GC之后的对象不够放在 Survivor 区,直接放到老年代,老年代能放下,GC 结束。
  • Minor GC之后的对象不够放在 Survivor 区,老年代也不放不下,进行 Full GC。

前面都是成功的GC案例,还有3种情况会导致GC失败,报OOM:

  • 未开启老年代分配担保机制,且一次Full GC后,老年代任然放不下剩余对象,只能OOM。(未开启分配担保机制——Full GC——老年代放不下)
  • 开启分配担保机制,担保通过,但老年代放不下,当进行Full GC之后,老年代仍然便不下剩余对象,只能OOM。(分配担保机制——担保成功——老年代放不下——Full GC——老年代依然放不下)
  • 开启老年代分配担保机制,但是担保不通过,一次Full GC后,老年代放不下剩余对象,只能OOM。(开启分配担保机制——担保失败——Full GC——老年代放不下)

新生代、老年代占用内存的比例1:2。

# Full GC 会导致什么?

Stop The World,即在GC期间全部暂停用户的应用程序。

# JVM什么时候触发 GC,如何减少 Full GC 的次数?

  • 当Eden区的空间耗尽时会触发一次Minor GC来收集新生代的垃圾,存活下来的对象会被送到Survivor区。

  • Major GC中,老年代剩余空间小于之前新生代晋升老年代的平均值,进行Full GC。在CMS等并发收集器中则时每隔一段时间来检查一下老年代内存使用量,超过一定比例时进行Full GC回收。

减少Full GC次数:

  1. 增加方法区、老年代、新生代空间;
  2. 禁止使用System.gc()方法;
  3. 使用标记-整理算法,尽量保持较大的连续内存空间;
  4. 排查代码中无用的大对象。

# 如何减少 GC 出现的次数?

  1. 对象不用时显式置为Null,一般而言,对象引用置为Null后,其堆中的对象实例都将被作为垃圾处理。
  2. 尽量少使用System.gc()
  3. 尽量少用静态变量,静态变量属于全局变量,不会被GC回收,他会一直占用内存。
  4. 尽量少用finalize函数,他会增加GC的工作量。
  5. 尽量使用StringBuffer、StringBuilder而不用String类累加字符串。
  6. 能用基本数据类型就不用包装类。
  7. 分散对象创建和删除时间。
  8. 增大-Xmx的值。

# 方法区会进行垃圾回收吗?

会,方法区在元空间内(MetaSpace),当元空间的大小达到阈值时会进行 Full GC。下面举个例子看一下:

设置VM参数:

  • -XX:MaxMetaspaceSize=10240000:设置元空间大小
  • -XX:+PrintGCDetails:设置打印垃圾回收信息

测试代码:通过CGLib反射创建新的代理类,这些类的元数据将不断添加到方法区内

public class MetaSpaceOOMTest {
    // 设置VM
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Object.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            Object o = enhancer.create();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

垃圾回收详情:

[GC (Allocation Failure) [PSYoungGen: 65536K->2552K(76288K)] 65536K->2560K(251392K), 0.0137046 secs] [Times: user=0.05 sys=0.02, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 68088K->2784K(141824K)] 68096K->2800K(316928K), 0.0168467 secs] [Times: user=0.07 sys=0.02, real=0.02 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 24538K->3040K(141824K)] 24554K->3064K(316928K), 0.0140900 secs] [Times: user=0.06 sys=0.02, real=0.02 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 3040K->0K(141824K)] [ParOldGen: 24K->2928K(76288K)] 3064K->2928K(218112K), [Metaspace: 9034K->9034K(1058816K)], 0.0117568 secs] [Times: user=0.04 sys=0.00, real=0.01 secs] 
[GC (Last ditch collection) [PSYoungGen: 0K->0K(245248K)] 2928K->2928K(321536K), 0.0003042 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(245248K)] [ParOldGen: 2928K->1376K(140800K)] 2928K->1376K(386048K), [Metaspace: 9034K->9034K(1058816K)], 0.0088830 secs] [Times: user=0.04 sys=0.01, real=0.01 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 4876K->64K(262656K)] 6253K->1440K(403456K), 0.0003653 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 64K->0K(262656K)] [ParOldGen: 1376K->1375K(219136K)] 1440K->1375K(481792K), [Metaspace: 9034K->9034K(1058816K)], 0.0094953 secs] [Times: user=0.04 sys=0.00, real=0.01 secs] 
[GC (Last ditch collection) [PSYoungGen: 0K->0K(271360K)] 1375K->1375K(490496K), 0.0002275 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(271360K)] [ParOldGen: 1375K->1375K(342528K)] 1375K->1375K(613888K), [Metaspace: 9034K->9034K(1058816K)], 0.0044636 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
1
2
3
4
5
6
7
8
9
10

GC (Allocation Failure):因为内存分配失败导致垃圾回收,只有年轻代发生了垃圾回收,所以是 Minor GC GC (Metadata GC Threshold):元空间达到阈值导致垃圾回收,进行 Full GC Full GC (Last ditch collection):经过 Metadata GC Threshold 触发的 Full GC 后还是不能满足条件,这个时候会触最后 Last ditch collection,这次 Full GC 会清理掉软引用(Soft Reference)

最后因为元空间的内存不足导致OOM

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
1
编辑 (opens new window)
#Java#JVM
上次更新: 2024/04/23, 21:28:15
JVM创建对象过程
JVM参数设置

← JVM创建对象过程 JVM参数设置→

最近更新
01
位运算
05-21
02
二叉树
05-12
03
Spring三级缓存解决循环依赖
03-25
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Marvel
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式