定位频繁创建对象导致内存溢出风险的思路

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
可观测监控 Prometheus 版,每月50GB免费额度
性能测试 PTS,5000VUM额度
简介: 定位频繁创建对象导致内存溢出风险的思路

背景介绍

近来一段时间排查了几例由于应用程序频繁创建对象导致FGC的问题,问题产生的场景包括:

  • 同一条SQL语句,平时返回预期内的数据条数,出问题的时候返回了几十万条数据,短时间内创建了大量对象进而导致非预期的GC。
  • 应用程序在处理Excel文件的时候,短时间内创建了大量对象进而导致非预期GC。
  • 接口的入参是List<Object>,未对入参参数个数做控制,导致后续逻辑创建了大量对象进而导致非预期GC。
  • … …

综上,无论是何种具体场景,最终的结果是短时间内创建了大量对象,导致GC频次升高,GC频次升高到一定程度会导致CPU飙高(频繁GC是导致CPU飙高的一个原因,所以在遇到CPU飙高的现象,可以迅速看下是否GC频繁导致,而不是首先dump热点线程/方法)。

如何发现此类问题呢?通常的做法是监控GC频次及GC耗时;发现了此类问题如何定位呢?网上此类问题的文章比较多,总结分析下来就是要定位出两个事情:

  1. 哪些非预期的对象大量占用了内存
  2. 这些非预期的对象是在什么地方构造出来的,即定位出哪块代码出的问题(当然有的时候通过第1步的对象就可以确定对象是由哪块代码初始化的)

进一步思考,是否可以在发现此类问题的时候,就能够将非预期的对象和分配该对象的线程栈展示出来,即将事后的定位分析前移到事中,变成一种常规的运维方式,以提高问题发现和定位的效率,节约研发人员的时间。

下面内容从此类问题发现和定位两个方面进行思路的总结。

发现(事中)

需要一种低开销的可编程的实时分析对象分配的方式,几种实时分析对象分配的方式如下。

字节码增强方式

HeapTracker

JDK提供的DEMO,总体思路:

  1. HOOK JVMTI_EVENT_CLASS_FILE_LOAD_HOOK事件,插入调用HeapTracker的字节码;
  2. 在创建对象的时候,通过插入的HeapTracker字节码完成对象分配的监控逻辑。

详情可参考:https://heapdump.cn/article/5484283

google allocation-instrumenter

Allocation Instrumenter是一个使用Java .lang.instrument API和ASM编写的Java代理。Java程序中的每个分配都是工具化的;在每个分配上调用用户定义的回调。

纯JAVA开发的agent,支持指定类对象分配的监控,应用启动时候需要指定-javaagent:path/to/java-allocation-instrumenter-3.3.1.jar,不支持attach方式。

使用方法:

<dependency>
  <groupId>com.google.code.java-allocation-instrumenter</groupId>
  <artifactId>java-allocation-instrumenter</artifactId>
  <version>3.3.1</version>
</dependency>
import com.google.monitoring.runtime.instrumentation.AllocationRecorder;
import com.google.monitoring.runtime.instrumentation.Sampler;
public class Test {
    public static void main(String [] args) throws Exception {
        AllocationRecorder.addSampler(new Sampler() {
            public void sampleAllocation(int count, String desc, Object newObj, long size) {
                System.out.println("I just allocated the object " + newObj 
                                   + " of type " + desc + " whose size is " + size);
                if (count != -1) { System.out.println("It's an array of size " + count); }
            }
        });
        for (int i = 0 ; i < 10; i++) {
            new String("foo");
        }
    }
}

详情:https://github.com/google/allocation-instrumenter

JVMTI SampledObjectAlloc

SampledObjectAlloc是在JDK11引入的,在功能和性能上是一个比较好的选择,可惜的是JDK8不支持,而JDK8 JVMTI VMObjectAlloc并不能完全覆盖对象分配的场景。

详情:https://heapdump.cn/article/5494193

定位(事后)

【发现(事中)】的字节码增强方式对系统性能影响较大,如果不能实现【发现(事中)】阶段的问题定位,那么需要总结下【定位(事后)】的一些套路,目的是以便捷、低代价的方式定位出问题。

下面代码示例模拟的是应用程序每隔一段时间创建大量对象:

package allocate;
import java.lang.management.ManagementFactory;
import java.util.LinkedList;
import java.util.List;
public class StackTest {
    private byte[] test;
    public StackTest(){
        this.test = new byte[1024];
    }
    public static void main(String[] args) throws Exception{
        System.out.println(ManagementFactory.getRuntimeMXBean().getName());
        while (true){
            Thread.sleep(1000L);
            a();
        }
    }
    public static void a(){
        b();
    }
    public static void b(){
        c();
    }
    private static void c(){
        List<StackTest> list = new LinkedList<>();
        for (int i = 0;i<100000;i++){
            list.add(new StackTest());
        }
    }
}

定位内存占用高的对象

当出现此类问题的时候,直接dump整个JVM代价比较高,某些场景下并不是一种高效的处理方式。

遇到此类问题的时候可以先使用jmap -histo看看哪些对象占据内存比较多,有些时候通过这些对象就可以定位是在什么逻辑的地方分配了这些对象:

如果不熟悉业务逻辑,或者一些对象并不属于业务对象则可以借助一些工具进行业务逻辑相关代码定位。

定位分配对象的逻辑

当我们通过jmap -histo发现有大量StackTest对象创建,如何定位创建StackTest的业务代码呢?可以通过两种方式:

arthas stack

通过arthas stack定位创建StackTest对象的业务代码。

arthas profiler

有的时候通过arthas stack并不好定位,比如分配了大量byte[]等对象类型的逻辑,此时可以使用arthas profiler进行分析。

profiler start --event alloc:

查看生成的profiler文件:

此处profiler主要用来进行故障排查,当然也可以用在性能分析优化的场景。

heapdump memory

如果以上轻量级的排查方式还是不能定位问题,可以考虑使用jmap -dump:format=b,file=heapDump <pid>的方式将内存dump下来,然后使用MAT等工具进行分析的方式。

分析dump内存文件的文档比较多,这里就不再做介绍了。

总结

  • 期望能够在发现问题的时候就能够给出产生问题的原因,以提高故障排查效率
  • HeapTracker、google allocation-instrumenter、JVMTI SampledObjectAlloc提供了监控对象分配的方式,需要根据具体场景来应用
  • 事后分析的套路是:定位异常对象->定位异常对象分配线程,实在不习则需要分析内存dump文件来进行定位。
目录
相关文章
|
4月前
|
存储 设计模式 监控
运用Unity Profiler定位内存泄漏并实施对象池管理优化内存使用
【7月更文第10天】在Unity游戏开发中,内存管理是至关重要的一个环节。内存泄漏不仅会导致游戏运行缓慢、卡顿,严重时甚至会引发崩溃。Unity Profiler作为一个强大的性能分析工具,能够帮助开发者深入理解应用程序的内存使用情况,从而定位并解决内存泄漏问题。同时,通过实施对象池管理策略,可以显著优化内存使用,提高游戏性能。本文将结合代码示例,详细介绍如何利用Unity Profiler定位内存泄漏,并实施对象池来优化内存使用。
261 0
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
53 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
2月前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
169 4
Android性能测试——发现和定位内存泄露和卡顿
|
3月前
|
搜索推荐 Java API
Electron V8排查问题之分析 node-memwatch 提供的堆内存差异信息来定位内存泄漏对象如何解决
Electron V8排查问题之分析 node-memwatch 提供的堆内存差异信息来定位内存泄漏对象如何解决
100 0
|
4月前
|
编译器
8086 汇编笔记(六):更灵活的定位内存地址的方法
8086 汇编笔记(六):更灵活的定位内存地址的方法
|
5月前
|
存储 监控 算法
【JVM】如何定位、解决内存泄漏和溢出
【JVM】如何定位、解决内存泄漏和溢出
225 0
|
6月前
|
Java Shell
java中jvm使用jststak定位线程cpu占用内存高的线程
java中jvm使用jststak定位线程cpu占用内存高的线程
51 5
|
6月前
|
存储 缓存 监控
深入解析linux内存指标:快速定位系统内存问题的有效技巧与实用方法(free、top、ps、vmstat、cachestat、cachetop、sar、swap、动态内存、cgroops、oom)
深入解析linux内存指标:快速定位系统内存问题的有效技巧与实用方法(free、top、ps、vmstat、cachestat、cachetop、sar、swap、动态内存、cgroops、oom)
1246 0
|
6月前
|
缓存 架构师 算法
Java内存溢出如何解决,Java oom排查方法,10个定位解决办法
在Java开发过程中,有效的内存管理是保证应用程序稳定性和性能的关键。不正确的内存使用可能导致内存泄露甚至是致命的OutOfMemoryError(OOM)。
139 0
|
存储 Java BI
MAT工具定位分析Java堆内存泄漏问题方法
MAT,全称Memory Analysis Tools,是一款分析Java堆内存的工具,可以快速定位到堆内泄漏问题。该工具提供了两种使用方式,一种是插件版,可以安装到Eclipse使用,另一种是独立版,可以直接解压使用。
104 0