JAVA OOM异常可观测最佳实践

这篇具有很好参考价值的文章主要介绍了JAVA OOM异常可观测最佳实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们常见 OOM 异常场景

  1. 堆溢出-java.lang.OutOfMemoryError: Java heap space。
  2. 栈溢出-java.lang.OutOfMemorryError。
  3. 栈溢出-java.lang.StackOverFlowError。
  4. 元信息溢出-java.lang.OutOfMemoryError: Metaspace。
  5. 直接内存溢出-java.lang.OutOfMemoryError: Direct buffer memory。
  6. GC超限-java.lang.OutOfMemoryError: GC overhead limit exceeded。

垃圾回收器

垃圾回收器就是内存回收的实践者,不同的产商、不同版本的虚拟机所包含的垃圾收集器都可能会有很大的差别,不同的虚拟机一般也都会提供各种参数供用户根据自己的应用特点和要求组合出各个内存分代所使用的收集器 ——《深入理解JAVA虚拟机》

关于垃圾采集器(也叫垃圾回收器),在《深入理解JAVA虚拟机》第三版目录中,已经为我们罗列了大部分垃圾采集器。如下图所示:

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

查看本地 JVM 垃圾回收器

通过命令 java -XX:+PrintFlagsFinal -version |FINDSTR /i ":"查看本地垃圾回收器为 Parallel

C:\Users\lenovo>java -XX:+PrintFlagsFinal -version |FINDSTR /i ":"
     intx CICompilerCount                          := 4                                   {product}
    uintx InitialHeapSize                          := 266338304                           {product}
    uintx MaxHeapSize                              := 4257218560                          {product}
    uintx MaxNewSize                               := 1418723328                          {product}
    uintx MinHeapDeltaBytes                        := 524288                              {product}
    uintx NewSize                                  := 88604672                            {product}
    uintx OldSize                                  := 177733632                           {product}
     bool PrintFlagsFinal                          := true                                {product}
     bool UseCompressedClassPointers               := true                                {lp64_product}
     bool UseCompressedOops                        := true                                {lp64_product}
     bool UseLargePagesIndividualAllocation        := false                               {pd product}
     bool UseParallelGC                            := true                                {product}
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

查看 K8s 环境 JVM 垃圾回收器

在 K8s 环境中,一般使用 openjdk:8-jdk-alpine或者openjdk:8u292作为基础镜像,启动服务后,并没有发现开启垃圾回收器。

root@ruoyi-system-c9c54dbd5-ltcvf:/data/app# 
root@ruoyi-system-c9c54dbd5-ltcvf:/data/app# java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=8388608 -XX:MaxHeapSize=134217728 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)
root@ruoyi-system-c9c54dbd5-ltcvf:/data/app# 

使用观测云进行JAVA OOM异常可观测

“观测云”是一款面向开发、运维、测试及业务团队的实时数据监测平台,能够统 一满足云、云原生、应用及业务上的监测需求,快速实现基础设施、中间件、应用层和业务层的可观测。基础设施监测、日志与指标管理、应用性能监测、用户 访问监测、可用性监测、异常检测、系统级安全巡检、场景和仪表板等是“观测云”的可观测解决方案,通过统一的数据采集、全面的数据监控、无缝的关联分析、自定义的场景搭建、高度的可编程性,敏捷的成员协作,为用户提供了最快、最轻松、最全面、最自由的系统可观测平台。在观测云官网注册一个账号,就可以在观测云免费进行JAVA OOM异常可观测。

前置条件

1、jdk 版本为 1.8 ,也称为 JDK8。

每个 jdk 垃圾回收机制均不太一样,同样内存结构也发生了很大的变化,尤其是 1.6、1.7、1.8 三个版本表现出比较明显,目前大部分企业用的是 jdk1.8 版本,本最佳实践也采用 jdk1.8 版本作为基础,如果是其他版本的jdk,可以借鉴思路。

2、接入JVM可观测。

请先接入 JVM可观测,从观测云视图上我们可以看出初始堆内存为80 M ,与我们启动时指定参数一致。

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

3、接入日志可观测

参考 Kubernetes 集群中日志采集的几种玩法,本次主要是采用 socket 方式,也可以用其他方式。

堆溢出 -java.lang.OutOfMemoryError: Java heap space

堆溢出异常,相信大家很常见。即堆内对象不能进行回收了,堆内存持续增大,这样达到了堆内存的最大值,数据满了,所以就出来了。我们直接放溢出的代码样例。设置启动最大堆内存为-Xmx80m,这样我们设置为最大堆内存,这样运行起来就很快就出来错误了。

1、启动参数

-Xmx80m
-javaagent:C:/"Program Files"/datakit/data/dd-java-agent.jar
-Ddd.service=system
-Ddd.agent.port=9529

2、请求

浏览器请求 http://localhost:9201/exec/heapOOM,需要等一段时间才能看到异常输出。看到异常输出后,即可前往观测云查看对应的日志。

3、观测云查看日志

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

栈溢出 -java.lang.OutOfMemorryError

抛出来的异常如下,如果真的需要创建线程,我们需要调整帧栈的大小-Xss512k,默认帧栈大小为1M,如果设置小了,可以创建更多线程。如果帧栈不够用了,我们需要了解什么地方创建了很多线程,线上程序需要用jstack 命令,将当前线程的状态导出来放到文件里边,然后将文件上传到 fastthread.io 网站上进行分析。若代码确实需要这么多的线程,此时可以根据 【JVM总内存 - 堆 = n*Java虚拟机栈 】,来减小堆的内存或者Xss来解决增加可分配线程的数量。

1、启动参数

-Xmx80m

-javaagent:C:/"Program Files"/datakit/data/dd-java-agent.jar

-Ddd.service=system

-Ddd.agent.port=9529

2、请求

浏览器访问地址:http://localhost:9201/exec/stackOOM

3、观测云查看日志

瞬间创建线程,JVM 自带工具不在上报线程等相关监控指标,观测云仍然上报最新 JVM 监控指标。

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

一段时间后,JVM 自带工具出现异常。

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

随后系统就会出现假死现象。

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

栈溢出 -java.lang.StackOverFlowError

主要表现在递归调用、死循环上,无论是由于栈帧太大还是虚拟机栈容量太小,当新的栈帧内存无非分配的时候,HotSpot 虚拟机抛出的都是 StackOverFlowError 异常。程序每次递归的时候,程序会把数据结果压入栈,包括里边的指针等,这个时候就需要帧栈大一些才能承受住更多的递归调用。

1、启动参数

-Xmx80m

-javaagent:C:/"Program Files"/datakit/data/dd-java-agent.jar

-Ddd.service=system

-Ddd.agent.port=9529

2、请求

浏览器请求 http://localhost:9201/exec/stackOFE

3、观测云查看日志

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

元信息溢出 -java.lang.OutOfMemoryError: Metaspace

在 JDK 8 以后,永久代便完全退出历史舞台,元空间作为其替代者登场,元数据区域也成为方法区。在默认设置下,很难迫使虚拟机产生方法区(元数据区域)的溢出异常,存储着类的相关信息,常量池,方法描述符,字段描述符,运行时产生大量的类就会造成这个区域的溢出。启动的时候设置 XX:MetaspaceSizeXX:MaxMetaspaceSize过小时,直接启动报错。

1、启动参数

-Xmx80m

-XX:MetaspaceSize=30M

-XX:MaxMetaspaceSize=90M

-javaagent:C:/"Program Files"/datakit/data/dd-java-agent.jar

-Ddd.service=system

-Ddd.agent.port=9529

2、请求

浏览器输入 http://localhost:9201/exec/metaspaceOOM。

3、查看日志

元数据溢出后,不会往再写入日志等相关操作。

直接内存溢出 -java.lang.OutOfMemoryError: Direct buffer memory

直接内存溢出,我们除了使用堆内存外,我们还可能用直接内存,即堆外内存。NIO 为了提高性能,避免在 Java Heap和native Heap中切换,所以使用直接内存,默认情况下,直接内存的大小和对内存大小一致。堆外内存不受JVM的限制,但是受制于机器整体内存的大小限制。如下代码设置堆最大内存为 80m,直接内存为 70m,然后我们每次分配1M放到list里边。这个时候,当输出 70 次( Springboot 应用会小于 70次 )的时候,下次再分配的时候会报 nested exception is java.lang.OutOfMemoryError: Direct buffer memory

1、启动参数

-Xmx80m

-javaagent:C:/"Program Files"/datakit/data/dd-java-agent.jar

-Ddd.service=system

-Ddd.agent.port=9529

2、请求

浏览器输入 http://localhost:9201/exec/directBufferOOM 。

3、观测云查看日志

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

GC超限 -java.lang.OutOfMemoryError: GC overhead limit exceeded

前面三种都会引起 GC 超限。JDK1.6 之后新增了一个错误类型,如果堆内存太小的时候会报这个错误。如果 98% 的 GC 的时候回收不到 2% 的时候会报这个错误,也就是最小最大内存出现了问题的时候会报这个错误。

观测云

无论是哪种异常,我们可以在观测云 JVM 监控视图上找到一些线索,同时结合日志情况,对 JVM 参数进行调优。gc 次数过多过少、gc 时间过长、线程突然增多、堆内存突然增多等等,都需要引起我们关注。

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

观测云 OOM 日志告警

以上几种 OOM 异常场景也只是演示了如何产生异常以及在观测云上如何表现。实际生产过程中, OOM 异常会影响业务逻辑,更严重的会导致系统中断。可以借助观测云告警功能快速通知相关人员进行干预。

配置 StackOverflowError 异常检测

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

配置 OutOfMemoryError 异常检测

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

配置告警通知

监控器列表 - 分组 ,点击告警通知按钮

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

配置通知对象,观测云支持多种通知对象,当前采用的是邮件通知。

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

触发异常后,可以收到邮件通知,内容如下:

setting the value of 'sessionobj' exceeded the quota.,技术干货,可观测实践,java,云计算,devops,运维,可观测性

演示代码

本程序代码是在若依微服务框架上进行演示的。文章来源地址https://www.toymoban.com/news/detail-737754.html

package com.ruoyi.system.controller;

import com.ruoyi.common.core.domain.system.SysDept;
import com.ruoyi.common.core.web.domain.AjaxResult;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author liurui
 * @date 2022/4/11 9:28
 */
@RequestMapping("/exec")
@RestController
public class ExceptionController {
    
    @GetMapping("/heapOOM")
    public AjaxResult heapOOM() {
        List<SysDept> list = new ArrayList<>();
        while (true) {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add(new SysDept());
        }
    }

    @GetMapping("/stackOOM")
    public AjaxResult stackOOM() {
        while (true) {
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.HOURS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            });
            thread.start();
        }
    }

    @GetMapping("/directBufferOOM")
    public AjaxResult directBufferOOM() {
        final int _1M = 1024 * 1024 * 1;
        List<ByteBuffer> buffers = new ArrayList<>();
        int count = 1;
        while (true) {
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);
            buffers.add(byteBuffer);
            System.out.println(count++);
        }
    }

    @GetMapping("/stackOFE")
    public AjaxResult StackOFE() {
        stackOverFlowErrorMethod();
        return AjaxResult.success();
    }

    public static void stackOverFlowErrorMethod() {
        stackOverFlowErrorMethod();
    }

    @GetMapping("/metaspaceOOM")
    public AjaxResult metaspaceOOM() {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(SysDept.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method,
                                        Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(obj, args);
                }
            });
            enhancer.create();
        }
    }
}

到了这里,关于JAVA OOM异常可观测最佳实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java异常处理的20个最佳实践:告别系统崩溃

    在Java编程中,异常处理是一个至关重要的环节,它不仅涉及到程序的稳定性和安全性,还关系到用户体验和系统资源的合理利用。合理的异常处理能够使得程序在面对不可预知错误时,能够优雅地恢复或者给出明确的反馈,而不是简单地崩溃退出。 文章开始前,我们先看下

    2024年02月22日
    浏览(34)
  • Java 异常处理与正则表达式详解,实例演练及最佳实践

    在 Java 代码执行期间,可能会发生各种错误,包括程序员编码错误、用户输入错误以及其他不可预料的状况。 当错误发生时,Java 通常会停止并生成错误消息,这个过程称为抛出异常。 try 语句允许您定义一段代码块,并在其中测试是否发生错误。 catch 语句允许您定义一段代

    2024年03月13日
    浏览(23)
  • Cannot invoke “Object.toString()“ because the return value of “java.util.Map.get(Object)“ is null

    在写瑞吉外卖的时候遇到 Object.toString()为空的问题,最后发现是 前端没有往后端传递数据,第一个图片是只传的phone最后改为from第二个是没有写函数sendMsgApi,改正后,发现登录进去又回退到主页面,发现是因为在过滤器中要保存session,保存后才能发送,再次添加形参Http

    2024年02月02日
    浏览(38)
  • 【Java异常】完美解决this version of the Java Runtime only recognizes class file versions up to xx.0异常

    哈喽各位,我是小白。时隔多日我又回来啦! 最近在部署项目后,发现线上业务数据都变成了默认值0,而且这个现象,仅仅出现在当次上线后生成的数据中 于是我去扫了一眼日志,发现如下报错 Caused by: java.lang.UnsupportedClassVersionError: org/eclipse/core/resources/IResource has been comp

    2024年02月11日
    浏览(31)
  • The importance of value passing and reference passing

    不管哪个语言都有值传递,引用传递两种方式,在C语言里面引用传递用指针搞定。在Java里面没有指针,所以Java就规定(之前的设计)了某些类型是值传递,某些类型是引用传递,当然完全没必要去全部类型分析一遍,比如int是什么XX传递,String是什么XX传递...,浪费时间,因

    2024年02月08日
    浏览(29)
  • Java异常 #Number of lines annotated by Git is not equal to number of lines in the file, check file …

    在项目中某个 java 文件左边栏右键查看代码版本履历(Annotate)时无法显示,IDEA 提示:Number of lines annotated by Git is not equal to number of lines in the file, check file encoding and line separators.   这个问题涉及到不同操作系统下文本文件的换行符差异引起的。在不同操作系统中,文本文件的

    2024年02月03日
    浏览(30)
  • Failed to calculate the value of task ‘:unityLibrary:compileDebugJavaWithJavac‘

    在打包时出现了如下问题: Failed to calculate the value of task \\\':unityLibrary:compileDebugJavaWithJavac\\\' property \\\'options.generatedSourceOutputDirectory\\\' 这个是由于 Gradle 版本导致的,Gradle 7.x 及以上版本进行了更新,将 Gradle 版本下调至 6.x 版本即可解决。

    2024年02月11日
    浏览(45)
  • 【异常】has been compiled by a more recent version of the Java Runtime (class file version 55.0)

    使用全局的JDK环境来运行项目,提示如下: 才想起来,这个项目的JDK版本是11,因为类是在比试图运行它的版本更高的Java版本编译的 更具体地说,在这种情况下,使用Java 11编译了类,并尝试使用Java 8运行它。

    2024年02月16日
    浏览(28)
  • Pioneer | X METAVERSE PRO Explores the New Value of “Mining + Finance“

    “The mining boom driven by Bitcoin has created many wealth myths: miners can earn 50 BTC every 10 minutes at that time. If you successfully get a Bitcoin block and hold it since 2009, you will have BTC worth $827,930 in your wallet by 2022. “ Cryptocurrency mining is an investment track with high popularity and high returns in the market. Especially in t

    2024年01月16日
    浏览(52)
  • Gradle打包报错:Failed to calculate the value of task ‘:unityLibrary:compileReleaseJavaWithJavac‘

    Unity项目使用Gradle打包时报如下错误: Failed to calculate the value of task \\\':unityLibrary:compileReleaseJavaWithJavac\\\' property \\\'options.generatedSourceOutputDirectory\\\'. Unity版本:2020.3.17f1; Gradle版本:7.6; 来自Unity官方的解决方案:更换Gradle版本为6.7或者6.8即可  原文链接:Troubleshooting Android integration

    2024年02月11日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包