You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JVM进程内存中对象实例存储位置及内存对象定位问题

问题1:JVM进程中对象实例的存储位置

没有绝对统一的存储位置,和JVM版本、运行时优化、对象类型都有关系,常规分布如下:

  • 绝大多数普通对象实例(包括你提到的String对象的“对象壳”)默认分配在JVM管理的Java堆内存中。这块内存是JVM启动时向操作系统申请的用户态虚拟内存区间,在/proc/[pid]/maps中会显示为匿名映射段,由JVM的垃圾回收器统一管理内存的分配和回收。
  • 开启逃逸分析的前提下,不会逃逸出当前方法作用域的小体积对象,可能被JIT做标量替换优化,直接拆解成基础数据分配在当前方法栈帧的局部变量表中,不会进入Java堆。
  • 特殊对象的存储差异:
    • JDK7之前,intern驻留字符串、类静态变量存储在永久代(当时的方法区实现,属于堆内的一块非GC常规管理区域);JDK7开始这部分数据被挪到Java堆中;JDK8彻底移除永久代,替换为元空间,元空间使用堆外本地内存存储类元数据、方法字节码等信息,不占用Java堆配额。
    • DirectByteBuffer这类堆外内存对象,对象实例本身的壳分配在Java堆中,它指向的实际业务数据缓冲区分配在堆外本地内存,不在Java堆范围内。
问题2:有maps和mem权限能不能找到特定对象实例(比如具体的String对象)

理论上完全可行,但实操不能靠直接搜字符串内容瞎匹配,要遵循JVM的内存布局规则,核心逻辑如下:

  1. 先通过/proc/[pid]/maps定位JVM的内存段:不需要扫描整个进程的地址空间,先识别出Java堆、栈、元空间对应的映射区间,缩小扫描范围。如果要找堆上的String对象,直接定位Java堆对应的匿名映射段即可。
  2. 严格按照JVM对象内存布局匹配:堆上的所有对象都有固定结构,开头是对象头(包含mark word、指向类元数据的类型指针,开启压缩指针时类型指针占4字节,关闭时占8字节),之后才是对象的实例字段。
    以String对象为例:JDK8中String对象的实例字段里存的是指向char[]数组的引用、字符串hash缓存值,实际的字符内容存在对应的数组对象中,不是直接存在String对象本身的内存块里;JDK9之后String底层改为byte[]数组存储,额外加了coder字段标记编码格式。
    找目标String的时候,不能直接在内存里搜目标字符串的字节序列——堆里大量零散的内存碎片可能刚好凑出相同字节序列导致误判。正确的做法是先匹配对象头的类型指针,筛选出所有存活的String实例,再顺着实例里的数组引用找到对应的数组对象,校验数组长度、coder标记后,再比对数组内的内容是否和目标字符串一致,才能精确定位。
  3. 必须注意两个干扰项:
    • JVM垃圾回收会移动对象位置:扫描内存期间如果触发YGC/FGC,标记整理、复制算法会把存活对象挪到新的内存位置,你读到的可能是已经被回收、还没被覆盖的旧对象数据,不是当前存活的有效实例。
    • 开启压缩指针时,堆内存储的对象引用是经过编码的压缩地址,不是直接的进程虚拟地址,需要结合Java堆的基址做偏移换算才能拿到真实的内存地址。
常见理解疏漏说明

相关认知里有几个比较容易踩的误区:

  • 不要把JVM当成什么特殊的黑盒程序:它本质就是个普通的Linux用户态进程,所有用到的内存(堆、栈、元空间、堆外直接内存)都在进程的虚拟地址空间里,只要你有/proc/[pid]/mem的读权限,确实可以读到所有JVM内存里的原始数据,没有什么“JVM内存用户态读不到”的说法。
  • 不要误以为String对象本身存储字符串内容:String只是个持有数组引用的包装类,实际字符数据存在它引用的数组对象里,直接搜字符串内容很容易定位错位置。
  • 不要默认堆上对象的地址是固定不变的:除了不会被GC移动的对象,绝大多数普通对象的内存地址会随着垃圾回收动作发生变化,不存在固定的对象地址。
  • 不要混淆元空间和堆的存储内容:JDK8之后元空间只存类元数据、字节码等信息,intern字符串、静态变量都存在Java堆里,不在元空间范围内。

内容的提问来源于stack exchange,提问作者Ann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 02:57:13