JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)

简介: 这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。

前言

  • JDK中的 垃圾回收器
    1. JDK8:PS+PO
    2. JDK9:G1。逻辑分代,物理不分代。从这里之前都是逻辑、物理都分代。
    3. JDK11:CMS就淘汰了,完成历史使命了。使用ZGC(Z Garbage Collector)垃圾回收器;逻辑、物理都不分代。
    4. JDK13:ZGC
    5. 说明,GC的调优越来越简单了;在有GC调优阶段,这是优势。
  • 学完这篇博客,可以在简历上写 有过JVM调优的经验 ,但是不能写精通哦了。

一、概述

OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上个博文的最后的案例:七.1)

  1. 硬件升级系统反而卡顿的问题(见上个博文的案例六.2
  2. 线程池不当运用产生OOM问题(见上个博文的案例七.1
    不断的往List里加对象(实在太LOW了)
  3. jira系统(提交测试报告用的应用系统)问题,jira跑在Tomcat中:通过设置-Xms1024m -Xmx9216m发现 系统在不停的GC,大约1秒一次。但是没有定位出来;想把堆文件dump出来,但是文件大约有10个G,dump文件可能会导致服务宕机,所以生产环境中一般不太好这样搞。然后添加 参数-XX:+HeapDumpOnOutOfMemoryError ,等什么时候溢出再看dump出的文件。但是项目有个习惯:就是宕机后立马重启。一秒一次GC,还不报错,项目很慢,但是可以响应。后来dump一次文件,大约10个G。但是没有查出来问题。
    实际系统不断重启
    解决问题:加内存 + 更换垃圾回收器 G1(JDK9才有)

二、案例二

tomcat 中 http-header-size过大问题

1、搭建实验环境

a、代码

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/oom")
    public String testName(){
        return "Hello world";
    }
}

b、发请求

http://localhost:9898/test/oom
在这里插入图片描述

c、Jmeter启动

2、设置参数

  1. server.port:设置项目默认端口
  2. server.max-http-header-size:设置 HTTP 消息头的最大大小。
    在这里插入图片描述
    在这里插入图片描述

3、使用jmeter模拟并发测试(模拟并发100)

  • 使用jmeter模拟并发测试(模拟并发100),jmeter 是压力测试用的
  • 这里模拟默认了100个线程去申请http的请求,这http请求会调用程序,这个程序,由于Tomcat 设置了 10000000大小 ,http每申请一个内存就增加了这么大的空间使用。很快就容易OOM,内存溢出。
    在这里插入图片描述

4、tomcat源码:

Http11Processor类,处理请求。
在这里插入图片描述
Http11InputBuffer类为请求创建buffer
在这里插入图片描述
发现每个请求都创建headerBufferSize+8192K
MAT截图:
在这里插入图片描述
结论:
当请求过多,设置堆内存太小,会导致oom问题。
发现同事无意设置过大。

三、案例:方法区内存溢出

  • lambda表达式导致方法区溢出问题(MethodArea / Perm Metaspace)
  • 不是那么真实,面试时尽量不要说这个, 只是想表达方法区也会内存溢出(OutOfMemoryError)
  • lambda表达式导致方法区溢出问题(MethodArea / Perm Metaspace)

1、代码:LambdaGC.java

启动参数:-XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails

package com.mashibing.jvm.c5_gc;

public class LambdaGC {
    public static void main(String[] args) {
        for(;;) {
            I i = C::n;
        }
    }
    public static interface I {
        void m();
    }

    public static class C {
        static void n() {
            System.out.println("hello");
        }
    }
}

2、元空间内存溢出日志

"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -XX:MaxMetaspaceSize=9M -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\lib\idea_rt.jar=49316:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\work\ijprojects\JVM\out\production\JVM;C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar" com.mashibing.jvm.gc.LambdaGC
[GC (Metadata GC Threshold) [PSYoungGen: 11341K->1880K(38400K)] 11341K->1888K(125952K), 0.0022190 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 1880K->0K(38400K)] [ParOldGen: 8K->1777K(35328K)] 1888K->1777K(73728K), [Metaspace: 8164K->8164K(1056768K)], 0.0100681 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] 1777K->1777K(73728K), 0.0005698 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(38400K)] [ParOldGen: 1777K->1629K(67584K)] 1777K->1629K(105984K), [Metaspace: 8164K->8156K(1056768K)], 0.0124299 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:388)
    at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:411)
Caused by: java.lang.OutOfMemoryError: Compressed class space
    at sun.misc.Unsafe.defineClass(Native Method)
    at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
    at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393)
    at sun.reflect.MethodAccessorGenerator.generateSerializationConstructor(MethodAccessorGenerator.java:112)
    at sun.reflect.ReflectionFactory.generateConstructor(ReflectionFactory.java:398)
    at sun.reflect.ReflectionFactory.newConstructorForSerialization(ReflectionFactory.java:360)
    at java.io.ObjectStreamClass.getSerializableConstructor(ObjectStreamClass.java:1574)
    at java.io.ObjectStreamClass.access$1500(ObjectStreamClass.java:79)
    at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:519)
    at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:494)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:494)
    at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:391)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1134)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at javax.management.remote.rmi.RMIConnectorServer.encodeJRMPStub(RMIConnectorServer.java:727)
    at javax.management.remote.rmi.RMIConnectorServer.encodeStub(RMIConnectorServer.java:719)
    at javax.management.remote.rmi.RMIConnectorServer.encodeStubInAddress(RMIConnectorServer.java:690)
    at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java:439)
    at sun.management.jmxremote.ConnectorBootstrap.startLocalConnectorServer(ConnectorBootstrap.java:550)
    at sun.management.Agent.startLocalManagementAgent(Agent.java:137)

3、分析

  • Compressed class space: Compressed class 是对象头的压缩指针;在方法区中,有一块内存专门是给压缩后的class用的,一旦占满之后,就会内存溢出。这里通过space 可以看出 是方法区的内存溢出。
  • 通过案例可以知道 元空间也是可以产生内存溢出的

4、疑问

  • 为什么lambda表达式导致方法区溢出问题,大多数不应该在堆中吗?而且这里为什么不会在栈中的局部变量中呢?而会在方法区呢?

四、案例:直接内存溢出问题(少见)(尽量不说)

  • 《深入理解Java虚拟机》P59,除非使用Unsafe分配直接内存,或者使用NIO的问题会导致直接内存(Direct Memory)溢出问题

  • 很少见的,一般不会搞出这个情况。

五、案例:栈内存溢出问题

1、栈溢出原因

2、案例(递归)

a、代码

package com.mashibing.jvm;

public class StackOverFlow {
    public static void main(String[] args) {
        m();
    }

    static void m() {
        m();
    }
}

b、结果

在这里插入图片描述

c、原因

栈中是每个的栈针,这里有方法的递归,所以栈的深度越来越大,导致栈的内存溢出。

d、解决方法

  • 设置栈的大小:-Xss

代码分析

1、代码

  • 代码1
Object o = null;
for(int i=0; i<100; i++) {
    o = new Object();
    //业务处理
}
  • 代码2
for(int i=0; i<100; i++) {
    Object o = new Object();
}

2、分析

  • 肯定是第一个代码会好一些。
  • 原因:
    • 第一个代码栈上只创建一个变量,通过循环会创建一个个的实例对象,然后变量指向实例对象。当变量指向第二个实例对象时,上一个实例对象就没有引用了,就会被垃圾回收掉。
    • 第二个代码每次都会产生一个对象,且有很大可能很多对象不会被回收掉。方法执行完,会被释放掉。
  • 综上所述:第一个代码好一些。

七、重写finalize引发频繁GC

1、概述

  • 小米云,HBase同步系统,系统通过nginx访问超时报警,最后排查,C++程序员重写finalize引发频繁GC问题
  • 排除情况:
    1. nginx访问超时报警
    2. 发现CPU飙高
    3. 发现频繁GC
    4. 再发现对象过多
    5. 最后发现:C++程序员重写finalize引发频繁GC问题

2、问题

  • 问题1:为什么C++程序员会重写finalize?
  • 问题2:为什么重写finalize会导致频繁GC。

3、答案

  • 因为C++程序员是手动回收内存的,先new 再delete。

    • C++的构造函数和java 一样,也需要 new,但是C++需要手动回收内存,但是手动回收内存是析构函数来解析完成的, C++ 调用delete语句的时候会默认调用 析构函数,调用new 语句的时候默认会调用构造函数。所以 delete语句在C++里是 手动回收内存要使用的语句。C++程序员会理所当然的认为java里面是不是也有一个类似于析构函数这样的东西,结果发现有个finalize,结果重写了。
  • 为什么造成频繁GC呢?

    • 是因为重新的finalize,耗时比较长。有可能会达到200ms(不确定)。这里有一些耗时长的操作,如果好多对象同时产生了,但是收到时会调用finalize,每个都要调用,所以回收不过来了,就会频繁GC。

4、案例

  • 如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的?

  • 有人手动调用了 System.gc() (这个比较Low)。

  • -XX:+DisableExplictGC,弃用System.gc()不管用 ,FGC

八、new 大量线程导致OOM(太LOW)

  • new 大量线程,会产生 native thread OOM.
  • 解决方案:
    • ==(太low)应该用线程池。 ==
    • 太LOW了,减少堆空间(太TMlow了),预留更多内存产生native thread
  • 经验扩展:JVM内存占物理内存 比例 50% - 80%
相关文章
|
1月前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
1月前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
2月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
2月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
61 4
|
2月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
93 2

相关实验场景

更多