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 应用的内存问题。
相关文章:
Async-profiler 内存采样机制解析:从原理到实现
引言 在 Java 性能调优的工具箱中,async-profiler 是一款备受青睐的低开销采样分析器。它不仅能分析 CPU 热点,还能精确追踪内存分配情况。本文将深入探讨 async-profiler 实现内存采样的多种机制,结合代码示例解析其工作原理。 为什么需要内…...
C++ 使用 ffmpeg 解码 rtsp 流并获取每帧的YUV数据
一、简介 FFmpeg 是一个开源的多媒体处理框架,非常适用于处理音视频的录制、转换、流化和播放。 二、代码 示例代码使用工作线程读取rtsp视频流,自动重连,支持手动退出,解码并将二进制文件保存下来。 注意: 代…...
Java毕业设计:办公自动化系统的设计与实现
JAVA办公自动化系统 一、系统概述 本办公自动化系统基于Java EE平台开发,实现了企业日常办公的数字化管理。系统包含文档管理、流程审批、会议管理、日程安排、通讯录等核心功能模块,采用B/S架构设计,支持多用户协同工作。系统使用Spring B…...

论文笔记:Large Language Models for Next Point-of-Interest Recommendation
SIGIR 2024 1 intro 传统的基于数值的POI推荐方法在处理上下文信息时存在两个主要限制 需要将异构的LBSN数据转换为数字,这可能导致上下文信息的固有含义丢失仅依赖于统计和人为设计来理解上下文信息,缺乏对上下文信息提供的语义概念的理解 ——>使用…...

LeetCode 2894.分类求和并作差
目录 题目: 题目描述: 题目链接: 思路: 思路一详解(遍历 判断): 思路二详解(数学规律/公式): 代码: Java思路一(遍历 判断&a…...
n8n:解锁自动化工作流的无限可能
在当今快节奏的数字时代,无论是企业还是个人,都渴望提高工作效率,减少重复性任务的繁琐操作。而 n8n,这个强大的开源自动化工具,就像一位智能的数字助手,悄然走进了许多人的工作和生活,成为提升…...
链结构与工作量证明7️⃣:用 Go 实现比特币的核心机制
链结构与工作量证明:用 Go 实现比特币的核心机制 如果你用 Go 写过区块、算过哈希,也大致理解了非对称加密、数据序列化这些“硬核知识”,那么恭喜你,现在我们终于可以把这些拼成一条完整的“区块链”。 不过别急,这一节我们重点搞懂两件事: 区块之间是怎么连接成“链”…...
CMake系统学习笔记
CMake系统学习笔记 基础操作 最基本的案例 // code #include <iostream>int main() {std::cout << "hello world " << std::endl;return 0; }// CMakeLists.txt cmake_minimum_required(VERSION 3.0)# 定义当前工程名称 project(demo)add_execu…...

CCF 开源发展委员会 “开源高校行“ 暨红山开源 + OpenAtom openKylin 高校行活动在西安四所高校成功举办
点击蓝字 关注我们 CCF Opensource Development Committee CCF开源高校行 暨红山开源 openKylin 高校行 西安站 5 月 26 日至 28 日,CCF 开源发展委员会 "开源高校行" 暨红山开源 OpenAtom openKylin 高校行活动在西安四所高校(西安交通大学…...
【Go语言基础【6】】字符串格式化说明
文章目录 零、格式化常用场景一、Go 字符串格式化核心概念二、常用格式化占位符1. 整数类型2. 浮点数类型3. 字符串与布尔类型4. 指针与通用类型 三、宽度与精度控制1. 宽度控制2. 精度控制(浮点数/字符串) 零、格式化常用场景 数值转字符串:…...
调试快捷键 pycharm vscode
目录 调试快捷键 pycharm vscode 修改快捷键 方法 1:通过菜单打开 方法 2:用快捷键打开 调试快捷键 pycharm Resume Program F9 Step Over F8 两个离的比较近,比较方便,比vscode的好。 vscode Continue F5 改为F9 S…...

RabbitMQ work模型
Work 模型是 RabbitMQ 最基础的消息处理模式,核心思想是 多个消费者竞争消费同一个队列中的消息,适用于任务分发和负载均衡场景。同一个消息只会被一个消费者处理。 当一个消息队列绑定了多个消费者,每个消息消费的个数都是平摊的&a…...

基于微信小程序的作业管理系统源码数据库文档
作业管理系统 摘 要 随着社会的发展,社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景,运用软件工程原理和开发方法,它主要是采用java语言技术和微信小程序来完成对系统的…...
C++参数传递 a与a的区别
在 C 中,&a(引用)和 a(值传递) 的关键区别在于 参数如何传递给函数,以及由此引发的 性能、语义和安全问题。 最核心的在于你想不想传入的参数被改变,如果想,就用参数传递&#…...

CSS(2)
文章目录 Emmet语法快速生成HTML结构语法 Snipaste快速生成CSS样式语法快速格式化代码 快捷键(VScode)CSS 的复合选择器什么是复合选择器交集选择器后代选择器(重要)子选择器(重要)并集选择器(重要)**链接伪类选择器**focus伪类选…...
Linux--vsFTP配置篇
一、vsFTP 简介 vsftpd(Very Secure FTP Daemon)是 Linux 下常用的 FTP 服务程序,具有安全性高、效率高和稳定性好等特点。支持匿名访问、本地用户登录、虚拟用户等多种认证方式,并可灵活控制权限。 二、安装与启动 1. 检查是否已…...

【RabbitMQ】- Channel和Delivery Tag机制
在 RabbitMQ 的消费者代码中,Channel 和 tag 参数的存在是为了实现消息确认机制(Acknowledgment)和精细化的消息控制。 Channel 参数 作用 Channel 是 AMQP 协议的核心操作接口,通过它可以直接与 RabbitMQ 交互: 手…...
.Net Framework 4/C# 面向对象编程进阶
一、继承 (一)使用继承 子类可以继承父类原有的属性和方法,也可以增加原来父类不具备的属性和方法,或者直接重写父类中的某些方法。 C# 中使用“:”来表示两个类的继承。子类不能访问父类的私有成员,但是可以访问其公有成员,即只要使用 public 声明类成员,就既可以让一…...

NLP学习路线图(三十四): 命名实体识别(NER)
一、命名实体识别(NER)是什么? 命名实体识别(Named Entity Recognition, NER)是自然语言处理中的一项关键序列标注任务。其核心目标是从非结构化的文本中自动识别出特定类别的名词性短语,并将其归类到预定义的类别中。 核心目标:找到文本中提到的命名实体,并分类。 典…...

【HTML】HTML 与 CSS 基础教程
作为 Java 工程师,掌握 HTML 和 CSS 也是需要的,它能让你高效与前端团队协作、调试页面元素,甚至独立完成简单页面开发。本文将用最简洁的方式带你掌握核心概念。 一、HTML,网页骨架搭建 核心概念:HTML通过标签定义内…...
【NLP】 38. Agent
什么是 Agent? 一个 Agent 就是能够 理解、思考,并且进行世界交互 的模型系统,并不是纯粹的 prompt 返回器。 它可以: 读取外部数据(文件/API)使用记忆进行上下文维持用类Chain-of-Thought (CoT)方式进行…...

Windows开机自动启动中间件
WinSW(Windows Service Wrapper 是一个开源的 Windows 服务包装器,它可以帮助你将应用程序打包成系统服务,并实现开机自启动的功能。 一、下载 WinSW 下载 WinSW-x64.exe v2.12.0 (⬇️ 更多版本下载) 和 sample-minimal.xml 二、配置 WinS…...
AIGC 基础篇 Python基础 02
1.bool类型 书接上回,我们上次最后讲了三大数据类型,除了这三个之外,Python也有bool类型,也就是True和False。 a 2 print(a1) print(a2) 像这里,输出的内容第一个是False,因为a的值为2,而第…...

【图片转AR场景】Tripo + Blender + Kivicube 实现图片转 AR 建模
总览 1.将 2D 图片转为立体建模 2. 3. 一、将 2D 图片转为立体建模 1.工具介绍 Tripo 网站 2.找图片 找的图片必须是看起来能够让 AI 有能力识别和推理的,因为现在的AI虽然可以补全但是能力还没有像人的想象力那么丰富。 比如上面这张图片,看起来虽…...
NLP常用工具包
✨做一次按NLP项目常见工具的使用拆解 1. tokenizer from torchtext.data.utils import get_tokenizertokenizer get_tokenizer(basic_english) text_sample "Were going on an adventure! The weather is really nice today." tokens tokenizer(text_sample) p…...

LSTM-XGBoost多变量时序预测(Matlab完整源码和数据)
LSTM-XGBoost多变量时序预测(Matlab完整源码和数据) 目录 LSTM-XGBoost多变量时序预测(Matlab完整源码和数据)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 普通的多变量时序已经用腻了,审稿人也看烦了&#…...
Git 切换到旧提交,同时保证当前修改不丢失
在 Git 中,可以通过以下几种方式切换到之前的提交,同时保留当前的修改 1. 使用 git checkout 创建临时分离头指针(推荐用于查看代码) git checkout <commit-hash>这会让你进入"分离头指针"状态,你可…...

C#学习12——预处理
一、预处理指令: 解释:是在编译前由预处理器执行的命令,用于控制编译过程。这些命令以 # 开头,每行只能有一个预处理指令,且不能包含在方法或类中。 个人理解:就是游戏里面的备战阶段(不同对局…...

Razor编程中@Helper的用法大全
文章目录 第一章:Helper基础概念1.1 Helper的定义与作用1.2 Helper的基本语法结构1.3 Helper与HtmlHelper的区别 第二章:基础Helper用法2.1 无参数Helper2.2 带简单参数的Helper2.3 带默认值的参数2.4 使用模型作为参数 第三章:高级Helper用法…...
React 样式方案与状态方案初探
React 本身只提供了基础 UI 层开发范式,其他特性的支持需要借助相关社区方案实现。本文将介绍 React 应用体系中样式方案与状态方案的主流选择,帮助开发者根据项目需求做出合适的选择。 1. React 样式方案 1.1. 内联样式 (Inline Styles) 通过 style …...