黑龙江建设厅网站首页,网站怎么更新内容,网页设计公司业绩介绍,平面设计做兼职网站一、摘要
熟悉 Java 语言特性的同学都知道#xff0c;相比 C、C 等编程语言#xff0c;Java 无需通过手动方式回收内存#xff0c;内存中所有的对象都可以交给 Java 虚拟机来帮助自动回收#xff1b;而像 C、C 等编程语言#xff0c;需要开发者通过代码手动释放内存资源相比 C、C 等编程语言Java 无需通过手动方式回收内存内存中所有的对象都可以交给 Java 虚拟机来帮助自动回收而像 C、C 等编程语言需要开发者通过代码手动释放内存资源否则会导致内存溢出。
尽管如此如果编程不当Java 应用程序也可能会出现内存溢出的现象例如下面这个异常
Exception in thread main java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:2760)at java.util.Arrays.copyOf(Arrays.java:2734)at java.util.ArrayList.ensureCapacity(ArrayList.java:167)at java.util.ArrayList.add(ArrayList.java:351)它表示当前服务已出现内存溢出简单的说就是当服务出现了内存不足时就会抛OutOfMemoryError异常。
这种异常是怎么出现的呢该如何解决呢
熟悉 JVM 内存结构的同学可能会很快看得出以上错误信息表示虚拟机堆内存空间不足因此了解 JVM 内存结构对快速定位问题并解决问题有着非常重要的意义。今天我们一起来了解一下 JVM 内存结构。
本文以 JDK1.7 版本为例不同的版本 JVM 内存布局可能稍有不同但是所涉及的知识点基本大同小异。
二、内存结构介绍
Java 虚拟机在执行程序的过程中会把所管理的内存划分成若干不同的数据区域。这些区域各有各有的用途有的区域会随着虚拟机进程的启动而一直存在有的区域会伴随着用户线程的启用和结束而创建和销毁。
其次JVM 内存区域也称为运行时数据区域这些数据区域包括程序计数器、虚拟机栈、本地方法栈、堆、方法区等可以用如下图来简要概括。 其中运行时数据区的程序计数器、虚拟机栈、本地方法栈属于每个线程私有的区域堆和方法区属于所有线程间共享的区域。
运行时数据区的线程间内存区域布局可以用如下图来简要描述 2.1、程序计数器
程序计数器Program Counter Register是一块较小的内存空间它的作用可以看做是当前线程所执行的字节码的行号指示器。
在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令比如分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
我们知道 Java 是支持多线程的其中虚拟机的多线程就是通过轮流切换线程并分配处理器执行时间的方式来实现的。在任何一个确定的时刻一个处理器对于多核处理器来说是一个内核只会执行一条线程中的指令为了线程切换后能恢复到正确的执行位置虚拟机为每个线程都设计了一个独立的程序计数器各条线程之间的程序计数器互不影响独立存储属于线程私有的内存区域生命周期与线程相同。
在 JVM 规范中如果线程执行的是非native方法则程序计数器中保存的是当前需要执行的指令的地址如果线程执行的是native方法则程序计数器中的值是Undefined也就是空。
由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变因此此内存区域是唯一一个在 JVM 规范中没有规定任何OutOfMemoryError情况的区域。
2.2、虚拟机栈
虚拟机栈Java Virtual Machine Stacks与程序计数器一样也是线程私有的内存区域它的生命周期与线程相同。
虚拟机栈描述的是 Java 方法执行时的内存模型每个方法执行的时候都会创建一个栈帧Stack Frame 用于存储局部变量表、操作数栈、动态链接、方法出口和一些额外的附加信息。每一个方法从被调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的全过程。
虚拟机栈内部结构可以用如下图来简要描述。 2.2.1、局部变量表
局部变量表是一组变量值的存储空间用于存储方法参数和局部变量例如 基本数据类型比如 boolean、byte、char、short、int、float、long、double 等 8 种基本数据类型 对象引用类型指向对象起始地址的引用指针 返回地址类型指向一条字节码指令的返回地址
通常局部变量表的内存空间在编译器就会确定其大小当进入一个方法时这个方法需要在帧中分配多大的局部变量空间是可以完全确定的因此在程序执行期间局部变量表的大小是不会改变的。
其次局部变量表的最小单位为 32 位的字长对于 64 位的 long 和 double 变量而言虚拟机会为其分配两个连续的局部变量空间。
2.2.2、操作数栈
操作数栈也常称为操作栈是一个后入先出的栈。虚拟机会利用操作栈的压栈和出栈操作来执行指令运算。
比如下面的两个数据相加的计算示例。
begin
iload_0 // push the int in local variable 0 onto the stack
iload_1 // push the int in local variable 1 onto the stack
iadd // pop two ints, add them, push result
istore_2 // pop int, store into local variable 2
end在这个字节码序列里前两个指令iload_0和iload_1将存储在局部变量表中索引为0和1的整数压入操作数栈中接着iadd指令从操作数栈中弹出那两个整数相加再将结果压入操作数栈最后istore_2指令从操作数栈中弹出结果并把它存储到局部变量表索引为2的位置完成数据的计算。
2.2.3、动态链接
每个栈帧都包含一个对当前方法类型的运行时常量池的引用以支持方法调用过程中的动态链接。可以简单的理解成当前栈帧与运行时常量池的方法引用建立链接。
比如方法 a 入栈后栈帧中的动态链接会持有对当前方法所属类的常量池的引用当方法 a 中调用了方法 b符号引用就可以通过运行时常量池查找到方法 b 具体的直接引用方法地址然后调用执行。
2.2.4、方法出口
当一个方法执行完毕之后要返回之前调用它的地方因此在栈帧中必须保存一个方法返回地址也称为方法出口。
在虚拟机栈中只有两种方式可以退出当前方法 正常返回当执行遇到返回指令会将返回值传递给上层的方法调用者这种退出方式称为正常返回一般来说调用者的程序计数器可以作为方法返回地址 异常返回当执行遇到异常并且当前方法体内没有得到处理就会导致方法退出此时是没有返回值的这种退出方式称为异常返回返回地址要通过异常处理器表来确定
当一个方法返回时可能依次进行以下 3 个操作 1.恢复上层方法的局部变量表和操作数栈 2.把返回值压入调用者栈帧的操作数栈 3.将程序计数器的值指向下一条方法指令位置
2.2.5、小结
在 JVM 规范中对这个内存区域规定了两种异常状况 如果当前线程请求的栈深度大于虚拟机栈所允许的深度将抛出StackOverFlowError异常当前虚拟机栈不允许动态扩展的情况下 如果虚拟机栈可以动态扩展当扩展到无法申请内存到足够的内存就会抛出OutOfMemoryError异常
2.3、本地方法栈
本地方法栈Native Method Stacks与虚拟机栈发挥的作用非常相似主要区别在于虚拟机栈为虚拟机执行 Java 方法也就是字节码服务本地方法栈则是为虚拟机使用到的Native方法服务通常采用 C 编写。
有些虚拟机发行版本比如Sun HotSpot虚拟机直接将本地方法栈和 Java 虚拟机栈合二为一。与虚拟机栈一样本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
2.4、堆
Java 堆是被所有线程共享的最大的一块内存区域在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例几乎所有的对象实例和数组都在这里分配内存也是出现OutOfMemoryError异常最常见的区域。
在虚拟机中堆被划分成两个不同的区域年轻代 (Young Generation) 和老年代 (Old Generation)默认情况下按照1 : 2的比例来分配空间。
其中年轻代又被划分为三个不同的区域Eden 区、From Survivor 区、To Survivor 区默认情况下按照8 : 1 : 1的比例来分配空间。
整个堆内存的空间划分可以用如下图来简要描述。 这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象包括内存的分配以及回收。
新创建的对象分配会首先放在年轻代的 Eden 区此区的对象回收频次会比较高Survivor 区作为 Eden 区和 Old 区之间的缓冲区在 Survivor 区的对象经历若干次收集仍然存活的就会被转移到老年代 Old 区。
关于对象内存回收的相关知识我们在后续的文章会再次进行介绍。
2.5、方法区
方法区在 JVM 中也是一个非常重要的区域和 Java 堆一样也是多个线程共享区域它用于存储类的信息包括类的名称、方法信息、字段信息、静态变量、常量以及即时编译后的代码等数据。
为了与 Java 堆区分它还有一个别名 Non-Heap非堆的意思。相对而言GC 对于这个区域的收集是很少出现的但是也不意味着不会出现异常当方法区无法满足内存分配需求时也会抛出OutOfMemoryError异常。
在 Java 7 及之前版本大家也习惯称方法区它为“永久代”Permanent Generation更确切来说应该是“HotSpot 使用永久代实现了方法区”
2.6、运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外还有一项信息是常量池 (Constant pool table)用于存放编译期生成的各种字面量和符号引用这部分内容将在类加载后进入运行时常量池中存放。
运行时常量池的功能类似于传统编程语言的符号表方便下游程序通过查表可找到对应的数据信息。
同时运行时常量池相对于Class文件常量池的另外一个特性是具备动态性Java 语言并不要求常量一定只有编译器才产生也就是说并非预置入Class文件中常量池的内容才能进入方法区运行时常量池运行期间也可能将新的常量放入池中其中String.intern()方法就是这个特性的应用。
2.7、直接内存
在之前的 Java NIO 文章中我们提及到直接内存。直接内存Direct Memory并不是虚拟机运行时数据区的一部分也不是 JVM 规范中定义的内存区域。
在 JDK1.4 中引入了 NIO 机制它允许 Java 程序直接从操作系统中分配直接内存这部分内存也被称为堆外内存在某些场景下可以提高程序执行性能因为避免了在 Java 堆和 Native 堆中来回复制数据的耗时。
Java NIO 创建堆外内存的简单示例。
// 创建直接内存
ByteBuffer byteBuffer ByteBuffer.allocateDirect(1024);这部分内存如果出现资源不足也可能导致OutOfMemoryError异常出现。
三、内存设置相关的命令
所有内存溢出的问题除了代码可能存在问题以外更直观的问题是内存空间不足如何通过参数来控制各区域的内存大小呢 3.1、堆内存大小相关参数设置
1-Xms
设置堆的最小空间大小此值必须是 1024 的倍数且大于 1 MB。附加字母 k 或 k 表示千字节m 或 m 表示兆字节g 或 g 表示千兆字节其它命令参数同理。比如-Xms1024m表示堆的最小内存为1024M默认值为物理内存的1/64。
2-Xmx
设置堆的最大空间大小此值必须是 1024 的倍数且大于 2 MB。比如-Xmx2048m表示堆最大内存为2G默认值为物理内存的1/4。
对于服务器部署-Xms和-Xmx通常建议设置为相同的值以避免堆的内存空间频繁扩缩。
3-XX:HeapDumpOnOutOfMemoryError
表示可以让虚拟机在出现内存溢出异常时 Dump 出当前的堆内存转储快照
3.2、年轻代内存大小相关参数设置
1-XX:NewSize
设置年轻代的最小空间大小比如-XX:NewSize256m表示年轻代的最小内存为256M。
GC 在这个区域比在其他区域执行的频率更高如果年轻一代的设置太小那么将进行大量的小频率 GCs。如果设置太大那么会执行完整的GCs这可能需要很长时间才能完成。Oracle 建议将年轻一代的大小保持在堆总大小的一半到四分之一之间。同时该值需要小于-Xms的值。
2-XX:MaxNewSize
设置年轻代的最大空间大小比如-XX:MaxNewSize512m表示年轻代的最大内存为512M。
3-Xmn
设置年轻代堆的初始大小和最大大小比如-Xmn128m表示年轻代的初始大小和最大大小为128M。
这个参数是对-XX:newSize、-XX:MaxnewSize两个参数同时进行配置虽然会很方便但需要注意的是这个参数是在 JDK1.4 版本以后才加入的低于此版本无法使用。
没有直接设置老年代的参数但是可以设置堆空间大小和年轻代空间大小两个参数来间接控制公式如下
老年代空间大小 堆空间大小 - 年轻代空间大小3.3、比例方式相关参数设置
1-XX:NewRatio
设置年轻代和老年代大小之间的比例默认值是-XX:NewRatio2表示Young : Old 1 : 2。
2-XX:SurvivorRatio
设置 Eden 空间大小和 Survivor 空间大小之间的比例默认值是-XX:SurvivorRatio8表示Eden : from : to 8 : 1 : 1。
3-XX:MinHeapFreeRatio
设置 GC 事件后允许的最小可用堆空间百分比0到100如果可用堆空间低于此值则堆将被扩展。默认情况下此参数为-XX:MinHeapFreeRatio40表示40%。
4-XX:MaxHeapFreeRatio
设置 GC 事件后允许的最大可用堆空间百分比0到100。如果可用堆空间高于此值则堆将被缩小。默认情况下此参数为-XX:MaxHeapFreeRatio70表示70%。
3.4、非堆区相关参数设置
1-XX:PermSize
设置永久代的最小空间大小比如-XX:PermSize256m表示永久代的最小内存为256M默认值为物理内存的1/64。
2-XX:MaxPermSize
置永久代的最大空间大小比如-XX:MaxPermSize512m表示永久代的最大内存为512M默认值为物理内存的1/4。
值得注意的是-XX:PermSize和-XX:MaxPermSize这两个参数在 JDK1.7 及以前的版本中有效在 JDK1.8 中已经被弃用被-XX:MetaspaceSize和-XX:MaxMetaspaceSize两个参数取代。
3.5、栈内存相关参数设置
1-Xss
设置每个线程的栈大小比如-Xss1024k表示每个线程的堆栈空间大小为1024KB通常不需要我们调整设置默认值取决于平台 Linux/ARM (32-bit)320 KB Linux/i386 (32-bit)320 KB Linux/x64 (64-bit)1024 KB OS X (64-bit)1024 KB Oracle Solaris/i386 (32-bit)320 KB Oracle Solaris/x64 (64-bit)1024 KB
2-Xoss
设置每个线程中的本地方法栈大小比如-Xoss128k表示每个线程中的本地方法栈大小为128KB不过 HotSpot 并不区分虚拟机栈和本地方法栈因此对于 HotSpot 来说这个参数是无效的。
3.6、堆外内存相关参数设置
1-XX:MaxDirectMemorySize
此参数的含义是通过Direct ByteBuffer方式分配的最大堆外内存大小。比如-XX:MaxDirectMemorySize60m表示堆外最大内存不能超过60M如果没有设置默认是 0JVM 会自动申请内存的大小最大大小受限于-Xmx值。
四、内存溢出的几种场景
在上文中我们介绍了 JVM 内存结构以及可能会发生的异常状况下面我们一起来复现一下几种常见的内存溢出现象。
4.1、堆溢出
堆溢出测试类如下。
/*** 虚拟机参数 -Xms10m -Xmx10m -XX:HeapDumpOnOutOfMemoryError*/
public class HeapOOMTest {public static void main(String[] args) {ListHeapOOMTest list new ArrayList();while (true){list.add(new HeapOOMTest());}}
}在 IDEA 中设置 JVM 相关的参数。 运行后输出结果如下
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid21886.hprof ...
Exception in thread main java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3210)at java.util.Arrays.copyOf(Arrays.java:3181)at java.util.ArrayList.grow(ArrayList.java:265)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)at java.util.ArrayList.add(ArrayList.java:462)at HeapOOMTest.main(HeapOOMTest.java:21)
Heap dump file created [12920047 bytes in 0.090 secs]从报错的日志上可以清晰的看到出现内存溢出的区域在Java heap space问题代码在HeapOOMTest.java:21。生成的快照文件在当前工程目录下。
4.2、虚拟机栈和本地方法栈溢出
栈溢出测试类如下JVM 相关的参数设置步骤同上。
/*** 虚拟机参数 -Xss256k*/
public class StackOOMTest {private int stackLength 1;public static void main(String[] args) {StackOOMTest stackOOMTest new StackOOMTest();try {stackOOMTest.stackLeak();} catch (Throwable e){System.out.println(stack length: stackOOMTest.stackLength);throw e;}}private void stackLeak() {stackLength;stackLeak();}
}运行后输出结果如下
Exception in thread main java.lang.StackOverflowError
stack length:2326at StackOOMTest.stackLeak(StackOOMTest.java:23)at StackOOMTest.stackLeak(StackOOMTest.java:23)at StackOOMTest.stackLeak(StackOOMTest.java:23)at StackOOMTest.stackLeak(StackOOMTest.java:23)at StackOOMTest.stackLeak(StackOOMTest.java:23)......在单个线程下当栈帧的深度过大也会超出虚拟机栈的最大容量当无法分配内存的时候虚拟机就会抛出StackOverflowError异常。
我们在来看另一个例子。
/*** 虚拟机参数 -Xss256k*/
public class StackOOMTest2 {public static void main(String[] args) {StackOOMTest2 stackOOMTest new StackOOMTest2();stackOOMTest.stackLeakByThread();}private void stackLeakByThread() {while (true) {new Thread(new Runnable() {Overridepublic void run() {running();}}).start();}}private void running() {while (true) {}}
}运行后输出结果如下
Exception in thread main java.lang.OutOfMemoryError: unable to create new native thread在无限制的创建多个线程下虚拟机栈也可能会出现OutOfMemoryError异常此时操作系统会出现假死 CPU 被完全跑满了测试过程中发现操作系统下所有的应用无法正常操作请谨慎测试。以上报错内容引入网上博主的测试结果
4.3、方法区和运行时常量池溢出
运行时常量池属于方法区的一部分这两个区域中我们抽取运行时常量池区域来测试内存溢出的现象。
针对这个区域我们可以采用String.intern()方法进行测试。String.intern()是一个Native方法意思是如果常量池中有一个String对象的字符串就返回池中的这个字符串的String对象否则将此String对象包含的字符串添加到常量池中去并且返回此String对象的引用。
测试代码如下JVM 相关的参数设置步骤同上。
/*** 虚拟机参数-XX:PermSize10M -XX:MaxPermSize10M*/
public class RuntimeConstantPoolOOMTest {public static void main(String[] args) {ListString list new ArrayList();int i 0;while (true) {list.add(String.valueOf(i).intern());}}
}运行后输出结果如下
Exception in thread main java.lang.OutOfMemoryError: PermGen spaceat java.lang.String.intern(Native Method)实际上这个异常只会出现在 JDK1.6 及之前的版本中在 JDK1.7 中是不会有这个异常的它会一直while循环下去。
在上文中我们介绍过在 JDK1.7 及之前的版本中方法区也被称为永久代因此看到的是PermGen space区域的OutOfMemoryError异常信息。
但在 JDK1.8 及之后的版本中没有-XX:PermSize和-XX:MaxPermSize这两个参数取而代之的是-XX:MetaspaceSize和-XX:MaxMetaspaceSize这两个参数同时方法区被称为元空间并划入到本地内存中。
4.4、直接内存溢出
直接内存溢出测试类如下JVM 相关的参数设置步骤同上。
/*** 虚拟机参数 -XX:MaxDirectMemorySize2048k*/
public class DirectMemoryTest {public static void main(String[] args){int i 0;ListByteBuffer buffers new ArrayList();while (true) {ByteBuffer bb ByteBuffer.allocateDirect(1024 * 1024 * 1);buffers.add(bb);System.out.println(i);}}
}运行后输出结果如下
0
1
Exception in thread main java.lang.OutOfMemoryError: Direct buffer memoryat java.nio.Bits.reserveMemory(Bits.java:694)at java.nio.DirectByteBuffer.init(DirectByteBuffer.java:123)at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)at DirectMemoryTest.main(DirectMemoryTest.java:22)从日志上可以清晰的看到OutOfMemoryError的异常区域为Direct buffer memory也就是直接内存区域。 五、JDK 各版本内存布局变化
在上文中我们也提及到过不同的版本 JVM 内存布局可能有所不同。最后我们再一起来看下 JDK 1.6、1.7、1.8 的内存模型演变过程。 每一次的调整改动都是为了更好的适应当下 CPU 性能最大限度的提升 JVM 运行效率各个版本的差异如下 在 JDK1.6 中常量池存放于方法区也被称为永久代 在 JDK1.7 中将常量池进行细分字符串常量池存放于堆中运行时常量池和类常量池存放于方法区中 在 JDK1.8 中无永久代将运行时常量池和类常量池都保存在元数据区中也就是大家常说的元空间但字符串常量池仍然存放在堆上
关于各个内存区域的变化有些面试官会提出以下一些问题我们一起来看下。
问题一在 JDK 1.7 中为什么要将字符串常量池移动到堆中
这个问题的主要原因在于 GC 的回收效率上在永久代中的数据 GC 回收效率非常低只有在整堆收集 (Full GC) 的时候才会被执行 GC而 Java 程序中通常会有大量的被创建的字符串需要等待回收将字符串常量池放到堆中能够更高效及时的回收字符串释放内存。
问题二JDK 1.8 为什么要废弃永久代用元空间取而代之
HotSpot 团队选择移除永久代简单的说有两个因素 外因在之前的文章中我们说到过Oralce 拥有 JRockit 与 HotSpot 两款优秀的虚拟机在 JRockit 中并没有永久代为了将 JRockit 优秀的设计融入 HotSpot 中在 JDK 1.8 中 HotSpot 移除了永久代 内因JDK 1.7 中永久代大小受-XX:PermSize和-XX:MaxPermSize这两个参数的限制这两个参数在物理空间上又受到 JVM 设定的内存大小限制这就会导致在使用中永久代可能出现内存溢出的问题因此在 JDK 1.8 及之后的版本中彻底移除了永久代用元空间来进行替代其中元空间并不在虚拟机内存中而是使用本地内存相比 JDK 1.7 而言出现内存溢出的风险要小很多但也不是完全不限制其大小受操作系统可用内存大小的限制也支持通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize这两个参数来配置
如果想要在 JDK1.8 中测试元空间的内存溢出现象可以通过 Cglib 动态代理框架来创建类它会将类存放在元空间测试示例如下。
/*** 虚拟机参数 -XX:MetaspaceSize10m -XX:MaxMetaspaceSize10m*/
public class MetaspaceOOMTest {public static void main(String[] args) {int i 0;try {while (true) {Enhancer enhancer new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});// 创建一个动态代理类enhancer.create();i;}} catch (Throwable e) {System.out.println(第 i 次时发生异常);e.printStackTrace();}}private static class OOMObject {public OOMObject() {}}
}运行后输出结果如下
第546次时发生异常
net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException--nullat net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:348)at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:117)at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)at MetaspaceOOMTest.main(MetaspaceOOMTest.java:26)
Caused by: java.lang.reflect.InvocationTargetExceptionat sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)... 6 more
Caused by: java.lang.OutOfMemoryError: Metaspaceat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:763)... 11 more从日志上可以清晰的看到执行到第 546 次时出现OutOfMemoryError内存区域在Metaspace。