每日一面 - Java OOM都有哪些,说出几种?(上)

简介: 每日一面 - Java OOM都有哪些,说出几种?(上)

Key TakeAways


  1. StackOverflowError: 调用栈过深,导致线程栈占用大小超过-Xss(或者是-XX:ThreadStackSize)的限制
  2. OutOfMemoryError: Java heap space:堆内存不够用,无法分配更多内存,就会抛出这个异常。
  3. OutOfMemoryError: unable to create native thread:这个在创建太多的线程,超过系统配置的极限。如Linux默认允许单个进程可以创建的线程数是1024个。
  4. OutOfMemoryError: GC Overhead limit exceeded:默认情况下,并不是等堆内存耗尽,才会报 OutOfMemoryError,而是如果 JVM 觉得 GC 效率不高,也会报这个错误。
  5. OutOfMemoryError: direct memory:这个是向系统申请直接内存时,如果系统可用内存不足,就会抛出这个异常
  6. OutOfMemoryError: map failed:这个是 File MMAP(文件映射内存)时,如果系统内存不足,就会抛出这个异常
  7. OutOfMemoryError: Requested array size exceeds VM limit:当申请的数组大小超过堆内存限制,就会抛出这个异常。
  8. OutOfMemoryError: Metaspace:Metadata 占用空间超限
  9. OutOfMemoryError: Compressed class space:class space 内存溢出导致的
  10. OutOfMemoryError: reason stack_trace_with_native_method:这个发生在 JNI 调用中,内存不足
本文基于 Java 15

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


微信图片_20220624201038.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 没有记录,那就是:

相关文章
|
7月前
|
SQL 存储 数据可视化
使用Java分析器优化代码性能,解决OOM问题
使用Java分析器优化代码性能,解决OOM问题
|
18天前
|
监控 网络协议 NoSQL
java线上排查OOM内存溢出
java线上排查OOM内存溢出
17 0
|
2月前
|
缓存 监控 架构师
阿里面试:Java开发中,应如何避免OOM
在Java开发中,OutOfMemoryError(OOM)错误一直是令开发者头疼的问题,也是Java面试中出现核心频率很高的问题。 那么我们究竟怎么样才能够有效正确的管理内存,日常开发中究竟要注意哪些核心技巧来避免OOM错误。 本文将带大家一起学习10个避免OOM的实用小技巧,让大家在工作中能够有的放矢,避免OOM错误的飞来横祸。
47 1
|
3月前
|
监控 Java Linux
java配置OOM时保存堆转储文件
java配置OOM时保存堆转储文件
50 0
|
3月前
|
缓存 架构师 算法
Java内存溢出如何解决,Java oom排查方法,解决办法
在Java开发过程中,有效的内存管理是保证应用程序稳定性和性能的关键。不正确的内存使用可能导致内存泄露甚至是致命的OutOfMemoryError(OOM)。
|
3月前
|
缓存 架构师 算法
Java内存溢出如何解决,Java oom排查方法,10个定位解决办法
在Java开发过程中,有效的内存管理是保证应用程序稳定性和性能的关键。不正确的内存使用可能导致内存泄露甚至是致命的OutOfMemoryError(OOM)。
|
4月前
|
Java
[Java 源码] 美团一面~ArrayList 的底层实现
[Java 源码] 美团一面~ArrayList 的底层实现
|
4月前
|
存储 安全 Java
[Java] 阿里一面~说一下ArrayList 与 LinkedList 区别
[Java] 阿里一面~说一下ArrayList 与 LinkedList 区别
123 1
|
4月前
|
存储 Java
[JVM] 京东一面~说一下Java 类加载过程
[JVM] 京东一面~说一下Java 类加载过程
|
5月前
|
Dubbo Java 应用服务中间件
阿里一面:说一说Java、Spring、Dubbo三者SPI机制的原理和区别
大家好,我是三友~~ 今天来跟大家聊一聊Java、Spring、Dubbo三者SPI机制的原理和区别。 其实我之前写过一篇类似的文章,但是这篇文章主要是剖析dubbo的SPI机制的源码,中间只是简单地介绍了一下Java、Spring的SPI机制,并没有进行深入,所以本篇就来深入聊一聊这三者的原理和区别。