目录-----------------------------------------------------------------------------------------------------------------------------------------
-
1.jvm运行时内存区域划分
- 1.1 运行时内存区
- 1.1.1 方法区(Method Area)
- 1.1.2 堆区(Heap)
- 1.1.3 虚拟机栈(JVM Stack)
- 1.1.4 本地方法栈(Native Method Stack)
- 1.1.5 程序计数器(Program Counter Register)
- 1.1.6 直接内存(Direct Memory)
-
2.jvm垃圾回收算法
- 2.1 标记-清除(Mark-Sweep)算法
- 2.2 复制(Copying)算法
- 2.3 标记-整理(Mark-Compact)算法
- 2.4 分代收集(Generational Collection)算法
-
3.jvm垃圾回收器
- 3.1 Serial收集器
- 3.2 Parallel Scavenge收集器
- 3.3 CMS(Concurrent Mark Sweep)收集器
- 3.4 G1(Garbage-First)收集器
-
4.jvm年轻代,老年代,元空间回收策略
- 4.1 年轻代
- 4.2 老年代
- 4.3 元空间
- 5.调优参数
-
6.调优命令与工具
- 6.1 jps
- 6.2 jmap
- 6.3 jstack
- 6.4 jstat
- 6.5 jinfo
- 6.6 dump文件
-
7.调优案例
- 7.1 需要调优吗?
- 7.2 调优量化指标
- 7.3 监控前准备工作
- 7.4模拟案例
- 7.4.1 OutOfMemoryError
- 7.4.2 响应延迟
- 7.4.3 CPU飙升
1.jvm运行时内存区域划分
1.1 运行时内存区
1.1.1 方法区(Method Area)
- 在JDK 1.8中,方法区被实现为元空间(Metaspace)。这是与永久代(PermGen)不同的一个区域,它使用本地内存而不是JVM堆内存。
- 元空间存储了类的元数据,例如类的名称、字段、方法、常量池等。
- 与永久代不同,元空间的大小不再受JVM参数
-XX:MaxPermSize
的限制,而是由-XX:MaxMetaspaceSize
参数控制。
1.1.2 堆区(Heap)
- 堆区是JVM中用于动态分配内存的区域,是所有线程共享的。
- 堆区进一步细分为新生代(Young Generation)和老年代(Old Generation)。
- 新生代:又分为Eden区、Survivor From区和Survivor To区,主要用于存放新创建的对象,分配比例默认为三分之一的堆空间,默认情况下,Eden区占据了年轻代空间的80%,而每个Survivor区则各占10%。
- 老年代:用于存放长时间存活的对象。
- 当Eden区没有足够的空间来存放新创建的对象时,JVM会触发一次Minor GC(垃圾回收)。在这个过程中,仍存活的对象会被移动到一个Survivor区(如果对象的年龄未达到阈值),或者如果对象的年龄已经达到了阈值,它们会被移动到老年代(Old Generation)。
- JVM使用垃圾收集器(Garbage Collector)自动管理堆区内存,回收不再使用的对象占用的内存。
说明
在JDK 1.8的JVM中,可以通过以下参数来调整年轻代的分配比例:
-
-Xmn
:这个参数用于直接设置年轻代的大小。例如,-Xmn256m
将年轻代的大小设置为256MB。这个值会直接影响Eden区和Survivor区的大小,因为年轻代是Eden区和Survivor区的总和。 -
-XX:SurvivorRatio
:这个参数用于设置Eden区与一个Survivor区的大小比例。例如,-XX:SurvivorRatio=8
表示Eden区与一个Survivor区的大小比例为8:1。如果Eden区的大小为8GB,那么每个Survivor区的大小就是1GB。 -
-XX:NewRatio
:这个参数用于设置年轻代与老年代的大小比例。例如,-XX:NewRatio=2
表示年轻代与老年代的大小比例为1:2。如果堆的总大小为3GB,那么年轻代的大小为1GB,老年代的大小为2GB。 -
-XX:MaxNewSize
:这个参数用于设置年轻代的最大值。当年轻代的使用量达到这个值时,JVM会触发一次Minor GC。 -
-XX:MinHeapFreeRatio
和-XX:MaxHeapFreeRatio
:这两个参数用于调整堆内存的自动扩展和收缩行为。当空闲堆内存比例低于MinHeapFreeRatio
时,JVM会尝试扩展堆大小,直到达到-Xmx
指定的最大堆大小。当空闲堆内存比例高于MaxHeapFreeRatio
时,JVM会尝试收缩堆大小,直到达到-Xms
指定的初始堆大小
1.1.3 虚拟机栈(JVM Stack)
- 每个线程在创建时都会创建一个虚拟机栈,用于存储方法调用的状态信息,包括局部变量表、操作数栈、动态链接、方法出口信息等。
- 每个方法执行时都会创建一个栈帧(Stack Frame)来存储该方法的局部变量、操作数栈、常量池引用等信息。
- 栈帧随着方法的进入和退出而创建和销毁。
1.1.4 本地方法栈(Native Method Stack)
- 与虚拟机栈类似,本地方法栈用于支持native方法的执行。
- 当一个线程调用一个native方法时,JVM会创建一个新的栈帧在本地方法栈中,用于存储该native方法的局部变量、参数等信息。
1.1.5 程序计数器(Program Counter Register)
- 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
- 字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 它是线程私有的,每个线程都有一个独立的程序计数器。
1.1.6 直接内存(Direct Memory)
- 直接内存并不是JVM运行时数据区的一部分,但它是被JVM规范所涵盖的。
- 在JDK 1.4中引入了NIO(New I/O)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
- 使用直接内存可以避免在Java堆和Native堆之间来回复制数据,因此在某些场景下可以提高性能。
2.jvm垃圾回收算法
2.1 标记-清除(Mark-Sweep)算法
这是最基本的垃圾回收算法,分为“标记”和“清除”两个阶段。标记阶段从根对象开始,递归地访问对象图,标记所有可达的对象。清除阶段则遍历整个堆,回收所有未被标记的对象。这种算法的主要缺点是会产生内存碎片,影响程序的性能,其次标记阶段会stop-world
说明
java语言中作为GC Root对象的有以下几种
- 虚拟机栈中(栈帧中的本地变量表)的引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI的引用对象
2.2 复制(Copying)算法
为了解决内存碎片问题,复制算法将可用内存划分为两个等大的区域,每次只使用其中一个区域。当这个区域用尽后,垃圾回收器会将还存活的对象复制到另一个区域,然后清空当前使用的区域。这种算法在对象存活率较低时效率较高,但会浪费一半的内存空间。
2.3 标记-整理(Mark-Compact)算法
标记-整理算法结合了标记-清除和复制算法的优点。在标记阶段和标记-清除算法一样,但在清除阶段,它会将所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。这样既可以避免内存碎片的产生,也不需要额外的内存空间。
2.4 分代收集(Generational Collection)算法
基于对象存活周期的不同,将堆内存划分为几个不同的区域,每个区域使用不同的垃圾回收算法。在JDK 1.8中,年轻代(Young Generation)通常使用复制算法,而老年代(Old Generation)则使用标记-清除或标记-整理算法。
说明
在JVM(Java虚拟机)的内存管理中,堆内存被划分为年轻代(Young Generation)和老年代(Old Generation)。年轻代又被细分为Eden区、Survivor From区和Survivor To区,主要用于存放新创建的对象。这些区域的作用和存储内容如下:
-
Eden区:
- 作用:Eden区是年轻代中用于存放新创建对象的主要区域。当Java程序创建新对象时,这些对象首先会被分配到Eden区。
- 存储内容:新创建的对象,如果对象的大小超过了Eden区的可用空间,那么会触发一次Minor GC(垃圾回收),此时大对象会被直接晋升到老年代。
-
Survivor From区:
- 作用:Survivor From区用于存放经历了一次或多次Minor GC后仍然存活的对象。在每次Minor GC后,Eden区和Survivor From区中的存活对象会被复制到Survivor To区,然后清空Eden区和Survivor From区。
- 存储内容:在Minor GC后从Eden区复制过来的存活对象,以及之前Survivor To区复制过来的对象。
-
Survivor To区:
- 作用:Survivor To区的作用与Survivor From区类似,也用于存放经历了一次或多次Minor GC后仍然存活的对象。但在每次Minor GC后,Survivor From区和Eden区中的存活对象会被复制到Survivor To区,然后Survivor From区和Survivor To区的角色会互换。
-
存储内容:在Minor GC后从Eden区和Survivor From区复制过来的存活对象。
这种年轻代的设计(特别是Eden区和两个Survivor区的比例通常为8:1:1)是为了优化垃圾回收的性能和效率。通过复制算法,JVM可以快速地清理不再使用的对象,并为新对象提供空间。同时,通过动态调整对象的年龄和晋升策略,JVM可以平衡内存使用和性能之间的关系。
3.jvm垃圾回收器
3.1 Serial收集器
1.jdk1.3.1 之前都是使用这个收集器
2.垃圾回收器会停止所有用户线程
3.单线程处理垃圾回收
4.新生代使用复制算法,老年代使用标记-整理 算法
3.2 Parallel Scavenge收集器
使用多线程处理垃圾回收,jdk1.8 默认垃圾回收器
3.3 CMS(Concurrent Mark Sweep)收集器
cms收集器时为了获取最短回收停顿时间为目的的收集器,基于标记-清除算法实现,主要分为以下4个步骤
1.初始标记 :停止用户线程,标记GC-Roots能直接关联的对象,速度很快
2.并发标记:就是roots-tracing(寻根)过程
3.重新标记:修正并发标记过程中用户线程运行导致标记变动的对象的再次标记,停止用户线程,时间较长
4.并发清除:多线程清除回收对象
缺点
CMS收集器会产生内存碎片,并且不适合处理大量浮动垃圾的场景。
3.4 G1(Garbage-First)收集器
G1收集器
4.jvm年轻代,老年代,元空间回收策略
4.1 年轻代
年轻代主要存放新创建的对象,通常又被细分为Eden区和两个Survivor区(S0和S1)。年轻代的回收策略通常采用复制(Copying)算法和标记-清除(Mark-Sweep)算法。
- Eden区:新对象首先被分配到Eden区。当Eden区满时,会触发一次Minor GC(也称为Young GC)。在这次GC中,存活的对象会被复制到Survivor区(S0或S1),而死亡的对象会被清理掉。
- Survivor区:Survivor区用于存放经历了一次或多次Minor GC后仍然存活的对象。在每次Minor GC后,Eden区和一个Survivor区中的存活对象会被复制到另一个Survivor区,然后清空Eden区和被复制的Survivor区。这个过程会不断重复,直到对象晋升到老年代。
4.2 老年代
老年代存放长时间存活的对象。老年代的回收策略通常采用标记-清除(Mark-Sweep)算法和标记-整理(Mark-Compact)算法。
- 标记-清除算法:首先标记出存活的对象,然后清理掉未标记的(即死亡的)对象。这种算法可能会导致内存碎片。
-
标记-整理算法:在标记存活对象后,将所有存活的对象向一端移动,然后清理掉边界以外的内存。这样可以避免内存碎片的产生。
老年代的GC通常称为Major GC或Full GC,因为它会同时回收年轻代和老年代中的垃圾对象。Major GC的频率通常比Minor GC低,但每次GC的停顿时间可能会更长,对应用程序的影响也更大。
说明
虚拟机会给每个对象定义一个对象年龄计数器,在Eden区每经过一次Minor GC存活对象,并且Survivor区能个容纳这些存活对象,被移动到Survivor区后,年龄计数器就会+1 默认是15,超过这个阈值就会进入到老年区,可以通过参数-XX:MaxTenuringThreshold 来设置这个阈值
发生Minor GC 时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代剩余空间大小,如果大于,则直接进行一次Full GC ,如果小于则查看HandlePromotionFailure设置是否允许担保失败,如果允许,只进行MinorGC ,反之进行Full GC.
4.3 元空间
元空间的垃圾回收主要发生在类加载器不再需要加载类时,即当类加载器被垃圾回收时,它加载的类的元数据也会被清理掉。元空间的垃圾回收通常不会引发应用程序的停顿。
5.调优参数
-
堆内存设置:
-
-Xms
和-Xmx
:分别设置JVM启动时的初始堆内存和最大堆内存。例如,-Xms256m -Xmx256m
。 -
-XX:NewSize
和-XX:MaxNewSize
:设置年轻代的初始大小和最大大小。 -
-XX:SurvivorRatio
:设置Eden区与Survivor区的大小比值。例如,-XX:SurvivorRatio=8
表示Eden区与一个Survivor区的大小之比为8:1:1。
-
-
垃圾回收器选择:
-
-XX:+UseParallelGC
:选择Parallel Scavenge收集器和Parallel Old收集器作为垃圾回收器。 -
-XX:+UseConcMarkSweepGC
:选择CMS收集器。 -
-XX:+UseG1GC
:选择G1收集器。
-
-
垃圾回收调优:
-
-XX:ParallelGCThreads
:设置Parallel Scavenge收集器的线程数。 -
-XX:CMSInitiatingOccupancyFraction
:设置CMS收集器开始GC前老年代占用的百分比。 -
-XX:G1HeapRegionSize
:设置G1收集器中每个Region的大小。
-
-
元空间设置:
-
-XX:MetaspaceSize
:设置元空间的初始大小。 -
-XX:MaxMetaspaceSize
:设置元空间的最大大小。
-
-
其他调优参数:
-
-XX:+PrintGC
:打印GC日志。 -
-XX:+PrintGCDetails
:打印详细的GC日志。 -
-XX:+HeapDumpOnOutOfMemoryError
:当发生OutOfMemoryError时,导出堆的快照。 -
-XX:OnOutOfMemoryError
:当发生OutOfMemoryError时,执行指定的命令。 -
-XX:HeapDumpPath=<path-to-heap-dump-file>
: 指定堆转储文件的输出路径。 -
-Xloggc:<path-to-gc-log-file>
: 指定 GC 日志文件的输出路径。 -
-XX:+PrintGCDetails
: 打印详细的 GC 日志。 -
-XX:+PrintGCDateStamps
: 在 GC 日志中打印时间戳。 -
-XX:+PrintGCApplicationStoppedTime
: 打印 GC 暂停应用程序的时间。 -
-XX:+PrintHeapAtGC
: 在每次 GC 后打印堆的详细信息。 -
-XX:+PrintTenuringDistribution
: 打印对象年龄分布。 -
-XX:+UseGCLogFileRotation
: 启用 GC 日志文件轮换。 -
-XX:NumberOfGCLogFiles=<number>
: 设置要保留的 GC 日志文件的数量。 -
-XX:GCLogFileSize=<size>
: 设置每个 GC 日志文件的大小。 -
-XX:+PrintAdaptiveSizePolicy
: 打印自适应大小的 GC 策略信息。
-
6.调优命令与工具
6.1 jps
jps
是一个常用的命令行工具,用于显示指定系统内所有的HotSpot虚拟机进程,并显示JVM主类名或jar包名,以及JVM进程的本地虚拟机唯一标识符(LVMID,即pid)
常用方法如下
jps
这将列出所有HotSpot JVM进程的pid和主类名或jar包名。
jps -l
使用-l
选项,jps
会输出主类或jar的完全路径名。
ps -v
使用-v
选项,jps
会输出传递给JVM的命令行参数。
jps -m
使用-m
选项,jps
会输出传递给main方法的参数。
jps -l -v <pid>
你可以结合使用-l
和-v
选项,并指定pid,以获取特定JVM进程的详细信息和参数。
6.2 jmap
用于生成堆内存映射或堆转储文件。这对于分析内存泄漏和内存使用情况非常有帮助
常用命令如下
jmap -heap <pid>
这个命令用于打印堆内存的使用情况,包括堆内存的大小、新生代、老年代、元空间等区域的使用情况
jmap -dump:format=b,file=<filename> <pid>
这个命令用于生成堆的转储文件,通常用于后续的内存分析。format=b
表示输出的文件是二进制格式,<filename>
是转储文件的名称,<pid>
是目标Java进程的进程ID。
jmap -histo[:live] <pid>
这个命令用于统计堆中对象的数量,并按类名、对象数量和所占空间大小进行排序。加上:live
选项后,只统计活的对象。
jmap -finalizerinfo <pid>
这个命令用于显示那些在F-Queue队列中等待Finalizer线程执行finalizer方法的对象信息。
jmap -clstats <pid>
这个命令用于打印类加载器的统计信息,包括加载的类数量、总字节数等。
6.3 jstack
jstack
(Stack Trace for Java)是一个用于生成Java虚拟机(JVM)当前时刻的线程堆栈跟踪信息的命令行工具。这对于分析线程问题,如死锁、线程阻塞、线程挂起等非常有用。
jstack <pid>
这个命令用于生成指定Java进程ID(<pid>
)的线程堆栈跟踪信息,并将其打印到标准输出(通常是终端或控制台)。
jstack <pid> > stacktraces.txt
将jstack
的输出重定向到一个文件中,以便后续分析。在这个例子中,输出被重定向到名为stacktraces.txt
的文件中。
jstack -l <pid>
使用-l
选项,jstack
会输出关于锁的附加信息,这有助于识别死锁和锁争用情况
jstack -F <pid>
如果正常的jstack
请求不被响应(例如,进程挂起或崩溃),你可以使用-F
选项强制jstack
输出线程堆栈信息。这会将输出发送到标准错误(stderr)。
6.4 jstat
用于实时监控JVM的各种运行状态信息,包括类加载、垃圾回收、即时编译等。
jstat -class <pid>
这个命令用于显示关于类加载的信息,包括加载的类数量、所占用空间大小、未加载的数量等。
jstat -compiler <pid>
这个命令显示JVM实时编译的数量等信息。
jstat -gc <pid>
这个命令提供关于垃圾收集的统计信息,包括新生代、老年代、永久代的垃圾收集情况。
jstat -gcnew <pid>
这个命令显示年轻代对象的信息,包括Eden区、Survivor区的使用情况。
jstat -gcnewcapacity <pid>
这个命令显示年轻代对象的信息及其占用量。
jstat -gcold <pid>
这个命令显示老年代对象的信息。
jstat -gcoldcapacity <pid>
这个命令显示老年代对象的信息及其占用量。
jstat -gcpermcapacity <pid>
这个命令显示永久代对象的信息及其占用量。
jstat -printcompilation <pid>
这个命令显示当前VM正在执行的编译任务。
jstat -gc <pid> 1000 10
这个命令会每隔1000毫秒(1秒)查询一次垃圾收集统计,总共查询10次。
6.5 jinfo
用于实时查看和调整 Java 虚拟机的各项参数。它既可以显示 JVM 的系统属性,也可以显示命令行参数,甚至支持在运行时动态地更改部分参数。
jinfo [option] <pid>
其中,<pid>
是目标 Java 进程的进程 ID,而 option
可以是以下信息:
-
-flag <name>
: 打印指定 Java 虚拟机的参数值。 -
-flag [+|-]<name>
: 设置或取消指定 Java 虚拟机参数的布尔值。例如jinfo -flag +PrintGC 13297,启用了PrintGC,GC后,便会输出GC日志。 -
-flag <name>=<value>
: 设置指定 Java 虚拟机的参数的值。例如 jinfo -flag HeapDumpPath=/java/dump.hprof 15525 设置dump文件的路径
6.6 dump文件
-
配置转储dump文件
方式1:自动生成到指定位置java -Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof -jar your-application.jar
方式2:手动导出到指定位置
jmap -dump:format=b,file=D://heapdump.hprof 14696
14696 是进程号
使用工具分析dump文件
7.调优案例
7.1 需要调优吗?
一般来说,JVM调优不是常规手段,而是Java性能优化的最后选择。JVM经过不断的优化迭代,其默认参数通常能够满足绝大部分应用场景的需求。
然而,在某些特殊情况下,如资源非常紧张或对性能有极高要求的环境中,可能需要根据线上实际JVM各个区域的大小占比来进行适当的参数调整。例如,当Heap内存(老年代)持续上涨达到设置的最大内存值、Full GC次数频繁、GC停顿时间过长(超过1秒)、应用出现OutOfMemory等内存异常、应用中有使用本地缓存且占用大量内存空间、系统吞吐量与响应性能不高或下降等情况时,可以考虑进行JVM调优。
在进行JVM调优时,需要全面监控和详细分析性能数据,确保调整的参数能够真正提升系统性能。同时,也要注意不要过度优化,以免引入新的问题或增加系统的复杂性。
7.2 调优量化指标
JVM调优的量化指标主要包括以下几个方面:
内存占用量:过高的内存占用可能导致系统资源紧张,影响整体性能。通常,我们可以通过监控工具来观察JVM的内存使用情况,包括堆内存、栈内存和方法区内存的使用情况。在调优过程中,需要关注内存占用率是否过高,以及是否存在内存泄漏等问题。
延迟(或停顿时间):延迟是指由于垃圾收集等原因导致的程序暂停时间。对于需要快速响应的应用来说,延迟是一个关键指标。调优过程中需要关注垃圾收集的频率和持续时间,以及选择合适的垃圾收集器来降低延迟。
吞吐量:吞吐量是指单位时间内程序完成的任务数量。在JVM调优中,我们通常关注CPU在用户应用程序运行的时间与总运行时间的比例,即程序的执行效率。提高吞吐量可以通过优化代码、减少不必要的对象创建和销毁、使用合适的数据结构等方式实现。
Full GC次数:Full GC(全局垃圾收集)是JVM中一种较为耗时的垃圾收集方式,会暂停所有用户线程进行垃圾回收。因此,Full GC的次数也是评估JVM性能的重要指标之一。在调优过程中,需要关注Full GC的触发频率和原因,以及如何通过调整JVM参数或优化代码来减少Full GC的次数。
响应时间:对于交互式应用来说,响应时间是一个关键指标。它反映了系统对用户请求的响应速度。在JVM调优中,需要关注系统的平均响应时间和最大响应时间,以确保用户体验的顺畅性。
需要注意的是,以上指标并不是孤立的,它们之间相互影响、相互制约。在JVM调优过程中,需要综合考虑各个指标的表现,找到最佳的平衡点。同时,不同应用场景和优化目标可能需要关注不同的指标和调优方法。因此,在进行JVM调优时,需要明确优化目标并结合实际场景来选择合适的调优策略。
7.3 监控前准备工作
1.安装Jprofiler监控程序(可监控本地或远程)
2.应用程序添加如下参数
-Xloggc:/gc.log #GC日志记录文件
-XX:+PrintGCDetails # 打印GC日志
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/dump.hprof
- 3.安装GCView 分析GC日志用
- 4.优化前堆栈配置
使用jmap -heap pid 查看堆内存信息如下
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 268435456 (256.0MB)
NewSize = 89128960 (85.0MB)
MaxNewSize = 89128960 (85.0MB)
OldSize = 179306496 (171.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
7.4分析案例
7.4.1 OutOfMemoryError
常见的OutOfMemoryError分别有一下几类
- HeapOutOfMemoryError
这个错误表明 Java 堆内存不足。Java 堆是 JVM 用于存储对象实例的区域。当应用程序创建对象并且 JVM 无法在堆中分配足够的空间时,就会抛出此错误。 - GCOverheadLimitExceededError
这个错误发生在垃圾收集器花费太多时间尝试回收内存,但实际上回收到的内存很少。这通常意味着存在内存泄漏,或者应用程序需要更多的堆内存。 - Metaspace
在 Java 8 及之后的版本中,元数据区(Metaspace)取代了永久代。如果 Metaspace 空间不足,就会抛出此错误。 - DirectBufferMemory
这个错误与直接缓冲区相关,直接缓冲区是 Java NIO(New I/O)包中用于高效 I/O 操作的一种内存区域。如果 JVM 无法为直接缓冲区分配更多内存,就会抛出此错误。
分析与定位
生成一般容易产生OutOfMemoryError异常的场景有,递归调用,导入导出,报表查询等等,这些操作过程中容易将数据都放在内存中,导致OutOfMemoryError,其次是业务量起来后目前jvm配置参数不能支持当前业务并发量的时候也会产生OutOfMemoryError异常,所以,OutOfMemoryError分析与定位分下面两方面。
- 代码导致
OutOfMemoryError 一般会打印异常日志,日志中分析定位引发问题的代码进行优化 - 并发量导致
这种场景下,需要分析根据目前机器内存和并发量大小配置什么样比例的jvm内存才合适,参考下面文章估算思路
亿级流量电商系统JVM模型参数预估方案
7.4.2 响应延迟
从jvm的角度分析(默认Parallel 收集器),响应延迟一般都是因为在垃圾回收过程中标记过程需要stop-the-world,没办法处理客户端请求,而且如果发生full gc的话,年轻代,老年代,元空间都需要被回收,整个过程时间又被拉长,这就导致了响应延迟,所以,这方面优化主要是控制年轻代回收过程标记过程时间,和避免full gc的频繁发生
7.4.3 CPU飙升
排查步骤
1.通过top 命令查看占用cpu资源的进程
2.通过ps -mp 命令把这个pid下的线程占用cpu情况查出来
3.通过printf “%x\n” tid 命令把这个id转换成16进制的数字
4.通过jstack pid grep tid 找到引起异常的java堆栈信息代码