程序崩溃日志开头如下:主要关键字G1ParCopyClosure
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007fe629cb5, pid=113675, tid=113703
#
# JRE version: Java(TM) SE Runtime Environment (14.0+36) (build 14+36-1461)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (14+36-1461, mixed mode, sharing, tiered, compressed oops, g1 gc, linux-amd64)
# Problematic frame:
# V [libjvm.so+0x6d0afa] G1ParCopyClosure<(G1Barrier)0,(G1Mark)0>::do_oop(unsigned int*)+0x5a
#
# Core dump will be written. Default location: Core dumps may be processed with "/usr/libexec/abrt-hook-ccpp %s %c %p %u %g %t e %P %I %h" (or dumping to /home/***/xxweb/core.113675)
#
# If you would like to submit a bug report, please visit:
这里一个重要信息是“SIGSEGV (0xb)”表示jvm crash时正在执行jni代码,而不是在执行java或者jvm的代码,如果没有在应用程序里手动调用jni代码,那么很可能是JIT动态编译时导致的该错误。
PS:除了“SIGSEGV(0xb)”以外,常见的描述还有“EXCEPTION_ACCESS_VIOLATION”,该描述表示jvm crash时正在执行jvm自身的代码,这往往是因为jvm的bug导致的crash;另一种常见的描述是“EXCEPTION_STACK_OVERFLOW”,该描述表示这是个栈溢出导致的错误,这往往是应用程序中存在深层递归导致的。
还有一个重要信息是:
# Problematic frame:
# V [libjvm.so+0x6d0afa] G1ParCopyClosure<(G1Barrier)0,(G1Mark)0>::do_oop(unsigned int*)+0x5a
这表示出现crash时jvm正在执行的操作“V”表示是 VM框架
还有可能是“C”、“j”、“V”、“v”,它们分别表示:
- C: Native C frame
- j: Interpreted Java frame
- V: VMframe
- v: VMgenerated stub frame
- J: Other frame types, including compiled Java frames
其中G1ParCopyClosure很明显,也可以由此判断出是在垃圾回收时出了问题。还有个关键日志
--------------- T H R E A D ---------------
这段日志是Java虚拟机(JVM)在发生崩溃或异常时生成的线程堆栈跟踪信息。从日志中,我们可以提取以下关键信息:
-
当前线程:
- 当前线程是一个名为"GC Thread#2"的垃圾收集(GC)任务线程,其ID为113703。
- 线程的栈内存范围为
[0x00007fe5cc4f0000,0x00007fe5cc5f0000]
,当前栈指针(sp)位置为0x00007fe5cc5ed750
,栈上剩余空间为1013k。
-
原生帧(Native frames):
- 这部分列出了崩溃发生时线程的调用栈,从JVM的底层开始,向上直到发生崩溃的位置。
- 可以看到,崩溃发生在
G1ParCopyClosure<(G1Barrier)0, (G1Mark)0>::do_oop(unsigned int*)
这个函数中,这个函数属于G1垃圾收集器的一部分,用于并行复制对象。 G1ParCopyClosure
、OopMapSet
、frame
、JavaThread
等函数和类都与JVM的垃圾收集机制相关。
-
Java帧(Java frames):
- 这部分显示了崩溃发生时Java层面的调用栈。
- 崩溃发生在
cn.hutool.core.util.ReflectUtil.invoke
方法中,该方法来自hutool
这个Java工具包。 - 接下来是
cn.hutool.core.bean.PropDesc.getValue
方法,与获取JavaBean属性的值有关。 - 接着是
cn.hutool.core.bean.copier.BeanCopier.valueProviderToBean
方法,这是用于将值提供者(ValueProvider)中的值复制到JavaBean对象中的操作。 - 最后是Lambda表达式
accept
方法,这通常是用于处理集合中元素的函数式接口方法。
分析:
- 崩溃发生在G1垃圾收集器的并行复制阶段,这通常与内存管理或垃圾收集器的配置有关。
ReflectUtil.invoke
方法的调用可能与反射操作有关,这可能会增加内存使用和垃圾收集的压力。PropDesc.getValue
和BeanCopier.valueProviderToBean
可能涉及JavaBean属性的访问和复制,这也可能涉及大量的内存分配和对象创建。ReflectUtil.invoke
方法的调用可能触发了大量的对象创建或内存分配,增加了垃圾收集的压力。- 如果
ReflectUtil.invoke
方法被频繁调用,或者调用的方法体很大,那么它可能会占用大量的栈空间,导致栈溢出或内存问题。
解决:
结合代码确实发现有循环中调用BeanUtil.copyPropertie,也确实会产生大量对象,那么具体是将这段代码修改还是优化G1配置则可以经过观察确定。
BeanUtil.copyPropertie替换掉吧
G1的配置可以尝试设置ParallelGCThreads和ConcGCThreads
java -XX:+PrintFlagsFinal -version 命令查看JVM默认参数具体可以grep查看
三种GC下,ParallelGCThreads默认值都是CPU线程数
ConcGCThreads 参数设置公式:
(参考https://download.youkuaiyun.com/blog/column/10400618/121548143)
- 并行Parallel GC下:ConcGCThreads = 0
- CMS GC下,ConcGCThreads=(ParallelGCThreads+3)/4下取整
- G1 GC下,ConcGCThreads=ParallelGCThreads/4四舍五入
以上是分析过程希望对你有帮助。