Async-profiler 内存采样机制解析:从原理到实现

引言

在 Java 性能调优的工具箱中,async-profiler 是一款备受青睐的低开销采样分析器。它不仅能分析 CPU 热点,还能精确追踪内存分配情况。本文将深入探讨 async-profiler 实现内存采样的多种机制,结合代码示例解析其工作原理。

为什么需要内存采样?

在排查 Java 应用的内存问题时,我们常常需要回答这些问题:

  • 哪些对象占用了最多的堆内存?
  • 哪些代码路径产生了大量临时对象?
  • 垃圾回收频繁的根源是什么?

async-profiler 的内存采样功能能够追踪对象分配的位置和大小,帮助我们定位内存泄漏和过度分配问题。

JVM 内存分配基础

在深入 async-profiler 的实现之前,先简要了解 JVM 的内存分配机制:

  • TLAB(Thread Local Allocation Buffer):每个线程独享的小型内存区域,用于快速分配小型对象
  • 大对象直接分配:超过 TLAB 大小的对象会直接在堆上分配
  • 栈上分配:某些情况下,对象可以直接在栈上分配,避免堆内存压力

Async-profiler 内存采样的多种机制

机制一:JVMTI ObjectSample 事件(JDK 11+)

JVMTI(Java Virtual Machine Tool Interface)提供了 ObjectSample 事件,允许在对象分配时触发回调。这是最直接的内存采样方式,但在 JDK 11 之前存在局限性。

// JVMTI ObjectSample 事件监听示例
public class AllocationListener {public static void main(String[] args) throws Exception {// 通过JVMTI注册对象分配事件Agent.setObjectAllocationCallback((thread, classDesc, size) -> {System.out.printf("分配对象: %s, 大小: %d 字节\n", classDesc, size);});// 应用代码继续执行// ...}
}

局限性

  • 在 JDK 11 之前,只能捕获大对象(超过 TLAB 大小)的分配
  • 启用该事件会带来显著的性能开销

机制二:二进制插桩(JDK 11 之前的主要方式)

对于 JDK 11 之前的版本,async-profiler 采用更底层的二进制插桩技术,直接修改 HotSpot VM 的代码。

关键步骤:

1. 定位目标函数:在 HotSpot VM 的二进制代码中找到关键的内存分配函数

 if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer27send_allocation_in_new_tlab")) != NULL &&(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer28send_allocation_outside_tlab")) != NULL) {_trap_kind = 1;  // JDK 10+} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandleP8HeapWord")) != NULL &&(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_eventE11KlassHandleP8HeapWord")) != NULL) {_trap_kind = 1;  // JDK 8u262+} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_event")) != NULL &&(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_event")) != NULL) {_trap_kind = 2;  // JDK 7-9} else {return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");}

这个步骤需要JDK的Debug Symbols,所以很多系统比如Alpine运行的java应用就不支持内存采样,因为Alpine的SDK为了精简体积默认都不包含Debug Symbols。

2. 插入陷阱指令:在函数入口处写入跳转指令,指向自定义的处理函数

# 伪代码:在目标函数起始位置写入跳转指令
push <trap_handler_address>
ret

3. 陷阱处理函数:收集分配信息并采样堆栈

// 陷阱处理函数
void trap_handler(KlassHandle klass, HeapWord* obj) {// 获取对象大小size_t size = get_object_size(klass);// 采样当前线程的堆栈void* stack[100];int depth = capture_stacktrace(stack, 100);// 记录分配事件record_allocation(obj, size, stack, depth);// 跳回原始函数继续执行execute_original_instructions();
}

4. 恢复原始代码:采样结束后恢复原始指令,减少对性能的影响

这种方法虽然强大,但也有明显缺点:

  • 与特定 JDK 版本深度耦合,兼容性差
  • 需要JDK包含Debug Symbols,很多系统比如Alpine的SDK都支持
  • 需要 root 权限才能修改运行中的 VM 进程
  • 实现复杂,稍有不慎就可能导致 JVM 崩溃

机制三:LD_PRELOAD 技术(针对堆外内存)

对于 Java 堆外内存分配(如 JNI 调用),async-profiler 使用 LD_PRELOAD 技术拦截 C 库的内存分配函数。

// preload.c - 使用LD_PRELOAD拦截malloc
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>// 原始malloc函数指针
static void* (*real_malloc)(size_t) = NULL;// 自定义malloc函数
void* malloc(size_t size) {// 首次调用时获取原始malloc函数地址if (!real_malloc) {real_malloc = dlsym(RTLD_NEXT, "malloc");}// 记录分配前的时间和堆栈void* ptr = real_malloc(size);// 记录分配信息record_allocation(ptr, size, get_current_stack());return ptr;
}

使用方式:

# 编译共享库
gcc -shared -fPIC preload.c -o preload.so -ldl# 运行Java程序时加载拦截库
LD_PRELOAD=./preload.so java YourMainClass

机制四:DTrace/SystemTap(特定平台)

在支持 DTrace 或 SystemTap 的系统中,async-profiler 可以使用这些工具进行动态插桩。

DTrace 示例:
// 监控Java对象分配的DTrace脚本
hotspot$target:::object-allocated
{// 获取对象类型和大小@allocations[copyinstr(arg1)] = sum(arg2);// 记录堆栈trace(arg0);ustack();
}

运行方式:

dtrace -s alloc.d -p <java_pid>

这种方法的优势是无需修改 Java 程序或 VM,但依赖特定平台支持。

Async-profiler 内存采样实战

下面通过一个简单的 Java 程序,演示如何使用 async-profiler 进行内存采样。

示例程序:

import java.util.ArrayList;
import java.util.List;public class MemoryAllocationDemo {public static void main(String[] args) throws InterruptedException {List<String> list = new ArrayList<>();// 生成大量字符串对象for (int i = 0; i < 1000000; i++) {list.add("Object-" + i);// 每10万次分配休眠一下,方便我们进行采样if (i % 100000 == 0) {Thread.sleep(100);}}System.out.println("分配完成,按任意键退出...");System.in.read();}
}

使用 async-profiler 进行内存采样:

# 编译Java程序
javac MemoryAllocationDemo.java# 运行程序
java MemoryAllocationDemo &# 获取Java进程ID
PID=$!# 使用async-profiler进行10秒的内存分配采样
./profiler.sh -e alloc -d 10 $PID# 生成火焰图
./profiler.sh -e alloc -f allocation-flamegraph.svg $PID

总结

async-profiler 的内存采样机制根据不同 JDK 版本和场景采用了多种技术:

  • JVMTI ObjectSample:简单直接,但在 JDK 11 之前功能有限
  • 二进制插桩:强大但复杂,与特定 JDK 版本深度绑定,且需要SDK含有Debug Symbols
  • LD_PRELOAD:适用于堆外内存分配的拦截
  • DTrace/SystemTap:平台特定但无需修改目标程序

理解这些机制有助于我们在不同场景下选择最合适的工具和方法,更高效地解决 Java 应用的内存问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/82568.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 颜色百分比对照

本文就是简单写个demo,打印下颜色百分比的数值.方便以后使用. 1: 获取透明色 具体的代码如下: /*** 获取透明色* param percent* param red* param green* param blue* return*/public static int getTransparentColor(int percent, int red, int green, int blue) {int alp…

MPLS-EVPN笔记详述

目录 EVPN简介: EVPN路由: 基本四种EVPN路由 扩展: EVPN工作流程: 1.启动阶段: 2.流量转发: 路由次序整理: 总结: EVPN基本术语: EVPN表项: EVPN支持的多种服务模式: 简介: 1.Port Based: 简介: 配置实现: 2.VLAN Based: 简介: 配置实现: 3.VLAN Bundle: 简…

SpringBoot自定义线程池详细教程

文章目录 1. 线程池基础概念1.1 什么是线程池1.2 Java线程池核心参数1.3 线程池执行流程 2. SpringBoot中的线程池2.1 SpringBoot默认线程池2.2 SpringBoot异步任务基础 3. 自定义线程池配置3.1 配置文件方式3.2 Java配置方式3.3 线程池工厂配置 4. 异步任务实际应用4.1 业务服…

智能快递地址解析接口如何用PHP调用?

一、什么是智能快递地址解析接口 随着互联网技术的普及和电子商务的迅猛发展&#xff0c;网购已成为现代人日常生活的重要组成部分。然而&#xff0c;在这个便捷的背后&#xff0c;一个看似不起眼却影响深远的问题正悄然浮现——用户填写的快递地址格式混乱、信息不全甚至错漏…

概率分布,支撑AI算法的数学基石

概率分布,是现代人工智能(AI)算法不可或缺的数学语言。它不仅描述了数据中的不确定性,更揭示了机器学习模型背后的本质运作机制。本文将带你深入了解概率分布的数学本质,以及它在监督学习、深度学习、生成模型等核心AI领域的关键作用,揭秘概率论如何成为AI理论和实践的强…

2025年Splunk的替代方案:更智能的安全选择

在安全信息和事件管理&#xff08;SIEM&#xff09;领域&#xff0c;2025年的竞争愈发激烈。Splunk凭借其强大的功能和稳定性长期占据市场主导地位&#xff0c;但其高昂的成本、复杂性和扩展性挑战促使许多企业转向其他解决方案。无论是初创公司、快速发展的中型企业&#xff0…

(10)Fiddler抓包-Fiddler如何设置捕获Firefox浏览器的Https会话

1.简介 经过上一篇对Fiddler的配置后&#xff0c;绝大多数的Https的会话&#xff0c;我们可以成功捕获抓取到&#xff0c;但是有些版本的Firefox浏览器仍然是捕获不到其的Https会话&#xff0c;需要我们更进一步的配置才能捕获到会话进行抓包。 2.环境 1.环境是Windows 10版…

simulink mask的使用技巧

1.mask界面布局 1.1如何调整控件的位置和控件大小&#xff1f; 反正2020a是调不了&#xff0c; 找了好久&#xff0c;只能是调布局&#xff0c;例如你要调成下面这样&#xff1a; 第一个控件的iTem location属性选择New row 后面跟着的几个和第一个同一行的空间属性选择Cu…

Go中MAP底层原理分析

MAP底层原理分析 参考 https://golang.design/go-questions/map/principalmap | Golang 中文学习文档 先来看一下map结构体&#xff0c;&#xff08;runtime.hmap结构体就是代表着 go 中的map&#xff0c;与切片一样map的内部实现也是结构体&#xff09; type hmap struct {/…

#开发环境篇:postMan可以正常调通,但是浏览器里面一直报403

本地header代理下面内容即可 headers: { // 添加必要的请求头 ‘Host’: ‘服务端域名’, ‘Origin’: https://服务端域名, ‘Referer’: https://服务端域名 }, devServer: {// 本地开发代理API地址proxy: {^/file: {target: https://服务端域名,changeOrigin: true, // 是否…

【论文阅读 | PR 2024 |ICAFusion:迭代交叉注意力引导的多光谱目标检测特征融合】

论文阅读 | PR 2024 |ICAFusion&#xff1a;迭代交叉注意力引导的多光谱目标检测特征融合 1.摘要&&引言2.方法2.1 架构2.2 双模态特征融合&#xff08;DMFF&#xff09;2.2.1 跨模态特征增强&#xff08;CFE&#xff09;2.2.2 空间特征压缩&#xff08;SFS&#xff09;…

效率、便捷、安全:智慧充电桩一站式解决方案如何重塑新能源充电体验?

在新能源浪潮席卷全球的背景下&#xff0c;电动汽车的普及对充电基础设施提出了更高要求。传统充电模式因效率低、操作繁琐、安全隐患等问题&#xff0c;难以满足用户需求。智慧充电桩一站式解决方案应运而生&#xff0c;通过技术创新将效率、便捷与安全融为一体&#xff0c;彻…

杰发科技AC7840——Timer修改重装载值

需要在运行过程中修改定时器的中断时间 int main(void) {SystemClock_Config(); /*时钟初始化*/GPIO_LedInit(); /*GPIO初始化*/TIMER_Init(); /*定时器初始化*/InitDebug(); …

https和http有什么区别-http各个版本有什么区别

http和 https的区别 HTTP&#xff08;超文本传输协议&#xff09;和 HTTPS&#xff08;安全超文本传输协议&#xff09;是两种用于在网络上传输数据的协议&#xff0c;它们的主要区别在于安全性&#xff1a; HTTP&#xff08;Hypertext Transfer Protocol&#xff09;&#x…

低秩矩阵、奇异值矩阵和正交矩阵

低秩矩阵 低秩矩阵&#xff08;Low-rank Matrix&#xff09;是指秩&#xff08;rank&#xff09;远小于其行数和列数的矩阵&#xff0c;即 r a n k ( M ) r ≪ min ⁡ ( m , n ) rank(M) r \ll \min(m,n) rank(M)r≪min(m,n)。其核心特点是信息冗余性&#xff0c;可通过少量…

对抗性提示:大型语言模型的安全性测试

随着大语言模型&#xff08;LLM&#xff09;在虚拟助手、企业平台等现实场景中的深度应用&#xff0c;其智能化与响应速度不断提升。然而能力增长的同时&#xff0c;风险也在加剧。对抗性提示已成为AI安全领域的核心挑战&#xff0c;它揭示了即使最先进的模型也可能被操纵生成有…

SSM 框架核心知识详解(Spring + SpringMVC + MyBatis)

&#x1f331; 第一部分&#xff1a;Spring 核心原理与使用 1. 什么是 Spring Spring 是一个开源的 Java 企业级开发框架&#xff0c;旨在简化 Java 企业应用程序开发。它核心思想是控制反转&#xff08;IoC&#xff09;和面向切面编程&#xff08;AOP&#xff09;&#xff0…

基于 Alpine 定制单功能用途(kiosk)电脑

前言 故事回到 7 年前, 在网上冲浪的时候发现了一篇介绍使用 Ubuntu 打造 kiosk 单功能用途电脑的文章, 挺好玩的, 就翻译了一下并比葫芦画瓢先后用了 CentOS 7, ArchLinux 进行了实现. 历史文章: 翻译 - 使用Ubutnu14.04和Chrome打造单功能用途电脑(大屏展示电脑) 使用CentOS…

【机器学习及深度学习】机器学习模型的误差:偏差、方差及噪声

机器学习模型的误差分析 V1.0机器学习模型的衡量准则概念引入机器学习模型误差分析误差出现的原因及消除 V1.0 机器学习模型的衡量准则 衡量机器学习模型的好坏可以考虑以下几个方面&#xff1a; 偏差&#xff08;Bias&#xff09;&#xff1a; 在充分训练的情况下&#xff0…

混沌映射(Chaotic Map)

一.定义 混沌映射是指一类具有混沌行为的离散时间非线性动力系统&#xff0c;通常由递推公式定义。其数学形式为 &#xff0c;其中 f 是非线性函数&#xff0c;θ 为参数。它们以简单的数学规则生成复杂的、看似随机的轨迹&#xff0c;是非线性动力学和混沌理论的重要研究对象…