文章目录
- 前言
- 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 的压力。添加此参数并设置为负号(-),表示禁用这个默认行为,不再提前进行新生代垃圾回收。
启动项目输出
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-垃圾回收器