JVM相关 - StackOverflowError 与 OutOfMemoryError(上)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JVM相关 - StackOverflowError 与 OutOfMemoryError(上)
本文基于 Java 15

StackOverflowError 与 OutOfMemoryError 是两个老生常谈的 Java 错误。Java 中的虚拟机错误 VirtualMachineError 包括以下四种:


微信图片_20220624194801.jpg


我们比较关心的就是 StackOverflowError 与 OutOfMemoryError,剩下的 InternalError 一般是内部使用错误,UnknownError 是虚拟机发生未知异常,这两种我们这里不讨论。


虚拟机规范中的 StackOverflowError 与 OutOfMemoryError


参考 Java 虚拟机规范官方文档:Run-Time Data Areas,可以知道,在如下情况下,会抛出这两种错误:

  • 当某次线程运行计算时,需要占用的 Java 虚拟机栈(Java Virtual Machine Stack)大小,也就是 Java 线程栈大小,超过规定大小时,抛出 StackOverflowError
  • 如果 Java 虚拟机栈大小可以动态扩容,发生扩容时发现内存不足,或者新建Java 虚拟机栈时发现内存不足,抛出 OutOfMemoryError
  • 当所需要的堆(heap)内存大小不足时,抛出 OutOfMemoryError
  • 当方法区(Method Area)大小不够分配时,抛出 OutOfMemoryError
  • 当创建一个类或者接口时,运行时常量区剩余大小不够时,抛出 OutOfMemoryError
  • 本地方法栈(Native Method Stack)大小不足时,抛出 StackOverflowError
  • 本地方法栈(Native Method Stack)扩容时发现内存不足,或者新建本地方法栈发现内存不足,抛出 OutOfMemoryError


Hotspot JVM 的实现


为了进一步搞清楚 StackOverflowError 与 OutOfMemoryError,我们来看具体实现。一般的 JVM 采用的都是官网的 HotSpot JVM,我们这里就用 Hotspot JVM 的实现来说明。


JVM 内存包括什么


我们一般通过两个工具 pmap 还有 jcmd 中的 VM.native_memory 命令去查看 Java 进程内存占用,由于 pmap 命令有点复杂而且很多内存映射是 anon 的,这里采用 jcmd 中的 VM.native_memory 命令,去看一下 JVM 内存的每一部分。需要指出的一点是,

如果想了解详细的 Native Memory Tracking,请参考我的另一篇文章JVM相关 - JVM 内存占用与分析

Native Memory Tracking:
Total: reserved=6308603KB, committed=4822083KB
-                 Java Heap (reserved=4194304KB, committed=4194304KB)
                            (mmap: reserved=4194304KB, committed=4194304KB) 
-                     Class (reserved=1161041KB, committed=126673KB)
                            (classes #21662)
                            (  instance classes #20542, array classes #1120)
                            (malloc=3921KB #64030) 
                            (mmap: reserved=1157120KB, committed=122752KB) 
                            (  Metadata:   )
                            (    reserved=108544KB, committed=107520KB)
                            (    used=105411KB)
                            (    free=2109KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=1048576KB, committed=15232KB)
                            (    used=13918KB)
                            (    free=1314KB)
                            (    waste=0KB =0.00%)
-                    Thread (reserved=355251KB, committed=86023KB)
                            (thread #673)
                            (stack: reserved=353372KB, committed=84144KB)
                            (malloc=1090KB #4039) 
                            (arena=789KB #1344)
-                      Code (reserved=252395KB, committed=69471KB)
                            (malloc=4707KB #17917) 
                            (mmap: reserved=247688KB, committed=64764KB) 
-                        GC (reserved=199635KB, committed=199635KB)
                            (malloc=11079KB #29639) 
                            (mmap: reserved=188556KB, committed=188556KB) 
-                  Compiler (reserved=2605KB, committed=2605KB)
                            (malloc=2474KB #2357) 
                            (arena=131KB #5)
-                  Internal (reserved=3643KB, committed=3643KB)
                            (malloc=3611KB #8683) 
                            (mmap: reserved=32KB, committed=32KB) 
-                     Other (reserved=67891KB, committed=67891KB)
                            (malloc=67891KB #2859) 
-                    Symbol (reserved=26220KB, committed=26220KB)
                            (malloc=22664KB #292684) 
                            (arena=3556KB #1)
-    Native Memory Tracking (reserved=7616KB, committed=7616KB)
                            (malloc=585KB #8238) 
                            (tracking overhead=7031KB)
-               Arena Chunk (reserved=10911KB, committed=10911KB)
                            (malloc=10911KB) 
-                   Tracing (reserved=25937KB, committed=25937KB)
                            (malloc=25937KB #8666) 
-                   Logging (reserved=5KB, committed=5KB)
                            (malloc=5KB #196) 
-                 Arguments (reserved=18KB, committed=18KB)
                            (malloc=18KB #486) 
-                    Module (reserved=532KB, committed=532KB)
                            (malloc=532KB #3579) 
-              Synchronizer (reserved=591KB, committed=591KB)
                            (malloc=591KB #4777) 
-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB)


这里的 mmapmalloc 是两种不同的内存申请分配方式,例如:

Internal (reserved=3643KB, committed=3643KB)
                            (malloc=3611KB #8683) 
                            (mmap: reserved=32KB, committed=32KB)

代表 Internal 一共占用 3643KB,其中3611KB是通过 malloc 方式,32KB 是通过 mmap 方式。 arena 是通过 malloc 方式分配的内存但是代码执行完并不释放,放入 arena chunk 中之后还会继续使用,参考:MallocInternals

可以看出,Java 进程内存包括:

  • Java Heap: 堆内存,即-Xmx限制的最大堆大小的内存。
  • Class:加载的类与方法信息,其实就是 metaspace,包含两部分: 一是 metadata,被-XX:MaxMetaspaceSize限制最大大小,另外是 class space,被-XX:CompressedClassSpaceSize限制最大大小
  • Thread:线程与线程栈占用内存,每个线程栈占用大小受-Xss限制,但是总大小没有限制。
  • Code:JIT 即时编译后(C1 C2 编译器优化)的代码占用内存,受-XX:ReservedCodeCacheSize限制
  • GC:垃圾回收占用内存,例如垃圾回收需要的 CardTable,标记数,区域划分记录,还有标记 GC Root 等等,都需要内存。这个不受限制,一般不会很大的。
  • Compiler:C1 C2 编译器本身的代码和标记占用的内存,这个不受限制,一般不会很大的
  • Internal:命令行解析,JVMTI 使用的内存,这个不受限制,一般不会很大的
  • Symbol: 常量池占用的大小,字符串常量池受-XX:StringTableSize个数限制,总内存大小不受限制
  • Native Memory Tracking:内存采集本身占用的内存大小,如果没有打开采集(那就看不到这个了,哈哈),就不会占用,这个不受限制,一般不会很大的
  • Arena Chunk:所有通过 arena 方式分配的内存,这个不受限制,一般不会很大的
  • Tracing:所有采集占用的内存,如果开启了 JFR 则主要是 JFR 占用的内存。这个不受限制,一般不会很大的
  • Logging,Arguments,Module,Synchronizer,Safepoint,Other,这些一般我们不会关心。

除了 Native Memory Tracking 记录的内存使用,还有两种内存 Native Memory Tracking 没有记录,那就是:


各种 StackOverflowError 与 OutOfMemoryError 场景以及定位方式


1. StackOverflowError

调用栈过深,导致线程栈占用大小超过-Xss(或者是-XX:ThreadStackSize)的限制,如果没指定-Xss,则根据不同系统确定默认最大大小。

确定默认大小的代码请参考:

总结起来就是,32 位的系统一般是 512k,64 位的是 1024k

一般报这个错都是因为递归死循环,或者调用栈真的太深而线程栈大小不足,比如那种回调背压模型的框架,netty + reactor 这种,一般线程栈需要调大一点。


2. OutOfMemoryError: Java heap space

堆内存不够用,无法分配更多内存,就会抛出这个异常。一般这种情况发生后,需要查看 heap dump,线上应用一般加上-XX: +HeapDumpOnOutOfMemoryErrorOutOfMemoryError发生的时候,进行 heap dump,之后进行分析。

heap dump 查看工具一般通过 Memory Analyzer (MAT)


image.png

相关文章
|
存储 Java
【面试题精讲】JVM-运行时数据区-StackOverFlowError
【面试题精讲】JVM-运行时数据区-StackOverFlowError
|
缓存 Java 大数据
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
190 0
|
存储 Java C++
【JVM】StackOverflowError与OutOfMemoryError
【JVM】StackOverflowError与OutOfMemoryError
109 0
【JVM】StackOverflowError与OutOfMemoryError
|
存储 Java 数据库连接
JVM之常见内存溢出(OutOfMemoryError)异常 ✨ 每日积累
JVM之常见内存溢出(OutOfMemoryError)异常 ✨ 每日积累
JVM之常见内存溢出(OutOfMemoryError)异常 ✨ 每日积累
|
Java
编写一个程序,它能导致JVM抛出一个OutOfMemoryError,然后捕获和处理这个异常
编写一个程序,它能导致JVM抛出一个OutOfMemoryError,然后捕获和处理这个异常
202 0
|
监控 Java Linux
JVM相关 - StackOverflowError 与 OutOfMemoryError(下)
JVM相关 - StackOverflowError 与 OutOfMemoryError(下)
JVM相关 - StackOverflowError 与 OutOfMemoryError(下)
|
存储 Java API
JVM 实战 OutOfMemoryError 异常
JVM 实战 OutOfMemoryError 异常
JVM 实战 OutOfMemoryError 异常
|
缓存 监控 Java
JVM源码分析之临门一脚的OutOfMemoryError完全解读
JVM源码分析之临门一脚的OutOfMemoryError完全解读
|
存储 Java API
JVM 实战 OutOfMemoryError 异常(下)
在《Java虚拟机规范》的规定里,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(下文称OOM)异常的可能。(本文主要是基于 jdk1.8 展开探讨)
154 0
|
16天前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
29 4