在现代计算机科学中控制逻辑与海量数据存储的解耦是一项经典的架构哲学。在JVMJava虚拟机的运行时数据区Runtime Data Area中这种哲学被具象化地体现为“虚拟机栈”与“堆”的分离。它们分别代表了Java程序的运行逻辑和数据存储。本文将基于JDK 1.8的最新演进深入拆解JVM的物理疆域并透视其背后高并发安全与极致性能的架构逻辑。一、 JVM物理疆域宏观全景JDK 1.8架构根据数据“是否被所有线程共享”JVM运行时数据区被严格划分为两大阵营线程私有区域随线程生灭无GC压力与线程共享区域GC的主战场高频OOM爆发地。运行时数据区宏观对比维度表对比维度程序计数器 (PC)虚拟机栈 (JVM Stack)本地方法栈 (Native Stack)堆 (Heap)方法区 / 元空间 (Metaspace)所属关系线程私有线程私有线程私有线程共享线程共享存储内容下一条字节码指令地址栈帧局部变量、方法参数等Native方法执行上下文对象实例、数组、字符串常量池类元数据、静态变量、类常量池生命周期与线程同生共死与线程同生共死与线程同生共死较长受GC生命周期管辖伴随整个应用运行或类卸载是否涉及GC否否方法结束自动释放否是GC的核心工作区是主要回收无用类和常量常见异常无任何异常StackOverflowErrorStackOverflowErrorOutOfMemoryError: Java heap spaceOutOfMemoryError: Metaspace二、 线程私有区域精细化工作台的微观解析线程私有区域是每个线程专属的“独立单间”在物理层面实现了数据的彻底隔离因而该区域完全不需要垃圾回收器GC的介入。1. 程序计数器 (PC Register) —— 线程的“GPS”核心作用程序计数器是一块极小的内存空间专门用来记录当前线程下一条将要执行的字节码指令的物理地址。多线程切换基座在CPU多线程时间片轮转切换后线程重新获取执行权时依赖程序计数器恢复到之前正确执行的代码位置。绝对安全它是整个JVM物理疆域中唯一一个在规范中绝对不会发生OutOfMemoryError的区域。2. 虚拟机栈 (JVM Stack) —— 方法执行的“工作台”虚拟机栈线程栈是Java方法执行的内存模型随线程的创建而分配。核心结构栈帧 (Stack Frame)一个线程中每次方法调用都会生成一个“栈帧”并压入栈中方法执行结束该栈帧会自动弹出并释放内存。虚拟机栈采用“先进后出”的活动结构任何时刻一个线程有且仅有一个位于栈顶的活动栈帧对应着当前正在执行的方法。栈帧存储内容局部变量表方法内部定义的局部变量。方法参数方法被调用时传入的入参。操作数栈方法执行过程中进行算术运算及入栈出栈的临时中转站。动态链接指向运行时常量池中该栈帧所属方法的符号引用支持多态特性。方法返回地址执行完毕后恢复上次调用现场并回到调用方代码的具体位置。 面试防坑栈存对象还是指针栈里仅存储基本数据类型和对象引用即指针地址真正的对象本体始终在堆里分配。局部变量表里的变量只是指向堆中对象的一个一根线。常见异常java.lang.StackOverflowError当方法递归调用过深例如死递归或缺乏正确的退出条件导致压入的栈帧数量超过了虚拟机允许的最大深度时就会爆发。3. 本地方法栈 (Native Method Stack)核心职责管理机制与虚拟机栈高度类似区别在于虚拟机栈为JVM执行Java字节码服务而本地方法栈是专门为JVM调用底层C/C实现的Native方法如System.currentTimeMillis()服务的。三、 线程共享区域公共大仓库与图纸库线程共享区域承载着系统运行的核心资产是多线程竞争交互的公共大仓库也是GC与OOM交织的最前线。1. 堆 (Heap) —— 对象的公共大仓库堆是JVM所管理的内存中最大的一块也是全线程共享的动态内存区。核心职责几乎所有通过new关键字创建的对象实例以及数组都必须在堆内存中分配空间。分代架构哲学基于对象生存周期的“弱代假说”绝大多数对象都是朝生夕死堆被宏观划分为两大分代年轻代 (Young Generation)约占堆空间的1/3。存放新创建的、生命周期较短的对象。内部进一步细分为Eden 区、Survivor 0 区 (S0)、Survivor 1 区 (S1)默认物理比例为8:1:1。这里会高频发生轻量级的垃圾回收Minor GC/Young GC。老年代 (Old Generation)约占堆空间的2/3。存放经过多次Minor GC依然存活的、生命周期较长的对象如长期使用的缓存、单例Service对象等。当老年代满时才会触发沉重的全局垃圾回收Full GC。大对象分配通道为了规避大对象如超大字节数组、长字符串在年轻代Eden和两个Survivor区之间来回复制产生高昂的内存拷贝成本JVM允许符合体积阈值由参数-XX:PretenureSizeThreshold控制的大对象直接绕过年轻代在老年代分配内存。常见异常当堆中存活对象过多且无法被GC清理导致无法为新对象分配足够空间时抛出java.lang.OutOfMemoryError: Java heap space。2. 方法区 (Method Area) 与元空间 (Metaspace) 的底层变革存储内容口诀“类、常、静、代”存储已被JVM加载的类元数据Class Metadata、运行时常量池Runtime Constant Pool、静态变量static、以及JIT编译器编译生成的本地机器代码。跨版本的底层架构进化JDK 1.7及以前方法区的物理实现在堆内存中被称为“永久代” (PermGen)。其大小固定极易因动态类加载过多而撑爆。JDK 1.8及以后彻底移除了永久代。方法区的实现变更为“元空间” (Metaspace)最核心的重构在于元空间被彻底移出JVM堆内存改用操作系统的本地物理内存 (Local Memory)。为什么要把方法区移出堆架构驱动因素现代微服务及企业级框架如 Spring、MyBatis大量采用动态代理、CGLIB 字节码增强等技术在系统运行时会动态生成成千上万个全新的类。永久代大小上限固定极易触发 Full GC 或直接导致OOM: PermGen space。改用元空间后其可用空间直接与操作系统的本地物理内存挂钩支持动态自动扩容彻底解除了类的元信息对JVM堆内存GC的绑架极大降低了 OOM 的风险。3. 深入探索String实例与字符串常量池的物理沉降大厂面试极其青睐考察字符串的物理存储位置其流转经历了多次版本演进对象本体通过new String(abc)创建的字符串对象实例始终分配在堆内存中。字符串常量池 (String Pool)字面量形式声明的字符串如abc其常量池在 JDK 1.7 之后已被从方法区移入了堆内存Heap中以便能更高效地接受堆内GC的扫描与回收。经典案例拆解String s new String(abc);到底创建了几个对象情况一若字面量常量池中此时没有abcJVM 会在堆内存的字符串常量池中创建一个abc对象实例。随后在堆内存常规区创建一个全新的new String(abc)对象本体。最后在虚拟机栈的局部变量表中压入变量s其指针指向堆中常规区的String对象。共创建 2 个对象情况二若字面量常量池中已经存在abcJVM 越过常量池创建步骤直接在堆内存常规区创建一个全新的new String(abc)对象。栈中的变量s指向该堆常规区对象。仅创建 1 个对象四、 核心探究JVM 为什么区分堆 (Heap) 与栈 (Stack)从设计哲学来看堆栈的分离绝非多此一举它是JVM兼顾极致性能与健壮安全的底层架构基石。1. 极致的性能优化动静分离栈负责程序的“运行逻辑”内部数据局部变量、方法参数生命周期极短随方法进出而生灭。栈的操作类似于 CPU 寄存器指针的碰撞移动其分配与释放效率为极致的 $O(1)$。而堆负责数据的“存储”专门管理复杂的、跨方法的、生命周期难以预期的对象实例。这种“动静分离”的设计使得栈可以保持极速响应。如果将短命的局部变量也丢进堆中接受垃圾回收器的扫描GC 的沉重负担将直接拖垮整机性能。2. 天然的并发安全数据隔离因为虚拟机栈是线程私有的每个线程都在独立的物理内存空间内创建栈帧并操作局部变量。这意味着局部变量在线程之间是天然物理隔离的。在没有发生引用逃逸的前提下局部变量天然具备线程安全性完全不需要使用synchronized等重型锁机制极大消除了并发编程的上下文切换开销。3. 优雅的故障隔离防止系统雪崩栈与堆拥有完全独立的物理边界和异常拦截机制。如果某个线程的代码发生了由于死递归引起的栈溢出 (StackOverflowError)JVM 只会强制终止并销毁当前报错线程的上下文而绝对不会污染共享的堆内存。此时系统其余 99% 的核心业务线程依然能平稳运行完美避免了单个小逻辑缺陷诱发整个操作系统进程瞬间雪崩的悲剧。4. 职责分明的管理机制栈的内存管理具有“完全确定性”由编译器和虚拟机指令集完成入栈即分配出栈即回收零人工算法干预。而堆的内存分配和回收具有“非确定性”专门由复杂的现代化垃圾回收器如 G1, ZGC通过分代或分区策略进行智能清理。这确保了不同性质的数据能采用最契合的资源分配手段实现了硬件资源配置的最优化。五、 经典结合场景与高频面试 QA 自测1. 经典结合场景一行代码看堆栈交互当我们在代码方法体内写下这一行最基础的代码时JavaObject obj new Object();虚拟机栈活动栈帧在当前方法的栈帧局部变量表中开辟空间存储一个名为obj的引用变量指针。堆 (Heap)在共享堆内存中开辟空间实例化出new Object()的真正对象数据本体。纽带连接栈中的obj变量存储着堆中该对象的首地址相当于一根无形的线精准穿透指向堆中的具体实例。 进阶思考大厂必考潜规则new出来的对象绝对都在堆里吗答案是不一定。 现代 JVM 引入了逃逸分析 (Escape Analysis)技术。如果 JIT 即时编译器分析出new Object()创建的对象在方法结束前绝对没有逃逸到外部方法或线程JVM 会打破规则通过标量替换手段将其打散成基本类型直接在虚拟机栈上分配内存对象随着方法弹栈直接消亡全程零 GC 压力。2. 线上排查必备四大常见 OOM 案发现场速查表当系统不幸爆出内存异常时准确的报错日志是进行“尸检”的唯一线索报错提示 (Error Message)定位引发区域核心诱发本质原因大厂真实排查 SOP / 解决方案java.lang.StackOverflowError虚拟机栈代码中存在没有正确退出条件的死递归或方法嵌套调用过深。检查业务代码中递归退出的判定逻辑若属于客观业务深可通过参数-Xss调大每个线程的栈空间。OutOfMemoryError: Java heap space堆内存一次性加载了海量数据到内存中如大批量查询未分页或内存泄漏导致历史大对象无法被GC回收。开启-XX:HeapDumpOnOutOfMemoryError自动保留 Dump 快照使用MAT 工具分析 GC Roots 引用链修补代码中的数据未卸载漏洞或将大List查询修改为分页查询。OutOfMemoryError: Metaspace元空间 / 方法区系统在运行时频繁使用了反射、CGLIB 动态代理等框架生成了过多的动态类。检查第三方框架生成的 Proxy 类是否未复用通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize调大元空间的物理边界。OutOfMemoryError: Direct buffer memory直接内存 (堆外)代码中大量使用了 Java NIO 的ByteBuffer.allocateDirect()申请堆外缓冲区但未及时手动解绑释放。检查 NIO 通道或 Netty 的内存释放机制确保直接缓冲区的虚引用垃圾回收器能正常回收通知释放。3. 高频八股文速记脑图附对象的四大引用类型为了更精准地控制堆中对象的生死栈中的引用变量对堆中对象持有四种完全不同的牵绊强度强引用 (Strong Reference)如Object obj new Object();。只要强引用链路还连着 GC Roots垃圾回收器绝对不回收它宁可抛出 OOM 导致程序挂掉也绝不清算。软引用 (Soft Reference)使用SoftReference包装。系统正常运行时不回收它仅在内存面临严重不足、即将触发 OOM 的最后关头GC 才会将其强制回收。极其适合作为图片/网页等非核心缓存。弱引用 (Weak Reference)使用WeakReference包装。它的生存期极短只要发生垃圾回收GC无视当前系统内存是否充裕统统立刻强制回收。经典实战包括ThreadLocal的 Key与WeakHashMap。虚引用 (Phantom Reference)形同虚设根本无法通过虚引用获取对象实例。其唯一宿命是在对象被 GC 清理时能把信息放进队列以收到一个系统通知常用于管理和安全释放堆外内存直接内存。
【JVM进阶与实战系列】篇一:JVM内存模型与动静分离架构(基石篇)
发布时间:2026/5/25 5:30:02
在现代计算机科学中控制逻辑与海量数据存储的解耦是一项经典的架构哲学。在JVMJava虚拟机的运行时数据区Runtime Data Area中这种哲学被具象化地体现为“虚拟机栈”与“堆”的分离。它们分别代表了Java程序的运行逻辑和数据存储。本文将基于JDK 1.8的最新演进深入拆解JVM的物理疆域并透视其背后高并发安全与极致性能的架构逻辑。一、 JVM物理疆域宏观全景JDK 1.8架构根据数据“是否被所有线程共享”JVM运行时数据区被严格划分为两大阵营线程私有区域随线程生灭无GC压力与线程共享区域GC的主战场高频OOM爆发地。运行时数据区宏观对比维度表对比维度程序计数器 (PC)虚拟机栈 (JVM Stack)本地方法栈 (Native Stack)堆 (Heap)方法区 / 元空间 (Metaspace)所属关系线程私有线程私有线程私有线程共享线程共享存储内容下一条字节码指令地址栈帧局部变量、方法参数等Native方法执行上下文对象实例、数组、字符串常量池类元数据、静态变量、类常量池生命周期与线程同生共死与线程同生共死与线程同生共死较长受GC生命周期管辖伴随整个应用运行或类卸载是否涉及GC否否方法结束自动释放否是GC的核心工作区是主要回收无用类和常量常见异常无任何异常StackOverflowErrorStackOverflowErrorOutOfMemoryError: Java heap spaceOutOfMemoryError: Metaspace二、 线程私有区域精细化工作台的微观解析线程私有区域是每个线程专属的“独立单间”在物理层面实现了数据的彻底隔离因而该区域完全不需要垃圾回收器GC的介入。1. 程序计数器 (PC Register) —— 线程的“GPS”核心作用程序计数器是一块极小的内存空间专门用来记录当前线程下一条将要执行的字节码指令的物理地址。多线程切换基座在CPU多线程时间片轮转切换后线程重新获取执行权时依赖程序计数器恢复到之前正确执行的代码位置。绝对安全它是整个JVM物理疆域中唯一一个在规范中绝对不会发生OutOfMemoryError的区域。2. 虚拟机栈 (JVM Stack) —— 方法执行的“工作台”虚拟机栈线程栈是Java方法执行的内存模型随线程的创建而分配。核心结构栈帧 (Stack Frame)一个线程中每次方法调用都会生成一个“栈帧”并压入栈中方法执行结束该栈帧会自动弹出并释放内存。虚拟机栈采用“先进后出”的活动结构任何时刻一个线程有且仅有一个位于栈顶的活动栈帧对应着当前正在执行的方法。栈帧存储内容局部变量表方法内部定义的局部变量。方法参数方法被调用时传入的入参。操作数栈方法执行过程中进行算术运算及入栈出栈的临时中转站。动态链接指向运行时常量池中该栈帧所属方法的符号引用支持多态特性。方法返回地址执行完毕后恢复上次调用现场并回到调用方代码的具体位置。 面试防坑栈存对象还是指针栈里仅存储基本数据类型和对象引用即指针地址真正的对象本体始终在堆里分配。局部变量表里的变量只是指向堆中对象的一个一根线。常见异常java.lang.StackOverflowError当方法递归调用过深例如死递归或缺乏正确的退出条件导致压入的栈帧数量超过了虚拟机允许的最大深度时就会爆发。3. 本地方法栈 (Native Method Stack)核心职责管理机制与虚拟机栈高度类似区别在于虚拟机栈为JVM执行Java字节码服务而本地方法栈是专门为JVM调用底层C/C实现的Native方法如System.currentTimeMillis()服务的。三、 线程共享区域公共大仓库与图纸库线程共享区域承载着系统运行的核心资产是多线程竞争交互的公共大仓库也是GC与OOM交织的最前线。1. 堆 (Heap) —— 对象的公共大仓库堆是JVM所管理的内存中最大的一块也是全线程共享的动态内存区。核心职责几乎所有通过new关键字创建的对象实例以及数组都必须在堆内存中分配空间。分代架构哲学基于对象生存周期的“弱代假说”绝大多数对象都是朝生夕死堆被宏观划分为两大分代年轻代 (Young Generation)约占堆空间的1/3。存放新创建的、生命周期较短的对象。内部进一步细分为Eden 区、Survivor 0 区 (S0)、Survivor 1 区 (S1)默认物理比例为8:1:1。这里会高频发生轻量级的垃圾回收Minor GC/Young GC。老年代 (Old Generation)约占堆空间的2/3。存放经过多次Minor GC依然存活的、生命周期较长的对象如长期使用的缓存、单例Service对象等。当老年代满时才会触发沉重的全局垃圾回收Full GC。大对象分配通道为了规避大对象如超大字节数组、长字符串在年轻代Eden和两个Survivor区之间来回复制产生高昂的内存拷贝成本JVM允许符合体积阈值由参数-XX:PretenureSizeThreshold控制的大对象直接绕过年轻代在老年代分配内存。常见异常当堆中存活对象过多且无法被GC清理导致无法为新对象分配足够空间时抛出java.lang.OutOfMemoryError: Java heap space。2. 方法区 (Method Area) 与元空间 (Metaspace) 的底层变革存储内容口诀“类、常、静、代”存储已被JVM加载的类元数据Class Metadata、运行时常量池Runtime Constant Pool、静态变量static、以及JIT编译器编译生成的本地机器代码。跨版本的底层架构进化JDK 1.7及以前方法区的物理实现在堆内存中被称为“永久代” (PermGen)。其大小固定极易因动态类加载过多而撑爆。JDK 1.8及以后彻底移除了永久代。方法区的实现变更为“元空间” (Metaspace)最核心的重构在于元空间被彻底移出JVM堆内存改用操作系统的本地物理内存 (Local Memory)。为什么要把方法区移出堆架构驱动因素现代微服务及企业级框架如 Spring、MyBatis大量采用动态代理、CGLIB 字节码增强等技术在系统运行时会动态生成成千上万个全新的类。永久代大小上限固定极易触发 Full GC 或直接导致OOM: PermGen space。改用元空间后其可用空间直接与操作系统的本地物理内存挂钩支持动态自动扩容彻底解除了类的元信息对JVM堆内存GC的绑架极大降低了 OOM 的风险。3. 深入探索String实例与字符串常量池的物理沉降大厂面试极其青睐考察字符串的物理存储位置其流转经历了多次版本演进对象本体通过new String(abc)创建的字符串对象实例始终分配在堆内存中。字符串常量池 (String Pool)字面量形式声明的字符串如abc其常量池在 JDK 1.7 之后已被从方法区移入了堆内存Heap中以便能更高效地接受堆内GC的扫描与回收。经典案例拆解String s new String(abc);到底创建了几个对象情况一若字面量常量池中此时没有abcJVM 会在堆内存的字符串常量池中创建一个abc对象实例。随后在堆内存常规区创建一个全新的new String(abc)对象本体。最后在虚拟机栈的局部变量表中压入变量s其指针指向堆中常规区的String对象。共创建 2 个对象情况二若字面量常量池中已经存在abcJVM 越过常量池创建步骤直接在堆内存常规区创建一个全新的new String(abc)对象。栈中的变量s指向该堆常规区对象。仅创建 1 个对象四、 核心探究JVM 为什么区分堆 (Heap) 与栈 (Stack)从设计哲学来看堆栈的分离绝非多此一举它是JVM兼顾极致性能与健壮安全的底层架构基石。1. 极致的性能优化动静分离栈负责程序的“运行逻辑”内部数据局部变量、方法参数生命周期极短随方法进出而生灭。栈的操作类似于 CPU 寄存器指针的碰撞移动其分配与释放效率为极致的 $O(1)$。而堆负责数据的“存储”专门管理复杂的、跨方法的、生命周期难以预期的对象实例。这种“动静分离”的设计使得栈可以保持极速响应。如果将短命的局部变量也丢进堆中接受垃圾回收器的扫描GC 的沉重负担将直接拖垮整机性能。2. 天然的并发安全数据隔离因为虚拟机栈是线程私有的每个线程都在独立的物理内存空间内创建栈帧并操作局部变量。这意味着局部变量在线程之间是天然物理隔离的。在没有发生引用逃逸的前提下局部变量天然具备线程安全性完全不需要使用synchronized等重型锁机制极大消除了并发编程的上下文切换开销。3. 优雅的故障隔离防止系统雪崩栈与堆拥有完全独立的物理边界和异常拦截机制。如果某个线程的代码发生了由于死递归引起的栈溢出 (StackOverflowError)JVM 只会强制终止并销毁当前报错线程的上下文而绝对不会污染共享的堆内存。此时系统其余 99% 的核心业务线程依然能平稳运行完美避免了单个小逻辑缺陷诱发整个操作系统进程瞬间雪崩的悲剧。4. 职责分明的管理机制栈的内存管理具有“完全确定性”由编译器和虚拟机指令集完成入栈即分配出栈即回收零人工算法干预。而堆的内存分配和回收具有“非确定性”专门由复杂的现代化垃圾回收器如 G1, ZGC通过分代或分区策略进行智能清理。这确保了不同性质的数据能采用最契合的资源分配手段实现了硬件资源配置的最优化。五、 经典结合场景与高频面试 QA 自测1. 经典结合场景一行代码看堆栈交互当我们在代码方法体内写下这一行最基础的代码时JavaObject obj new Object();虚拟机栈活动栈帧在当前方法的栈帧局部变量表中开辟空间存储一个名为obj的引用变量指针。堆 (Heap)在共享堆内存中开辟空间实例化出new Object()的真正对象数据本体。纽带连接栈中的obj变量存储着堆中该对象的首地址相当于一根无形的线精准穿透指向堆中的具体实例。 进阶思考大厂必考潜规则new出来的对象绝对都在堆里吗答案是不一定。 现代 JVM 引入了逃逸分析 (Escape Analysis)技术。如果 JIT 即时编译器分析出new Object()创建的对象在方法结束前绝对没有逃逸到外部方法或线程JVM 会打破规则通过标量替换手段将其打散成基本类型直接在虚拟机栈上分配内存对象随着方法弹栈直接消亡全程零 GC 压力。2. 线上排查必备四大常见 OOM 案发现场速查表当系统不幸爆出内存异常时准确的报错日志是进行“尸检”的唯一线索报错提示 (Error Message)定位引发区域核心诱发本质原因大厂真实排查 SOP / 解决方案java.lang.StackOverflowError虚拟机栈代码中存在没有正确退出条件的死递归或方法嵌套调用过深。检查业务代码中递归退出的判定逻辑若属于客观业务深可通过参数-Xss调大每个线程的栈空间。OutOfMemoryError: Java heap space堆内存一次性加载了海量数据到内存中如大批量查询未分页或内存泄漏导致历史大对象无法被GC回收。开启-XX:HeapDumpOnOutOfMemoryError自动保留 Dump 快照使用MAT 工具分析 GC Roots 引用链修补代码中的数据未卸载漏洞或将大List查询修改为分页查询。OutOfMemoryError: Metaspace元空间 / 方法区系统在运行时频繁使用了反射、CGLIB 动态代理等框架生成了过多的动态类。检查第三方框架生成的 Proxy 类是否未复用通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize调大元空间的物理边界。OutOfMemoryError: Direct buffer memory直接内存 (堆外)代码中大量使用了 Java NIO 的ByteBuffer.allocateDirect()申请堆外缓冲区但未及时手动解绑释放。检查 NIO 通道或 Netty 的内存释放机制确保直接缓冲区的虚引用垃圾回收器能正常回收通知释放。3. 高频八股文速记脑图附对象的四大引用类型为了更精准地控制堆中对象的生死栈中的引用变量对堆中对象持有四种完全不同的牵绊强度强引用 (Strong Reference)如Object obj new Object();。只要强引用链路还连着 GC Roots垃圾回收器绝对不回收它宁可抛出 OOM 导致程序挂掉也绝不清算。软引用 (Soft Reference)使用SoftReference包装。系统正常运行时不回收它仅在内存面临严重不足、即将触发 OOM 的最后关头GC 才会将其强制回收。极其适合作为图片/网页等非核心缓存。弱引用 (Weak Reference)使用WeakReference包装。它的生存期极短只要发生垃圾回收GC无视当前系统内存是否充裕统统立刻强制回收。经典实战包括ThreadLocal的 Key与WeakHashMap。虚引用 (Phantom Reference)形同虚设根本无法通过虚引用获取对象实例。其唯一宿命是在对象被 GC 清理时能把信息放进队列以收到一个系统通知常用于管理和安全释放堆外内存直接内存。