前言

视频教程:https://www.bilibili.com/video/BV1Dz4y1A7FB?p=2&vd_source=85ac5ee1b07df12a44b648a8751d30f6

相关知识点

案例篇章-OOM

堆溢出

报错信息

java.lang.OutOfMemoryError: Java heap space

JVAM参数配置

-XX:+PrintGCDetails -XX:MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdump.hprof -XX:+PrintGCDateStamps -Xms200M -Xmx200M -Xloggc:log/gc-oomHeap.log

原因及解决方案

  • 原因
    • 代码中可能存在大对象分配
    • 可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对
  • 解决方法
    • 检查是否存在大对象的分配,最有可能的是大数组分配
    • 通过jmap命令,把堆内存dump下来,使用MAT等工具分析一下,检查是否存在内存泄漏的问题
    • 如果没有找到明显的内存泄漏,使用 -Xmx 加大堆内存
    • 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

元空间溢出

报错信息

java.lang.OutOfMemoryError: Metaspace

参数配置

-XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m -Xss512K -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdumpMeta.hprof -XX:SurvivorRatio=8 -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps -Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log

原因及解决方案

JDK8后,元空间替换了永久代,元空间使用的是本地内存

  • 原因

    • 运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
    1. 应用长时间运行,没有重启
    2. 元空间内存设置过小
  • 解决方案

    • 检查是否永久代空间或者元空间设置的过小
    1. 检查代码中是否存在大量的反射操作
    2. dump之后通过mat检查是否存在大量由于反射生成的代理类
    3. 如果加载同一个代理类即可,可以创建对象以后调用对象的setUseCache(true)方法,

    选择为true的话,使用和更新一类具有相同属性生成的类的静态缓存,而不会在同一个类文件还继续被动态加载并视为不同的类,这个其实跟类的equals()和hashCode()有关,它们是与cglib内部的class cache的key相关的。

GC overhead limit exceeded

报错信息

GC overhead limit exceeded

原因及解决方案

  • 原因
    • 超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常,本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出。
  • 解决方案
    • 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码
    • 添加参数 -XX:-UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space
    • dump内存,检查是否存在内存泄漏,如果没有,加大内存

线程溢出

报错信息

java.lang.OutOfMemoryError : unable to create new native Thread

原因及解决方案

  • 原因

    • 出现这种异常,基本上都是创建了大量的线程导致的

    • 正常情况下,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右

    • 能创建的线程数的具体计算公式如下:
      (MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads

      MaxProcessMemory 指的是进程可寻址的最大空间
      JVMMemory JVM内存
      ReservedOsMemory 保留的操作系统内存
      ThreadStackSize 线程栈的大小

    • 在Java语言里, 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory)。

    • 由公式得出结论:你给JVM内存越多,那么你能创建的线程越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread

  • 解决方案

    • 如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。

    • 如果程序确实需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数。

      • MaxProcessMemory 使用64位操作系统
      • JVMMemory 减少JVMMemory的分配
      • ThreadStackSize 减小单个线程的栈大小

      经实测,在32位windows系统下较为严格遵守;64位系统下只能保证正/负相关性,甚至说相关性也不能保证。即:
      在测试的过程中,64位操作系统下调整Xss的大小并没有对产生线程的总数产生影响,程序执行到极限的时候,操作系统会死机。无法看出效果

    • 线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:

      • /proc/sys/kernel/pid_max 系统最大pid值,在大型系统里可适当调大

      • /proc/sys/kernel/threads-max 系统允许的最大线程数

      • maxuserprocess(ulimit -u) 系统限制某用户下最多可以运行多少进程或线程

      • /proc/sys/vm/max_map_count

        max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量。虚拟内存区域是一个连续的虚拟地址空间区域。在进程的生命周期中,每当程序尝试在内存中映射文件,链接到共享内存段,或者分配堆空间的时候,这些区域将被创建。调优这个值将限制进程可拥有VMA的数量。限制一个进程拥有VMA的总数可能导致应用程序出错,因为当进程达到了VMA上线但又只能释放少量的内存给其他的内核进程使用时,操作系统会抛出内存不足的错误。如果你的操作系统在NORMAL区域仅占用少量的内存,那么调低这个值可以帮助释放内存给内核用。

面试题

说到内存泄漏,问有没有碰到,内存泄漏怎么解决?(拼多多)
内存泄漏是怎么造成的?(拼多多、字节跳动)
如何理解内存泄漏问题?有哪些情况会导致内存泄露?如何解决? (阿里)

1