JVM垃圾回收笔记01-垃圾回收算法
文章目录
- 前言
- 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 版本之前,堆内存被通常分为下面三部分:
- 新生代内存(Young Generation)
- 老生代(Old Generation)
- 永久代(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...");}
}
操作以下步骤
- 运行程序,停留在mark 1
- jps查看进程pid
- 使用以下命令执行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二进制文件) - 程序继续向下执行到mark 2,使用上面命令执行mark 2时的堆对象快照

- 打开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不是只使用以上一种算法,根据情况使用。具体使用的是分代垃圾回收。

- 新的对象首先分配在伊甸园区域
- 当新生代空间不足时,触发 minor gc(新生代垃圾回收,通过可达性分析算法标记),采用复制算法,将伊甸园存活的对象使用 copy 复制到 to 中,存活的对象年龄加1,并且交换 from和to区域的位置。
- 当第二次新生代空间不足时,触发 minor gc,将伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to
- …
- minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,阈值的最大寿命是15(因为是保持在对象头中,所以为4bit)
- 当老年代空间不足,会先尝试触发 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 的压力。添加此参数并设置为负号(-),表示禁用这个默认行为,不再提前进行新生代垃圾回收。
启动项目输出
Heapdef 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 1056768Kclass 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]
Heapdef 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 1056768Kclass 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:-ScavengeBeforeFullGCpublic 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]
Heapdef 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 1056768Kclass 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:-ScavengeBeforeFullGCpublic static void main(String[] args) throws InterruptedException {ArrayList<byte[]> list = new ArrayList<>();list.add(new byte[_8MB]);}
输出
Heapdef 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 1056768Kclass 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:-ScavengeBeforeFullGCpublic 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]
Heapdef 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 1056768Kclass space used 364K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat 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:-ScavengeBeforeFullGCpublic 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 spaceat 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)
主线程结束!
Heapdef 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 1056768Kclass 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-垃圾回收器
相关文章:
JVM垃圾回收笔记01-垃圾回收算法
文章目录 前言1. 如何判断对象可以回收1.1 引用计数法1.2 可达性分析算法查看根对象哪些对象可以作为 GC Root ?对象可以被回收,就代表一定会被回收吗? 1.3 引用类型1.强引用(StrongReference)2.软引用(SoftReference…...
【初探数据结构】树与二叉树
💬 欢迎讨论:在阅读过程中有任何疑问,欢迎在评论区留言,我们一起交流学习! 👍 点赞、收藏与分享:如果你觉得这篇文章对你有帮助,记得点赞、收藏,并分享给更多对数据结构感…...
numpy学习笔记10:arr *= 2向量化操作性能优化
numpy学习笔记10:arr * 2向量化操作性能优化 在 NumPy 中,直接对整个数组进行向量化操作(如 arr * 2)的效率远高于显式循环(如 for i in range(len(arr)): arr[i] * 2)。以下是详细的解释: 1. …...
蓝桥杯备考:二分答案之路标设置
最大距离,找最小空旷指数值,我们是很容易想到用二分的,我们再看看这个答案有没有二段性 是有这么个二段性的,我们只要二分就行了,但是二分的check函数是有点不好想的,我们枚举空旷值的时候,为了…...
回调方法传参汇总
文章目录 0. 引入问题1. 父子组件传值1.1 父传子:props1.2 子传父:$emit1.3 双向绑定:v-model 2. 多个参数传递3. 父组件监听方法传递其他值3.1 $event3.2 箭头方法 4. 子组件传递多个参数,父组件传递本地参数4.1 箭头函数 … 扩…...
在 Linux下使用 Python 3.11 和 FastAPI 搭建带免费证书的 HTTPS 服务器
在当今数字化时代,保障网站数据传输的安全性至关重要。HTTPS 协议通过使用 SSL/TLS 加密技术,能够有效防止数据在传输过程中被窃取或篡改。本教程将详细介绍如何在 Ubuntu 22.04 系统上,使用 Python 3.11 和 FastAPI 框架搭建一个带有免费 SS…...
XSS基础靶场练习
目录 1. 准备靶场 2. PASS 1. Level 1:无过滤 源码: 2. level2:转HTML实体 htmlspecialchars简介: 源码 PASS 3. level3:转HTML深入 源码: PASS 4. level4:过滤<> 源码: PASS: 5. level5:过滤on 源码…...
Redis核心机制(一)
目录 Redis的特性 1.速度快 2.以键值对方式进行存储 3.丰富的功能 4.客户端语言多 5.持久化 6.主从复制 7.高可用和分布式 Redis使用场景 Redis核心机制——持久化 RDB bgsave执行流程 编辑 AOF AOF重写流程 3.混合持久化(RDBAOF) Red…...
QGroupBox取消勾选时不禁用子控件
默认情况下,QGroupBox取消勾选会自动禁用子控件,如下图所示 那么如何实现取消勾选时不禁用子控件呢? 实现很简单,直接上代码了 connect(ui->groupBox, &QGroupBox::toggled, this, [](bool checked){if (checked false){…...
Go语言中package的使用规则《二》
在 Go 语言中,包(Package) 是代码组织和复用的核心单元。以下是其定义、引用规则及使用习惯的详细说明: 一、包的定义规则 目录与包名 一个包对应一个目录(文件夹),目录名通常与包名一致。 包名…...
MyBatis-Plus 自动填充:优雅实现创建/更新时间自动更新!
目录 一、什么是 MyBatis-Plus 自动填充? 🤔二、自动填充的原理 ⚙️三、实际例子:创建时间和更新时间字段自动填充 ⏰四、注意事项 ⚠️五、总结 🎉 🌟我的其他文章也讲解的比较有趣😁,如果喜欢…...
canvas数据标注功能简单实现:矩形、圆形
背景说明 基于UI同学的设计,在市面上找不到刚刚好的数据标注工具,遂决定自行开发。目前需求是实现图片的矩形、圆形标注,并获取标注的坐标信息,使用canvas可以比较方便的实现该功能。 主要功能 选中图形,进行拖动 使…...
Python 魔术方法深度解析:__getattr__ 与 __getattribute__
一、核心概念与差异解析 1. __getattr__ 的定位与特性 触发时机: 当访问对象中 **不存在的属性** 时自动触发,是 Python 属性访问链中的最后一道防线。 核心能力: 动态生成缺失属性实现优雅的错误处理构建链式调用接口(如 R…...
【机器学习】机器学习工程实战-第2章 项目开始前
上一章:第1章 概述 文章目录 2.1 机器学习项目的优先级排序2.1.1 机器学习的影响2.1.2 机器学习的成本 2.2 估计机器学习项目的复杂度2.2.1 未知因素2.2.2 简化问题2.2.3 非线性进展 2.3 确定机器学习项目的目标2.3.1 模型能做什么2.3.2 成功模型的属性 2.4 构建机…...
【UI设计】一些好用的免费图标素材网站
阿里巴巴矢量图标库https://www.iconfont.cn/国内最大的矢量图标库之一,拥有 800 万 图标资源。特色功能包括团队协作、多端适配、定制化编辑等,适合企业级项目、电商设计、中文产品开发等场景。IconParkhttps://iconpark.oceanengine.com/home字节跳动…...
Visual Studio(VS)的 Release 配置中生成程序数据库(PDB)文件
最近工作中的一个测试工具在测试多台设备上使用过程中闪退,存了dump,但因为是release版本,没有pdb,无法根据dump定位代码哪块出了问题,很苦恼,查了下怎么加pdb生成,记录一下。以下是具体的设置步…...
ubuntu 解挂载时提示 “umount: /home/xx/Applications/yy: target is busy.”
问题如题所示,我挂载一个squanfs文件系统到指定目录,当我使用完后,准备解挂载时,提示umount: /home/xx/Applications/yy: target is busy.,具体的如图所示, 这种提示通常是表明这个路径的内容正在被某些进…...
一条不太简单的TEX学习之路
目录 rule raisebox \includegraphics newenviro 、\vspace \stretch \setlength 解释: 总结: 、\linespread newcommand \par 小四 \small simple 、mutiput画网格 解释: 图案解释: xetex pdelatex etc index 报…...
Matplotlib完全指南:数据可视化从入门到实战
目录 引言 一、环境配置与基础概念 1.1 安装Matplotlib 1.2 导入惯例 1.3 两种绘图模式 二、基础图形绘制 2.1 折线图(Line Plot) 2.2 柱状图(Bar Chart) 三、高级图表类型 3.1 散点图(Scatter Plotÿ…...
在大数据开发中ETL是指什么?
hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在数字经济时代,数据已成为企业最核心的资产。然而,分散在业务系统、日志文件…...
OAuth 2.0认证
文章目录 1. 引言1.1 系列文章说明1.2 OAuth 2.0 的起源与演变1.3 应用场景概览 2. OAuth 2.0 核心概念2.1 角色划分2.2 核心术语解析 3. 四种授权模式详解3.1 授权码模式(Authorization Code Grant)3.1.1 完整流程解析3.1.2 PKCE 扩展(防止授…...
【Linux 下的 bash 无法正常解析, Windows 的 CRLF 换行符问题导致的】
文章目录 报错原因:解决办法:方法一:用 dos2unix 修复方法二:手动转换换行符方法三:VSCode 或其他编辑器手动改 总结 这个错误很常见,原因是你的 wait_for_gpu.sh 脚本 文件格式不对,具体来说…...
Kubernetes的Replica Set和ReplicaController有什么区别
ReplicaSet 和 ReplicationController 是 Kubernetes 中用于管理应用程序副本的两种资源,它们有类似的功能,但 ReplicaSet 是 ReplicationController 的增强版本。 以下是它们的主要区别: 1. 功能的演进 ReplicationController 是 Kubernete…...
WSL 导入完整系统包教程
作者: DWDROME 配置环境: OS: Ubuntu 20.04.6 LTS on Windows 11 x86_64Kernel: 5.15.167.4-microsoft-standard-WSL2ros-noetic 🧭WSL 导入完整系统包教程 ✅ 一、准备导出文件 假设你已有一个 .tar 的完整系统包(如从 WSL 或 L…...
[Lc_2 二叉树dfs] 布尔二叉树的值 | 根节点到叶节点数字之和 | 二叉树剪枝
目录 1.计算布尔二叉树的值 题解 2.求根节点到叶节点数字之和 3. 二叉树剪枝 题解 1.计算布尔二叉树的值 链接:2331. 计算布尔二叉树的值 给你一棵 完整二叉树 的根,这棵树有以下特征: 叶子节点 要么值为 0 要么值为 1 ,其…...
SOFABoot-07-版本查看
前言 大家好,我是老马。 sofastack 其实出来很久了,第一次应该是在 2022 年左右开始关注,但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…...
蓝桥杯 之 第27场月赛总结
文章目录 习题1.抓猪拿国一2.蓝桥字符3.蓝桥大使4.拳头对决 习题 比赛地址 1.抓猪拿国一 十分简单的签到题 print(sum(list(range(17))))2.蓝桥字符 常见的字符匹配的问题,是一个二维dp的问题,转化为对应的动态规划求解 力扣的相似题目 可以关注灵神…...
第十六章:Specialization and Overloading_《C++ Templates》notes
Specialization and Overloading 一、模板特化与重载的核心概念二、代码实战与测试用例三、关键知识点总结四、进阶技巧五、实践建议多选题设计题代码测试说明 一、模板特化与重载的核心概念 函数模板重载 (Function Template Overloading) // 基础模板 template<typename…...
可视化动态表单动态表单界的天花板--Formily(阿里开源)
文章目录 1、Formily表单介绍2、安装依赖2.1、安装内核库2.2、 安装 UI 桥接库2.3、Formily 支持多种 UI 组件生态: 3、表单设计器3.1、核心理念3.2、安装3.3、示例源码 4、场景案例-登录注册4.1、Markup Schema 案例4.2、JSON Schema 案例4.3、纯 JSX 案例 1、Form…...
Amdahl 定律
Amdahl 定律是用来表示,当提高系统某部分性能时对整个系统的影响,其公式如下: a表示我们提升部分初始耗时比例,k是我们的提升倍率,通过这个公式我们可以轻松的得知对每一部分的提醒,对整个系统带来的影响…...
