文章目录
- System.gc()
- 内存溢出(OOM)
- OOM 的原因
- 内存泄漏
- 垃圾回收的并行与并发
- 安全点与安全区域
- Java 中的引用分类
- 强引用(Strong Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
- 垃圾回收器分类
- 按线程数分
- 按工作模式分
- 按是否压缩内存来分
- 按工作区间分
- 垃圾回收器的性能指标
- 垃圾收集器
- JDK 8 的默认垃圾收集器
- 查看 JDK 的默认垃圾收集器
- Serial 收集器
- ParNew 收集器
- Parallel 收集器
- CMS(Concurrent Mark Sweep 并发标记清除收集器
- CMS 四个过程
- 参数设置
- G1(Garbage-First)收集器
- 参数设置
- Region 详解
- G1 垃圾回收过程
- 如何选择垃圾收集器
- GC 相关参数
System.gc()
默认情况下,调用 System.gc(),其实际是调用的 Runtime.getRuntime().gc()(其实一个 native 方法),会显式的触发 Full GC,同时对新生代和老年代进行回收。但其调用无法保证一定会进行 GC,JVM 会在适当的时候进行 GC。
调用 System.runFinalization(); 方法可以强制调用失去引用的对象的 finalize 方法
内存溢出(OOM)
OOM,表示即便是 JVM GC 之后也没有空闲的内存可以使用了,就会导致 OOM。OOM 在新生代、老年代都会发生。
OOM 的原因
- 堆内存不够:堆内存设置过小,通过 -Xmx 和 -Xms 来调整堆大小
- 代码中创建了大量的对象且对象占用空间比较大。这种情况就是我们后续要进行内存优化或分析的重点情况。
- 元空间内存不够:元空间默认情况下是使用的本地内存,不受堆空间大小限制,但受限于服务器物理内存的限制,如果元空间出现OOM,则是物理可用内存不够了。
特殊的,当创建一个超大的对象时(比如占 200M 空间),但 JVM 设置的 堆空间(比如只有150M),那么堆空间是无法放下此对象的,直接报 OOM。
内存泄漏
严格来说,只有对象不会再被程序使用,但又无法通过 GC 释放,就是内存泄漏。但实际使用中,由于各种疏忽,导致某些对象的生命周期超长,长时间无法释放,最后导致这种对象越来越多,最后 OOM,目前也称之为内存泄漏。
- 单例:单例的生命周期一般是和应用程序一直,生命周期很长,在单例中如果引用了外部的对象,就能导致这个对象的生命周期延长,导致内存泄漏的情况
- 带 close 方法的对象,如果 io、socket、连接池等,要及时调用 close 方法释放资源,否则也会导致内存泄漏的问题。
垃圾回收的并行与并发
- 并行:只有多核CPU才有,多个线程分别在不同的CPU中并行执行。
- 并发:多个线程在同一个CPU中执行,在某个时间段内,这些线程在CPU中不停的切换执行,此过程是非常的快的,看起来像是这些线程在同时执行。(实际单位时间内只有一个线程在执行)
安全点与安全区域
- 安全点:应用程序并非在所有的时间点上都能直接停止下来执行 GC,只有在特点的程序执行位置才能开始 GC,这些位置称为“安全点”
一般选择一下执行时间较长的指令来设置安全点,比如:方法调用、循环跳转和异常跳转等。
- 安全区域是指在代码片段中,对应的引用关系不会发生变化,在这个区域的任何一个位置开始 GC 都是安全的。因为安全点并不连续,如果在两个安全点中间需要 GC,也会导致无法开始 GC 的问题。
Java 中的引用分类
强引用(Strong Reference)
我们平时的代码中,几乎所有都是强引用。如:Object obj = new Object(); 。垃圾回收时,只要强引用关系存在,在 GC 时该对象不会被回收。
软引用(Soft Reference)
在系统将要发生内存溢出之前,将会把这些对象列如回收范围中,在第二次进行回收时(先回收不可达对象),不可达对象回收后内存任然不足时,进行回收。当第二次回收之后还没有足够的内存,才会抛出 OOM。
标记一个对象为软引用
Object obj = new Object();
Reference<Object> ref = new SoftReference<Object>(obj);
System.out.println(obj);
System.out.println(ref.get());
ref.clear();
// obj 是强引用,GC 的时候 Object 还是不会被回收,所以以上的写法要么不要定义 obj 引用,要么在后面将 obj = null,去掉强引用。
System.out.println(obj);
System.out.println(ref.get()); // null
修改如下:
Object obj = new Object();
Reference<Object> ref = new SoftReference<Object>(obj);
obj = null;
System.out.println(obj);// null
System.out.println(ref.get());// not null
System.gc();
// 等待 gc 完成
Thread.sleep(10000);
// obj 是强引用,GC 的时候 Object 还是不会被回收,所以以上的写法要么不要定义 obj 引用,要么在后面将 obj = null,去掉强引用。
System.out.println(obj);// null
System.out.println(ref.get()); // not null 回收不可达对象之后,内存还不足时才会回收
Reference 是 java.lang.ref 包下的抽象类,其下有SoftReference、WeakReference、PhantomReference 等实现类
弱引用(Weak Reference)
当执行 GC 时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。软引用和弱引用都非常适合用来保存那些可有可无的缓存数据。
Object obj = new Object();
Reference<Object> ref = new WeakReference<>(obj);
obj = null;
System.out.println(obj);// null
System.out.println(ref.get());// not null
System.gc();
// 等待 gc 完成
Thread.sleep(10000);
System.out.println(obj);// null
System.out.println(ref.get()); // null 发现即回收
虚引用(Phantom Reference)
无法通过虚引用来获得一个对象的实例,为一个对象设置虚引用关联的唯一目的是在这个对象被垃圾收集时收到一个系统通知。虚引用对对象的生命周期没有任何影响。
// 虚引用,必须传入 ReferenceQueue 引用队列
Reference<Object> ref = new PhantomReference<>(new Object(),new ReferenceQueue<>());
System.out.println(ref.get()); // null 虚引用无法通过 get 方法获取
System.gc();
System.out.println(ref.get()); // null
引用强度:强引用 > 软引用 > 弱引用 > 虚引用
垃圾回收器分类
按线程数分
- 串行垃圾收集器:在cpu、内存等硬件不怎么好的平台使用,串行回收器默认被应用在客户端的 Client 模式下的 JVM 中(32 位的JDK 是默认的 Client 模式,64 位的 JDK 默认为 Server 模式)
- 并行垃圾收集器:在并发能力较强的平台下使用,并行回收器的停顿时间较串行回收器短。
按工作模式分
- 并发式回收器:与应用线程交替执行,尽可能的降低停顿时间
- 独占式回收器:停止所有用户线程,直到垃圾回收过程结束
按是否压缩内存来分
- 压缩式回收器
- 压缩后再分配内存空间使用指针碰撞的方式进行分配
- 非压缩式回收器
- 再分配内存空间使用空闲列表的方式进行分配
主要是以回收后,是否对内存碎片进行压缩整理(标记-压缩算法)
按工作区间分
- 年轻代的垃圾回收器
- 老年代的垃圾回收器
垃圾回收器的性能指标
- 吞吐量:运行用户代码的时间所占总运行时间(程序的运行时间+垃圾回收时间)的比例。
- 垃圾收集的开销:垃圾收集时间与总运行时间的比例。
- 暂停时间:stop the World 的时间
- 内存占用:堆区所占内存的大小
- 收集频率和时间:相对来说频率越小且时间越小说明性能高。
当前垃圾收集器最注重的2点:吞吐量 + 暂停时间。在最大吞吐量的前提下,尽量的降低暂停时间。
垃圾收集器
常用的回收器:
- 串行回收器:Serial、Serial Old
- 并行回收器:ParNew、 Parallel Scavenge 、 Parallel Old
- 并发回收器:CMS(Concurrent Mark Sweep 并发标记清除收集器)、G1(Garbage-First 垃圾优先收集器)
JDK 8 的垃圾收集文档中,(https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/),提及的两款垃圾收集器即为:CMS 和 G1
- 新生代垃圾收集器:Serial、ParNew、 Parallel Scavenge
- 老年代垃圾收集器:Serial Old、Parallel Old、CMS
- 整堆垃圾收集器:G1
图片太大,可以使用新标签单独打开或者下载下来查看。
JDK 8 的默认垃圾收集器
查看 JDK 的默认垃圾收集器
- 使用 -XX:+PrintCommandLineFlags 参数打印当前的应用程序中使用的垃圾收集器信息。示例信息如下:
-XX:InitialHeapSize=264885120 -XX:MaxHeapSize=4238161920 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
我们最后会看到,-XX:+UseParallelGC ,说明 JDK 8 默认使用的垃圾收集器为:ParallelGC,其对应的老年代收集器为 ParallelOldGC
- 使用 jinfo 命令查询
图中 13392 这个线程id就是我们当前的执行程序。
Serial 收集器
Serial 收集器是一个串行的收集器,一般在可用内存较小(几十 M 到一两百 M ),且垃圾收集可以在较短的时间内完成的情况下。使用参数 -XX:UseSerialGC 指定老年代和新生代分别使用 SerialOldGC 和 SerialGC。(此处注意:没有 -XX:UseSerialOldGC的参数)。目前此收集器很少使用。
ParNew 收集器
ParNew 是并行回收器,其处理的是新生代的垃圾回收。其可以配合 CMS 和 Serial Old 来进行垃圾回收。使用参数 -XX:UseParNewGC 设置新生代使用 ParNew 收集器。使用 -XX:ParallelGCThreads 来限制线程的数量,默认数量为 CPU 的核数(建议不要超过 CPU 的核数)。目前此收集器也很少使用了。
Parallel 收集器
Parallel 收集器和 ParNew 收集器同样采用了复制算法、并行回收等机制,但 Parallel 有吞吐量优先的特性, Parallel 还有自适应调节策略。
参数配置
- -XX:+UseParallelGC 指定年轻代使用 Parallel 收集器收集
- -XX:+UseParallelOldGC 指定老年代使用 ParallelOld 收集器收集(此参数和 UserParallelGC 有一个开启,另一个自动开启)
- -XX:+ParallelGCThreads 设置年轻代 Parallel 收集器的线程数。最好于 CPU 核数相等。
- 默认情况下,如果 CPU 核数 < 8,ParallelGCThreads = CPU 核数,如果 CPU 核数 > 8,则 ParallelGCThreads = 3 + [(5 * CPU) / 8]
- -XX:MaxGCPauseMillis 设置垃圾回收器最大的停顿时间(stop the World)。单位毫秒
- 垃圾收集器会尽可能的把停顿时间控制在 MaxGCPauseMillis 以内,官网称此为软目标(尽可能的意思),建议谨慎设置此参数
- -XX:+UseAdaptiveSizePolicy 启用自适应内存大小控制,默认为启用。在此模式下,年轻代的大小、eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到堆空间、吞吐量和停顿时间点一个平衡点。建议使用此参数即可。
CMS(Concurrent Mark Sweep 并发标记清除收集器
尽可能的降低 STW 的时间(低延迟),采用并发式标记清除算法进行垃圾收集,只负责老年的垃圾收集,且不能与 Parallel Scavenge 配合使用,只能和 Serial 或 ParNew 配合使用 。JDK 9 中已不推荐使用,被标记为 Deprecate。
CMS 四个过程
工作原理:初始标记、并发标记、重新标记、并发清理四个过程
- 初始标记(Initial-Mark):在此阶段,程序中所有的工作线程都将会因为“Stop The World”机制出现短暂的暂停,此阶段主要标记处 GC Roots 能直接关联到的对象,由于关联的对象少,所以这里速度很快。标记完成后,之前被暂停的线程就会恢复。
- 并发标记(Concurrent-Mark):从GC Roots 直接关联的对象开始遍历整个堆空间的对象,此过程耗时较长,但此过程是和用户线程并行的。
- 重新标记(Remark):由于并发标记阶段是和用户线程并行的,所以在重新标记阶段要修正并发标记期间因用户线程执行而产生的变动,对这些变动的对象进行重新标记。这个时间通常比初始标记长,但远比并发标记短。
- 并发清除(Concurrent-Sweep):此阶段删除掉标记为不可达的对象,释放内存空间。注意:此阶段不会移动可达对象,不会对内存进行压缩移动。
由于并发标记和并发清除阶段是和用户线程一起并行的,所以 CMS 收集器可以达到低延迟的一个标准。
也是由于用户线程和垃圾收集线程并行,如果确实没有对内存进行执行,此时 JVM 会启用 Serial Old 对老年代进行垃圾收集。
也是由于用户线程和垃圾收集线程并行,所以说不能使用标记压缩算法的(压缩时会改变对象的地址,改变对象的地址用户线程就无法执行了)
CMS 收集器是使用标记清除算法,会产生内存碎片,也只能使用空闲列表的方式进行再分配。
参数设置
- -XX:+UseConcMarkSweepGC
设置老年代使用 CMS 垃圾收集器。开启此参数后,将自动开启 -XX:UseParNewGC 。即新生代使用 ParNewGC ,老年代使用 CMS ,当发生 CMS 无法并行执行的时候启用 SerialOldGC - -XX:CMSInitiatingOccupancyFraction=percent
设置初始化时触发老年代 CMS 垃圾回收的内存百分比。比如 80,表示内存使用率达到 80% 时,CMS 开始进行垃圾回收。默认为 -1。- 负数表示,CMS 使用 -XX:CMSTriggerRatio 来定义触发老年代 CMS 垃圾回收的阀值。
- -XX:MinHeapFreeRatio=百分比
设置GC事件后允许的最小可用堆空间百分比(0到100)。如果可用堆空间低于此值,则堆将被扩展。默认情况下,此值设置为40%。 - -XX:CMSTriggerRatio:设置触发老年代 CMS 垃圾回收的内存百分比(占用 -XX:MinHeapFreeRatio 所设置的空间)。比如 80,表示内存使用率达到 80% 时,CMS 开始进行垃圾回收。默认为 80。
本文中提到了很多的参数,这些参数的设置官方文档就是 java 命令的文档 (https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html)
G1(Garbage-First)收集器
区域化分代式垃圾收集器,G1 是并行收集器,它把内存分割为很多不想关的区域(Region),每个Region可能是 Eden区,幸存者1区或幸存者0区,老年代等,这样可以避免在整堆中进行垃圾收集,将整个堆化整为零,一次回收其中一个或一些 Region,G1 进行垃圾回收时,会优先收集价值比较大的Region(可以认为当前不可达对象比较多的 Region 为价值比较大)。其在尽可能的保证低延迟的情况下提高吞吐量,其即可以回收新生代又可以回收老年代。
G1 在 JDK 7 加入,JDK 9 被设定为默认的垃圾回收器。
- G1 在回收期间,可以有多个 GC 线程同时工作,可以有效利用多核计算能力,此时用户线程是挂起的。
- G1 任然是分代收集的,只是 G1 中 Eden 区可能是多个 Region 组成,它们在内存中可能是不连续的。
- G1 将内存划分为一个个的 Region,内存回收以 Region 为单位,Region 直接使用复制算法,但整体上可以看作标记压缩算法,所以 G1 可以有效避免内存碎片问题。Java 堆比较大的时候(大内存多处理器),G1 的优势会比较明显。
- 由于 G1 不是整堆进行回收,它可以判断价值较大的 Region 进行垃圾回收的停顿时长,可以根据停顿时长进行有效的控制回收哪些 Region(原则上将,每次回收的 Region 较少,停顿时间就较少)。
参数设置
- -XX:+UseG1GC
启用 G1 垃圾收集器 - -XX:G1HeapRegionSize=size
设置每个 Region 的大小。范围是 1M ~ 32M,默认大小是根据堆的大小计算的,大概是堆大小的 1/2000。在 JVM 执行过程中每个 Region 的大小都是相同的且不会改变。 - -XX:MaxGCPauseMillis=time
设置期望的最大 GC 停顿时间。默认 200ms。(JVM 不一定能达到此设置) - -XX:ParallelGCThreads=threads
设置年轻代和老年代并行回收线程数,默认为CPU的数量 - -XX:ConcGCThreads=threads
设置并发线程数。大概设置为 ParallelGCThreads 的 1/4 即可。 - -XX:InitiatingHeapOccupancyPercent=percent
设置堆占用率的百分比(0到100),在该百分比处启动并发GC循环。它由垃圾收集器使用,垃圾收集器根据整个堆的占用情况触发并发GC循环。- 默认情况下,初始值设置为45%。值为0表示不间断GC循环。
Region 详解
- E 表示 Eden 区
- S 表示幸存者区
- O 老年代
- H Humongous(翻译为巨大的):用于存储大对象,当一个对象大小大于 1.5 倍 Region 的时候,就会放入此区域。如果H区都无法存储,G1 会寻找连续的H区来存储,如果还找不到,可能导致 Full GC。G1 会将 H区当作老年代来对待。
官方图片如下,红色为 Eden 区,红色+S字母为幸存者区,H 为 Humongous。
G1 垃圾回收过程
- 年轻代GC(Young GC):当年轻代内存不足时,触发 Young GC,G1 暂停所有应用程序线程,启动多个GC线程进行垃圾回收,对象移动过程和原来一样(Eden 区存活对象移动到幸存者区,达到年龄的对象移动到老年代等)
- 老年代并发标记过程(Concurrent Marking):当内存达到阀值(默认45%)时,开始老年代并发标记过程
- 混合回收(Mixed GC):标记完成马上开始混合回收过程,混合回收时,不需要收集整个老年代全部回收,可能只回收老年代的部分Region。
- Full GC
垃圾收集的相关知识可参考 JDK 8 的官方文档 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/
如何选择垃圾收集器
在通常情况下,我们可以直接提高堆内存大小来提高性能,但在无法提高堆内存大小的情况下,再来手动调整垃圾收集器
- 如果应用程序的数据集较小(100 MB),则使用选项选择串行收集器-XX:+UseSerialGC .
- 如果应用程序将在单处理器上运行,并且没有暂停时间要求,则使用选项选择串行采集器-XX:+UseSerialGC .
- 如果应用程序性能是第一优先级,并且没有暂停时间要求,或者暂停1秒或更长时间是可以接受的,那么让VM选择收集器或选择并行收集器-XX:+UseParallelGC 。(吞吐量优先)
- 如果响应时间比总吞吐量更重要,并且垃圾收集暂停必须保持在大约1秒以下,则选择一个并发收集器 -XX:+UseG1GC (垃圾优先)或 -XX: 使用ConcMarkSweepGC(低延迟) 。
堆内存如果足够大(8G或以上,官网说明中是 10G),推荐使用 G1 收集器。
GC 相关参数
- -XX:+PrintGC
打印 GC 日志
2023-10-09T16:26:42.467+0800: [GC (Allocation Failure) 6743K->1008K(29696K), 0.0009465 secs]
2023-10-09T16:26:43.108+0800: [GC (Allocation Failure) 8390K->1144K(29696K), 0.0009188 secs]
2023-10-09T16:26:43.752+0800: [GC (Allocation Failure) 7585K->1184K(29696K), 0.0021388 secs]
2023-10-09T16:26:44.382+0800: [GC (Allocation Failure) 7469K->1232K(29696K), 0.0011422 secs]
2023-10-09T16:26:45.012+0800: [GC (Allocation Failure) 7524K->1248K(29696K), 0.0010169 secs]
2023-10-09T16:26:45.655+0800: [GC (Allocation Failure) 7544K->1264K(28672K), 0.0007992 secs]
2023-10-09T16:26:46.284+0800: [GC (Allocation Failure) 7543K->1259K(29184K), 0.0012118 secs]
2023-10-09T16:26:46.927+0800: [GC (Allocation Failure) 7540K->1259K(29184K), 0.0007576 secs]
2023-10-09T16:26:47.571+0800: [GC (Allocation Failure) 7541K->1259K(28160K), 0.0005698 secs]
2023-10-09T16:26:48.215+0800: [GC (Allocation Failure) 7542K->1259K(28672K), 0.0011637 secs]
2023-10-09T16:26:48.629+0800: [GC (Allocation Failure) 5474K->1259K(28672K), 0.0003956 secs]
2023-10-09T16:26:49.044+0800: [GC (Allocation Failure) 5474K->1259K(28672K), 0.0007266 secs]
2023-10-09T16:26:49.472+0800: [GC (Allocation Failure) 5474K->1259K(28672K), 0.0005000 secs]
说明(注:此时我们设置的堆内存大小为 30M):
- 前面的时间是 -XX:+PrintGCDateStamps 参数的作用。
- GC (Allocation Failure):表示 GC 发生的原因,Allocation Failure 表示内存分配失败
6743K->1008K(29696K):表示 GC 之前内存占用 6743K,GC 之后内存占用 1008K(GC 之后空余内存 29696K)
0.0009465 secs :表示 GC 时间
- -XX:+PrintGCDetails
打印详细 GC 日志
2023-10-09T16:40:37.840+0800: [GC (Allocation Failure) [PSYoungGen: 6743K->1018K(9216K)] 6743K->1026K(29696K), 0.0012821 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2023-10-09T16:40:38.481+0800: [GC (Allocation Failure) [PSYoungGen: 8400K->1016K(9216K)] 8408K->1152K(29696K), 0.0009312 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2023-10-09T16:40:39.119+0800: [GC (Allocation Failure) [PSYoungGen: 7457K->1016K(9216K)] 7593K->1184K(29696K), 0.0007436 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2023-10-09T16:40:39.763+0800: [GC (Allocation Failure) [PSYoungGen: 7301K->1016K(9216K)] 7469K->1240K(29696K), 0.0009514 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2023-10-09T16:40:40.406+0800: [GC (Allocation Failure) [PSYoungGen: 7308K->1016K(9216K)] 7532K->1256K(29696K), 0.0012255 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2023-10-09T16:40:41.045+0800: [GC (Allocation Failure) [PSYoungGen: 7312K->1000K(8192K)] 7552K->1264K(28672K), 0.0010059 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
- PSYoungGen 当前 GC 的类型,当前我们是默认的 Parallel 收集器,所以是 PSYoungGen,不同的收集器显示不一样。
- [PSYoungGen: 6743K->1018K(9216K)]:中括号内,年轻代垃圾回收之前空间占用 6743K,回收后空间占用 1018K(回收后空闲 9216K)
- 中括号外:6743K->1026K(29696K),年轻代 + 老年代的空间占用情况。
[Times: user=0.00 sys=0.00, real=0.00 secs]:user表示用户态回收耗时,real 表示实际耗时,sys表示内核态回收耗时。
- -XX:+PrintGCTimeStamps
打印 GC 时间戳 - -XX:+PrintGCDateStamps
打印 GC 日期时间戳 - -XX:+G1PrintHeapRegions
打印 G1 收集器当前收集了哪些 Region - -XX:+PrintTenuringDistribution
打印年龄信息
Desired survivor size 48286924 bytes, new threshold 10 (max 10)
- age 1: 28992024 bytes, 28992024 total
- age 2: 1366864 bytes, 30358888 total
- age 3: 1425912 bytes, 31784800 total
...
age 为 1 的对象是前一次 GC 幸存下来的对象,大小为 28992024 bytes,age 为 2 的对象是在前两次 GC 中幸存下来的对象,大小为 1366864 bytes,依此内推,每一行最后的 total 表示小于等于当前年龄的对象总大小。
示例中,28 992 024个字节在一次清除中幸存下来,并从eden复制到surviver空间,1 366 864个字节被年龄2的对象占用,等等。
- -Xloggc:filename 输出详细的 GC 日志到文件
- -XX:HeapDumpPath=path
将 OutOfMemoryError 转储到 .hprof 文件,此设置虚配合 -XX:+HeapDumpOnOutOfMemoryError 一起使用
示例:-XX:HeapDumpPath=./java_pid%p.hprof , %p 表示进程id
- -XX:+HeapDumpOnOutOfMemoryError
当出现 java.lang.OutOfMemoryError 异常,则将根据 -XX:HeapDumpPath=path 配置输出 .hprof 文件