• 【OOM-笔记】


    什么是OOM

    OOM,全称“Out Of Memory”,意思就是“内存用完了”,

    详细说明
    当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)

    继承树结构:

    Object
    	Throwable
    		Error
    			VirtualMachineError
    				OutOfMemoryError
    
    • 1
    • 2
    • 3
    • 4
    • 5

    内存泄露 和 内存溢出

    • 内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
    • 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
    • 偶尔的内存泄露可能不会造成问题,而大量的内存泄露可能会导致内存溢出。

    为什么会OOM?

    • 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
    • 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
    • 在Java语言中,由于存在了垃圾自动回收机制,所以,我们一般不用去主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄露”的。但是,如果编码不当,比如,将某个对象的引用放到了全局的Map中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收。如果该种情况出现次数多了,就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内
      存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。

    堆内存不够与OutOfMemoryError异常的关系

    线程发生OutOfMemoryError,首先是堆空间不够了,然后再由jvm在申请分配空间的的时候,在调用上抛出OOM异常。
    申请内存的线程,会会像处理普通的其他异常一样,处理OutOfMemoryError。
    线程是资源调度的基本单位,Java在设计线程时充分考虑了线程的独立性。
    在异常方面,线程也保持了线程异常的独立性。
    在线程执行中,如果发生的异常,都由线程进行独立的处理,而不是也不会抛出到其它的线程。
    这就是保证了这种线程的独立性。从线程的实现维度,也可以看到异常处理的策略。

    线程Thread里边,最终会执行内部target对象的run方法,也就是java.lang.Runnable接口实现方法,线程通过其run方法运行,方法签名如下:

    public abstract void run();
    
    • 1

    注意这个方法,run方法不能声明抛出任何检查异常(checked exception)。因此在线程方法执行中发生的任何检查异常,必须在线程中处理。
    线程拿到异常,有两种处理方式:

    • 捕获并且处理异常,线程继续执行
    • 线程停止执行

    默认异常处理器

    如果没有被捕获,除了检查异常,java中还有非检查异常(unchecked exception),这种异常无需显式声明也能沿着方法调用链向上抛出。
    线程对于这种未处理的异常,提供了默认异常处理器

    /**
    * Dispatch an uncaught exception to the handler. This method is
    * intended to be called only by the JVM. (将未被捕获的异常分发给处理器。这个方法只被JVM调用)
    */
    private void dispatchUncaughtException(Throwable e) {
       getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Thread的init()方法线程至少有一个默认异常处理器,兜底的异常处理器是当前线程父线程的线程组ThreadGroup,可以看到线程组是有能力处理异常的:

    public  class ThreadGroup implements Thread.UncaughtExceptionHandler {}
    
    • 1

    线程通过这两种机制,保证内部发生的异常,在线程内解决,而不会抛出给启动线程的外部线程。

    JVM退出条件

    java虚拟机退出的条件是:JVM 不存在非守护线程(前台线程),JVM就会退出。

    线程发生未处理的异常(未处理异常由默认异常处理器处理)会导致线程结束,而线程结束了, 如果还有非守护线程(前台线程),JVM也不会退出。

    OOM也是一种异常,它的发生也不会导致JVM退出。

    所以,OOM 与JVM的退出,没有很强的关系。

    什么时候发生OOM、JVM才退出呢?

    场景1:所有的非守护线程由于申请不到内存而OOM,所有非守护线程退出,JVM退出,这个属于主动退出
    OOM的发生表示了此刻JVM堆内存告罄,不能分配出更多的资源,或者GC回收效率不可观。

    一个线程的OOM,在一定程度的并发下,若此时其他线程(含非守护线程)也需要申请堆内存,那么其他线程也会因为申请不到内存而OOM,甚至连锁反应导致整个JVM的退出。

    场景2:OOM溢出,说明内存耗尽,如果操作系统内存耗尽,就会发生OOM killer(Out Of Memory killer),干掉JVM进程,导致被动退出

    Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory()被触发,然后调用select_bad_process()选择一个”bad”进程杀掉。如何判断和选择一个”bad进程呢?linux选择”bad”进程是通过调用oom_badness(),挑选的算法和想法都很简单很朴实:最bad的那个进程就是那个最占用内存的进程。
    
    • 1

    参考:https://www.kernel.org/doc/gorman/html/understand/understand016.html

    出现OOM原因及解决办法

    堆溢出

    这种场景最为常见,报错信息:

    java.lang.OutOfMemoryError: Java heap space
    
    • 1

    原因

    1、代码中可能存在大对象分配 2、可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

    解决方法

    1、检查是否存在大对象的分配,最有可能的是大数组分配

    2、通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题

    3、如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存

    4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

    永久代/元空间溢出

    报错信息:

    java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace
    
    • 1

    原因

    永久代是 HotSot 虚拟机对方法区的具体实现,存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。JDK8后,元空间替换了永久代,元空间使用的是本地内存,还有其它细节变化:

    • 字符串常量由永久代转移到堆中
    • 永久代相关的JVM参数已移除

    解决方法

    因为该OOM原因比较简单,解决方法有如下几种:

    1、检查是否永久代空间或者元空间设置的过小

    2、检查代码中是否存在大量的反射操作

    3、dump之后通过mat检查是否存在大量由于反射生成的代理类 4、放大招,重启JVM

    方法栈溢出
    报错信息:

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

    原因
    出现这种异常,基本上都是创建的了大量的线程导致的,以前碰到过一次,通过jstack出来一共8000多个线程。

    解决方法
    1、通过 -Xss 降低的每个线程栈大小的容量
    2、线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:

    /proc/sys/kernel/pid_max
    /proc/sys/kernel/thread-max
    maxuserprocess(ulimit -u)
    /proc/sys/vm/maxmapcount
    
    • 1
    • 2
    • 3
    • 4

    OOM分析–heapdump

    要dump堆的内存镜像,可以采用如下两种方式:

    1. 设置JVM参数
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={dump-path}
    
    • 1
    1. 使用JDK自带的jmap命令
    "jmap -dump:format=b,file=heap.bin "
    
    • 1

    dump堆内存信息后,需要对dump出的文件进行分析,从而找到OOM的原因。常用的工具有:

    • mat: eclipse memory analyzer, 基于eclipse RCP的内存分析工具。详细信息参见:http://www.eclipse.org/mat/,推荐使用。
    • jhat:JDK自带的java heap analyze tool,不推荐使用

    注意:因为JVM规范没有对dump出的文件的格式进行定义,所以不同的虚拟机产生的dump文件并不是一样的。在分析时,需要针对不同的虚拟机的输出采用不同的分析工具(当然,有的工具可以兼容多个虚拟机的格式)。IBM HeapAnalyzer也是分析heap的一个常用的工具。

    jvm参考:
    https://docs.oracle.com/en/java/javase/17/vm/index.html#Java-Platform%2C-Standard-Edition

  • 相关阅读:
    jvm YGC和FGC发生的具体场景
    vue、uniapp实现组件动态切换
    [附源码]SSM计算机毕业设计小说网站的设计与实现1JAVA
    【Try Hack Me】Buffer Overflow-3
    Unity之NetCode多人网络游戏联机对战教程(4)--连接申请ConnectionApproval
    数据采集时使用HTTP代理IP效率不高怎么办?
    html 按钮点击倒计时,限制不可点击
    举个栗子~ Minitab 技巧(1):快速安装和激活 Minitab 统计软件
    Python问题1:ModuleNotFoundError: No module named ‘numpy‘
    【数据存储:小端模式和大端模式】
  • 原文地址:https://blog.csdn.net/guo20082200/article/details/133908091