JVM垃圾回收笔记01-垃圾回收算法

news2025/3/25 7:37:13

文章目录

  • 前言
  • 1. 如何判断对象可以回收
    • 1.1 引用计数法
    • 1.2 可达性分析算法
      • 查看根对象
      • 哪些对象可以作为 GC Root ?
      • 对象可以被回收,就代表一定会被回收吗?
    • 1.3 引用类型
      • 1.强引用(StrongReference)
      • 2.软引用(SoftReference)
      • 3.弱引用(WeakReference)
      • 4.虚引用(PhantomReference)
      • 5.终结器引用(FinalReference)
    • 如何判断一个常量是废弃常量
    • 如何判断一个类是无用的类
  • 2. 垃圾回收算法
    • 2.1 标记清除
    • 2.2 标记整理
    • 2.3 复制
  • 3. 分代垃圾回收
    • 3.1分代收集算法是什么?
    • 3.2 相关 VM 参数
    • 代码示例1(启动空程序)
    • 代码示例2(触发新生代垃圾回收)
    • 代码示例3(触发两次垃圾回收)
    • 代码示例4(大对象,当新生代放不下时)
    • 代码示例5(大对象,当新生代和老年代都放不下时)
    • 代码示例6(线程报错OOM会不会影响主线程)


前言

Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是堆内存中对象的分配与回收
Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。
从垃圾回收的角度来说,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆被划分为了几个不同的区域,这样我们就可以根据各个区域的特点选择合适的垃圾收集算法。
在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:

  1. 新生代内存(Young Generation)
  2. 老生代(Old Generation)
  3. 永久代(Permanent Generation)

JDK 8 版本之后 PermGen(永久) 已被 Metaspace(元空间) 取代,元空间使用的是直接内存 。
详情可以看这篇文章:JVM内存结构-堆

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

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

1.1 引用计数法

在这里插入图片描述

1.2 可达性分析算法

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

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
下图中的 Object 6 ~ Object 10 之间虽有引用关系,但它们到 GC Roots 不可达,因此为需要被回收的对象。
在这里插入图片描述

查看根对象

代码示例
用到的工具:https://eclipse.dev/mat/
注意:Memory Analyzer 1.14 及更高版本需要 Java 17 VM 或更高版本的虚拟机才能运行。 Memory Analyzer 1.12 及更高版本需要 Java 11 VM 或更高版本的 VM 才能运行。 Memory Analyzer 1.8 至 1.11 需要 Java 1.8 VM 或更高版本的 VM 才能运行

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示GC Roots
 */
public class Demo2_2 {

    public static void main(String[] args) throws InterruptedException, IOException {
        List<Object> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        System.out.println("mark 1");
        System.in.read();

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

操作以下步骤

  1. 运行程序,停留在mark 1
  2. jps查看进程pid
  3. 使用以下命令执行mark 1和mark 2时的堆对象快照
    jmap -dump:format=b,live,file=mark1.bin 进程id
    如:jmap -dump:format=b,live,file=D:\qf\mark1.bin 2448
    解释:生成在当前应用程序堆内存中对象的快照(mark.bin二进制文件)
  4. 程序继续向下执行到mark 2,使用上面命令执行mark 2时的堆对象快照
    在这里插入图片描述
  5. 打开Memory Analyzer查看
    在这里插入图片描述选择刚刚创建的mark1.bin和mark2.bin
    在这里插入图片描述
    选择刚刚创建的mark1.bin和mark2.bin
    在这里插入图片描述
    选择以下选项
    在这里插入图片描述
    mark1时:
    在这里插入图片描述
    mark2时:
    在这里插入图片描述
    可以看到在将 list1 = null 后通过指令中的live关键字触发了垃圾回收后,这里的ArrayList被垃圾回收了。

哪些对象可以作为 GC Root ?

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

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

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;
可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。
当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

1.3 引用类型

软引用、弱引用、虚引用在 JDK 中定义的类分别是 SoftReference、WeakReference、PhantomReference。

1.强引用(StrongReference)

  • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
  • 当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

2.软引用(SoftReference)

  • 如果内存空间足够,垃圾回收器就不会回收软引用,如果内存空间不足了,就会回收这些软引用对象的内存。软引用可用来实现内存敏感的高速缓存。
  • 可以配合引用队列(ReferenceQueue)来释放软引用自身,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。

3.弱引用(WeakReference)

  • 在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
  • 注意:垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
  • 可以配合引用队列来释放弱引用自身,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

4.虚引用(PhantomReference)

  • 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
  • 虚引用主要用来跟踪对象被垃圾回收的活动。
  • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
  • 场景:在直接内存时,使用到了虚引用,当ByteBuffer回收后,虚引用会放入引用队列,此时会有线程定时的执行寻找引用队列中新的虚引用,找到了会调用Cleaner.clean(),进行直接内存的回收。
  • 虚引用必须配合引用队列使用。主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

5.终结器引用(FinalReference)

  • 终结器引用用于标记那些重写了finalize()方法的对象,确保这些对象在被垃圾回收之前能够执行finalize()方法。
  • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 **finalize()**方法,第二次 GC 时才能回收被引用对象

注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
在这里插入图片描述

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

运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?
假如在字符串常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池了。

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

方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类”:

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

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

2. 垃圾回收算法

2.1 标记清除

定义: 标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和“清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
它是最基础的收集算法,后续的算法都是对其不足进行改进得到。

  • 优点:速度较快
  • 缺点:会造成内存碎片
    在这里插入图片描述
    释放内存时,不是将内存数据进行清零操作,是将对象占用的起始、结束内存地址记录下来,放在空闲地址列表中,下次给新对象分配内存地址时在该列表中寻找进行分配。

2.2 标记整理

定义:标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

  • 速度慢
  • 没有内存碎片
    在这里插入图片描述
    由于多了整理这一步,因此效率也不高,适合老年代这种垃圾回收频率不是很高的场景。

2.3 复制

定义:为了解决标记-清除算法的效率和内存碎片问题,复制(Copying)收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

  • 不会有内存碎片
  • 需要占用双倍内存空间
  • 不适合老年代(如果存活对象数量比较大,复制性能会变得很差)
    在这里插入图片描述
    将被引用的内存复制到To,然后将from和to对调
    在这里插入图片描述

3. 分代垃圾回收

实际上jvm不是只使用以上一种算法,根据情况使用。具体使用的是分代垃圾回收。
在这里插入图片描述

  1. 新的对象首先分配在伊甸园区域
  2. 当新生代空间不足时,触发 minor gc(新生代垃圾回收,通过可达性分析算法标记),采用复制算法,将伊甸园存活的对象使用 copy 复制到 to 中,存活的对象年龄加1,并且交换 from和to区域的位置。
  3. 当第二次新生代空间不足时,触发 minor gc,将伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to
  4. minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
  5. 当对象寿命超过阈值时,会晋升至老年代,阈值的最大寿命是15(因为是保持在对象头中,所以为4bit)
  6. 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,stop the world的时间更长(新生代回收算法采用复制算法,而老年代采用标记清除或标记整理算法。

3.1分代收集算法是什么?

当前虚拟机的垃圾收集都采用分代收集算法,这种算法是根据对象存活周期的不同将内存分为几块。一般将 Java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

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

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

以下通过代码触发垃圾回收,观察新生代和老年代区域变化

代码示例1(启动空程序)

/**
 *  演示内存的分配策略
 */
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        
    }
}

配置参数

-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC

参数解释:
用于配置Java虚拟机(JVM)的垃圾收集(GC)行为和内存分配

  • -Xms20M:设置 JVM 堆内存的初始大小为 20MB。也就是说,当 JVM 启动时,就会立即分配 20MB 的堆内存空间给 Java 应用程序使用。堆内存用于存放 Java 对象实例,提前分配足够的初始内存能减少后续内存动态扩展带来的性能开销。
  • -Xmx20M:设定 JVM 堆内存的最大可使用大小为 20MB。JVM 在运行过程中,堆内存会随着对象的创建和销毁而动态变化,但无论如何,堆内存占用量都不会超过这个上限值。将-Xms和-Xmx设置成相同的值,可以避免堆内存动态扩容与缩容带来的额外性能损耗,让 JVM 运行更加稳定。
  • -Xmn10M:用于指定新生代(Young Generation)的大小为 10MB。新生代是堆内存的一部分,主要用于存放新创建的 Java 对象,对象在经过多次垃圾回收后依然存活,就会被移到年老代(Old Generation)。通过合理设置新生代大小,可以优化垃圾回收效率,因为新生代的垃圾回收频率通常远高于年老代。
  • -XX:+UseSerialGC:这是一个垃圾回收器相关的参数,它指定 JVM 使用串行垃圾回收器。串行垃圾回收器是最基本的一种回收器,在进行垃圾回收时,只有一个线程参与垃圾回收工作,进行垃圾回收时会暂停整个 Java 应用程序(即 “Stop-the-World” 现象),直到垃圾回收完毕。这种回收器适合单核 CPU 环境或者对内存使用量要求不高、应用程序暂停时间不太敏感的简单场景。
  • -XX:+PrintGCDetails:启用该参数后,JVM 每次进行垃圾回收时,都会打印出详细的垃圾回收信息,例如回收前后各代内存的使用情况、回收了多少对象、花费了多长时间等。这些详细信息对于分析 JVM 内存使用效率、排查内存泄漏等问题非常有帮助。
  • -verbose:gc:这个参数同样用于输出垃圾回收相关的信息,不过相较于-XX:+PrintGCDetails,它输出的信息更为简洁,只是简单告知垃圾回收事件的发生,像是何时进行了新生代垃圾回收、何时进行了年老代垃圾回收。
  • -XX:-ScavengeBeforeFullGC:默认情况下,在进行一次完整的堆垃圾回收(Full GC)之前,会先执行一次新生代的垃圾回收(Minor GC,也叫 Scavenge),这一操作旨在尽量减少进入年老代的对象数量,降低 Full GC 的压力。添加此参数并设置为负号(-),表示禁用这个默认行为,不再提前进行新生代垃圾回收。

启动项目输出

Heap
 def new generation   total 9216K, used 2318K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  28% used [0x00000000fec00000, 0x00000000fee43960, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
 Metaspace       used 3290K, capacity 4564K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

观察def new generation(新生代)和tenured generation(老年代)

代码示例2(触发新生代垃圾回收)

public static void main(String[] args) throws InterruptedException {
    ArrayList<byte[]> list = new ArrayList<>();
    list.add(new byte[_7MB]);
}

输出

[GC (Allocation Failure) [DefNew: 2154K->687K(9216K), 0.0014977 secs] 2154K->687K(19456K), 0.0015450 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 8265K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  92% used [0x00000000fec00000, 0x00000000ff366840, 0x00000000ff400000)
  from space 1024K,  67% used [0x00000000ff500000, 0x00000000ff5abea8, 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 3292K, capacity 4564K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

可以看到只有新生代中发生变化,触发垃圾回收时将伊甸园(eden)存活的对象使用 copy 复制到 to 中,存活的对象年龄加1,并且交换 from和to区域的位置。

代码示例3(触发两次垃圾回收)

	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 {
        ArrayList<byte[]> list = new ArrayList<>();
        list.add(new byte[_7MB]);
        list.add(new byte[_512KB]);
    }

输出

[GC (Allocation Failure) [DefNew: 2154K->713K(9216K), 0.0013532 secs] 2154K->713K(19456K), 0.0013897 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 8639K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  96% used [0x00000000fec00000, 0x00000000ff3bd8d0, 0x00000000ff400000)
  from space 1024K,  69% used [0x00000000ff500000, 0x00000000ff5b25a0, 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 3293K, capacity 4564K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

代码示例4(大对象,当新生代放不下时)

当放入的对象过大,超过伊甸园的剩余空间时,会直接放入老年代,不会触发垃圾回收。

    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 {
        ArrayList<byte[]> list = new ArrayList<>();
        list.add(new byte[_8MB]);
    }

输出

Heap
 def new generation   total 9216K, used 2318K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  28% used [0x00000000fec00000, 0x00000000fee439b8, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000)
 Metaspace       used 3292K, capacity 4564K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

代码示例5(大对象,当新生代和老年代都放不下时)

    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 {
        ArrayList<byte[]> list = new ArrayList<>();
        list.add(new byte[_8MB]);
        list.add(new byte[_8MB]);
    }

输出

[GC (Allocation Failure) [DefNew: 2154K->713K(9216K), 0.0013283 secs][Tenured: 8192K->8904K(10240K), 0.0016970 secs] 10346K->8904K(19456K), [Metaspace: 3286K->3286K(1056768K)], 0.0030726 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured: 8904K->8886K(10240K), 0.0012463 secs] 8904K->8886K(19456K), [Metaspace: 3286K->3286K(1056768K)], 0.0012659 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 246K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,   3% used [0x00000000fec00000, 0x00000000fec3d890, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 8886K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  86% used [0x00000000ff600000, 0x00000000ffeadba8, 0x00000000ffeadc00, 0x0000000100000000)
 Metaspace       used 3318K, capacity 4564K, committed 4864K, reserved 1056768K
  class space    used 364K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at cn.itcast.jvm.t2.Demo2_1.main(Demo2_1.java:22)

可以看到当新生代和老年代内存都不够时,则会报错OOM

代码示例6(线程报错OOM会不会影响主线程)

    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(3000L);
        System.out.println("主线程结束!");
    }

输出

sleep....
[GC (Allocation Failure) [DefNew: 4723K->916K(9216K), 0.0023784 secs][Tenured: 8192K->9106K(10240K), 0.0029042 secs] 12915K->9106K(19456K), [Metaspace: 4202K->4202K(1056768K)], 0.0053451 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured: 9106K->9051K(10240K), 0.0031245 secs] 9106K->9051K(19456K), [Metaspace: 4202K->4202K(1056768K)], 0.0031630 secs] [Times: user=0.00 sys=0.00, real=0.00 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:27)
    at cn.itcast.jvm.t2.Demo2_1$$Lambda$1/1747585824.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)
主线程结束!
Heap
 def new generation   total 9216K, used 1411K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  17% used [0x00000000fec00000, 0x00000000fed60e88, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 9051K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  88% used [0x00000000ff600000, 0x00000000ffed6da8, 0x00000000ffed6e00, 0x0000000100000000)
 Metaspace       used 4732K, capacity 4866K, committed 4992K, reserved 1056768K
  class space    used 525K, capacity 559K, committed 640K, reserved 1048576K

可以看到一个线程内的OOM,不会影响到java进程的结束。


相关文章:
JVM内存结构

  • JVM内存结构笔记01-运行时数据区域
  • JVM内存结构笔记02-堆
  • JVM内存结构笔记03-方法区
  • JVM内存结构笔记04-字符串常量池
  • JVM内存结构笔记05-直接内存
  • JVM内存结构笔记06-HotSpot虚拟机对象探秘
  • JVM中常量池和运行时常量池、字符串常量池三者之间的关系

JVM垃圾回收

  • JVM垃圾回收笔记01-垃圾回收算法
  • JVM垃圾回收笔记02-垃圾回收器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2320369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【初探数据结构】树与二叉树

&#x1f4ac; 欢迎讨论&#xff1a;在阅读过程中有任何疑问&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;如果你觉得这篇文章对你有帮助&#xff0c;记得点赞、收藏&#xff0c;并分享给更多对数据结构感…

蓝桥杯备考:二分答案之路标设置

最大距离&#xff0c;找最小空旷指数值&#xff0c;我们是很容易想到用二分的&#xff0c;我们再看看这个答案有没有二段性 是有这么个二段性的&#xff0c;我们只要二分就行了&#xff0c;但是二分的check函数是有点不好想的&#xff0c;我们枚举空旷值的时候&#xff0c;为了…

回调方法传参汇总

文章目录 0. 引入问题1. 父子组件传值1.1 父传子&#xff1a;props1.2 子传父&#xff1a;$emit1.3 双向绑定&#xff1a;v-model 2. 多个参数传递3. 父组件监听方法传递其他值3.1 $event3.2 箭头方法 4. 子组件传递多个参数&#xff0c;父组件传递本地参数4.1 箭头函数 … 扩…

XSS基础靶场练习

目录 1. 准备靶场 2. PASS 1. Level 1&#xff1a;无过滤 源码&#xff1a; 2. level2&#xff1a;转HTML实体 htmlspecialchars简介&#xff1a; 源码 PASS 3. level3:转HTML深入 源码&#xff1a; PASS 4. level4:过滤<> 源码&#xff1a; PASS: 5. level5:过滤on 源码…

Redis核心机制(一)

目录 Redis的特性 1.速度快 2.以键值对方式进行存储 3.丰富的功能 4.客户端语言多 5.持久化 6.主从复制 7.高可用和分布式 Redis使用场景 Redis核心机制——持久化 RDB bgsave执行流程 ​编辑 AOF AOF重写流程 3.混合持久化&#xff08;RDBAOF&#xff09; Red…

QGroupBox取消勾选时不禁用子控件

默认情况下&#xff0c;QGroupBox取消勾选会自动禁用子控件&#xff0c;如下图所示 那么如何实现取消勾选时不禁用子控件呢&#xff1f; 实现很简单&#xff0c;直接上代码了 connect(ui->groupBox, &QGroupBox::toggled, this, [](bool checked){if (checked false){…

MyBatis-Plus 自动填充:优雅实现创建/更新时间自动更新!

目录 一、什么是 MyBatis-Plus 自动填充&#xff1f; &#x1f914;二、自动填充的原理 ⚙️三、实际例子&#xff1a;创建时间和更新时间字段自动填充 ⏰四、注意事项 ⚠️五、总结 &#x1f389; &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢…

canvas数据标注功能简单实现:矩形、圆形

背景说明 基于UI同学的设计&#xff0c;在市面上找不到刚刚好的数据标注工具&#xff0c;遂决定自行开发。目前需求是实现图片的矩形、圆形标注&#xff0c;并获取标注的坐标信息&#xff0c;使用canvas可以比较方便的实现该功能。 主要功能 选中图形&#xff0c;进行拖动 使…

【UI设计】一些好用的免费图标素材网站

阿里巴巴矢量图标库https://www.iconfont.cn/国内最大的矢量图标库之一&#xff0c;拥有 800 万 图标资源。特色功能包括团队协作、多端适配、定制化编辑等&#xff0c;适合企业级项目、电商设计、中文产品开发等场景。IconParkhttps://iconpark.oceanengine.com/home字节跳动…

ubuntu 解挂载时提示 “umount: /home/xx/Applications/yy: target is busy.”

问题如题所示&#xff0c;我挂载一个squanfs文件系统到指定目录&#xff0c;当我使用完后&#xff0c;准备解挂载时&#xff0c;提示umount: /home/xx/Applications/yy: target is busy.&#xff0c;具体的如图所示&#xff0c; 这种提示通常是表明这个路径的内容正在被某些进…

一条不太简单的TEX学习之路

目录 rule raisebox \includegraphics newenviro 、\vspace \stretch \setlength 解释&#xff1a; 总结&#xff1a; 、\linespread newcommand \par 小四 \small simple 、mutiput画网格 解释&#xff1a; 图案解释&#xff1a; xetex pdelatex etc index 报…

Matplotlib完全指南:数据可视化从入门到实战

目录 引言 一、环境配置与基础概念 1.1 安装Matplotlib 1.2 导入惯例 1.3 两种绘图模式 二、基础图形绘制 2.1 折线图&#xff08;Line Plot&#xff09; 2.2 柱状图&#xff08;Bar Chart&#xff09; 三、高级图表类型 3.1 散点图&#xff08;Scatter Plot&#xff…

在大数据开发中ETL是指什么?

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在数字经济时代&#xff0c;数据已成为企业最核心的资产。然而&#xff0c;分散在业务系统、日志文件…

OAuth 2.0认证

文章目录 1. 引言1.1 系列文章说明1.2 OAuth 2.0 的起源与演变1.3 应用场景概览 2. OAuth 2.0 核心概念2.1 角色划分2.2 核心术语解析 3. 四种授权模式详解3.1 授权码模式&#xff08;Authorization Code Grant&#xff09;3.1.1 完整流程解析3.1.2 PKCE 扩展&#xff08;防止授…

Kubernetes的Replica Set和ReplicaController有什么区别

ReplicaSet 和 ReplicationController 是 Kubernetes 中用于管理应用程序副本的两种资源&#xff0c;它们有类似的功能&#xff0c;但 ReplicaSet 是 ReplicationController 的增强版本。 以下是它们的主要区别&#xff1a; 1. 功能的演进 ReplicationController 是 Kubernete…

[Lc_2 二叉树dfs] 布尔二叉树的值 | 根节点到叶节点数字之和 | 二叉树剪枝

目录 1.计算布尔二叉树的值 题解 2.求根节点到叶节点数字之和 3. 二叉树剪枝 题解 1.计算布尔二叉树的值 链接&#xff1a;2331. 计算布尔二叉树的值 给你一棵 完整二叉树 的根&#xff0c;这棵树有以下特征&#xff1a; 叶子节点 要么值为 0 要么值为 1 &#xff0c;其…

蓝桥杯 之 第27场月赛总结

文章目录 习题1.抓猪拿国一2.蓝桥字符3.蓝桥大使4.拳头对决 习题 比赛地址 1.抓猪拿国一 十分简单的签到题 print(sum(list(range(17))))2.蓝桥字符 常见的字符匹配的问题&#xff0c;是一个二维dp的问题&#xff0c;转化为对应的动态规划求解 力扣的相似题目 可以关注灵神…

可视化动态表单动态表单界的天花板--Formily(阿里开源)

文章目录 1、Formily表单介绍2、安装依赖2.1、安装内核库2.2、 安装 UI 桥接库2.3、Formily 支持多种 UI 组件生态&#xff1a; 3、表单设计器3.1、核心理念3.2、安装3.3、示例源码 4、场景案例-登录注册4.1、Markup Schema 案例4.2、JSON Schema 案例4.3、纯 JSX 案例 1、Form…

Amdahl 定律

Amdahl 定律是用来表示&#xff0c;当提高系统某部分性能时对整个系统的影响&#xff0c;其公式如下&#xff1a; a表示我们提升部分初始耗时比例&#xff0c;k是我们的提升倍率&#xff0c;通过这个公式我们可以轻松的得知对每一部分的提醒&#xff0c;对整个系统带来的影响…

Linux系统之美:环境变量的概念以及基本操作

本节重点 理解环境变量的基本概念学会在指令和代码操作上查询更改环境变量环境变量表的基本概念父子进程间环境变量的继承与隔离 一、引入 1.1 自定义命令&#xff08;我们的exe&#xff09; 我们以往的Linux编程经验告诉我们&#xff0c;我们在对一段代码编译形成可执行文件后…