当前位置: 首页 > web >正文

深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第二章知识点问答(21题)

1 运行时数据区域总览(名称 → 私有/共享 → 典型异常)

  • 程序计数器(PC) → 线程私有 → (规范未定义 OOME);记录下一条将执行的字节码指令地址/偏移(执行 native 时可能无意义)。
  • 虚拟机栈 → 线程私有StackOverflowError(栈深超限);OutOfMemoryError(需要扩栈但内存不足);一帧=局部变量表/操作数栈/动态链接/返回地址
  • 本地方法栈 → 线程私有 → 同上两类异常;为 native 方法服务。
  • 共享OutOfMemoryError: Java heap space(或 GC overhead limit exceeded);存放对象实例/数组,GC 主要管理区域。
  • 方法区(HotSpot: Metaspace)共享OutOfMemoryError: Metaspace;承载类元数据/运行时常量池/静态方法信息(JIT 代码在 Code Cache)。
  • 运行时常量池共享(属方法区一部分)→ OutOfMemoryError(常伴随 constant pool 报文);存字面量/符号引用等。
  • 直接内存(NIO DirectBuffer) → 进程级共享(堆外)→ OutOfMemoryError: Direct buffer memoryByteBuffer.allocateDirect()/mmap 用于少拷贝 I/O

2 栈错误对比:StackOverflowError vs 栈相关 OutOfMemoryError

  • 触发

    • SOE:深递归/极大方法嵌套导致栈深超限(受 -Xss 影响)。
    • OOME(栈)创建线程失败(OS 线程/内存/进程限制)或单线程 -Xss 过大导致总内存不足(常见报文 unable to create native thread)。
  • 复现

    • SOE:小栈 -Xss256k + 递归方法死递归;
    • OOME(栈)-Xss8m + 循环创建休眠线程直到失败。
  • 排查

    • 看异常栈&-Xss
    • jcmd <pid> Thread.print 看线程数/状态,核对 OS/容器限制(ulimit -u)。

3 四类 OOM 的触发条件与首条线索

  • Java heap space:堆无法分配新对象(泄漏/短时分配暴涨/堆太小)→ 首看 GC 日志heap dump
  • GC overhead limit exceeded大部分时间在 GC,回收极少(近似 98%/2%)→ 首看 GC 日志确认症状,根因仍指向堆紧张
  • Metaspace:类元数据空间耗尽(动态生类/类加载器泄漏)→ 先看 NMTclass loader stats
  • Direct buffer memory:直接内存超上限或未释放 → 查 -XX:MaxDirectMemorySizeallocateDirect 使用点与 NMTNIO 类别。

4 如何可靠复现 Metaspace OOM(思路)

  • 做法:用 ByteBuddy/CGLIB/ASM 不断生成新类;每批使用新 ClassLoader 并把 Loader 放入 static List 强引用防卸载;运行时加 -XX:MaxMetaspaceSize=64m
  • 第一条排查线索OOME: Metaspace 报错出现后,立刻 jcmd <pid> VM.native_memory summaryjcmd <pid> GC.class_stats / VM.class_loader_stats
  • 防护:① 复用 Loader/卸载前清所有强引用(含 TCCL/缓存/ThreadLocal);② 设上限并监控 NMT 指标。

5 运行时常量池 vs 字符串常量池(StringTable)

  • 位置/时机

    • 运行时常量池:在方法区(JDK8+ 为 Metaspace);随类加载/链接建立。
    • 字符串常量池JDK7+ 在堆(JDK6 在 PermGen);JVM 启动即有,运行期通过字面量/intern() 填充。
  • 典型 OOM

    • 常量池:OOME: Metaspace(或常量池相关文案)。
    • 字符串池(堆):OOME: Java heap space(或 GC overhead)。
  • 示例(施压 StringTable)

    while (true) UUID.randomUUID().toString().intern();
    

6 new 对象的关键流程(6 步)

  1. 类可用性检查:未加载/未初始化则触发 加载→链接(验证/准备/解析)→初始化
  2. 分配:优先在 TLAB(线程本地)用指针碰撞;不够时到共享堆用 CAS/加锁
  3. 清零:实例字段所在内存全部置 0(得默认值)。
  4. 对象头:写入 Mark Word(哈希/锁标志/偏向/年龄)与 Klass 指针(数组还写长度)。
  5. 执行 <init>:按继承层级构造;引用写入触发写屏障维护卡表。
  6. 失败分支:分配失败→尝试 GC/扩堆;仍失败→ OOME: Java heap space

7 新生代/晋升/G1&ZGC

  • Eden / S0 / S1:Young GC 时将 Eden+from 的幸存者复制到 to,对象年龄+1,然后 from/to 互换
  • 三条晋升路径:① 到达 MaxTenuringThreshold;② 动态年龄判定(某年龄及以上占 Survivor 超阈值→直晋升);③ 大对象直接进老年代/巨型区(Parallel 的 PretenureSizeThresholdG1 Humongous ≥ 半个 Region)。
  • G1:Region 化 + 分代(Young/Mixed GC),复制晋升;Humongous 用一组连续 Region(通常计入 Old)。
  • ZGC:早期不分代,用染色指针+读屏障并发重定位;JDK 21+ 可开启 -XX:+ZGenerational 引入分代,仍保持低停顿。

8 OOM ↔ 关键参数/限制配对

  1. Java heap space-Xmx/-Xms(堆不足);先看 GC 日志/heap dump。
  2. GC overhead limit exceeded → 根因仍是 -Xmx 紧张(可临时 -XX:-UseGCOverheadLimit 获取证据)。
  3. Metaspace-XX:MaxMetaspaceSize(类元空间不足);看 NMT/类加载器。
  4. Direct buffer memory-XX:MaxDirectMemorySize(直接内存上限);看 NMT NIO
  5. unable to create native thread → OS/容器线程&内存限制(亦受 -Xss 影响)。

9 Heap OOM 的最小闭环(三步)

  1. 留证-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/heap.hprof -Xlog:gc*:file=/var/gc.log; 必要时 jcmd <pid> GC.heap_dump /tmp/heap.hprof
  2. 定位:MAT 看 Leak Suspects / Dominator Tree / Path to GC Roots;结合 GC 日志判断“泄漏”vs“分配暴涨”。
  3. 处置:短期限流/减并发/小幅调 Xmx、收紧缓存;长期修引用链/类加载器/缓存策略并加 JFR/GC/NMT 监控。

10(可选延伸)Safepoint 与“进入慢”

  • 定义:在 GC/类卸载/去优化等全局操作前,JVM 要求所有 Java 线程停在带轮询的安全点,以便一致地枚举根并扫描
  • 进入慢的常见原因:① 紧凑长循环/大方法缺少计数 Safepoint;② JNI Critical/长时间阻塞 I/O导致线程久不回到轮询点。
  • 对策:开 -Xlog:safepoint-XX:+PrintSafepointStatistics 定位;拆循环/开启 -XX:+UseCountedLoopSafepoints、缩短 JNI 临界区/改为可中断阻塞。

11 String 不可变性的利与弊 & 高拼接实践

  • :可放入常量池、可缓存 hashCode、天然线程安全,适合做 Map Key/跨线程共享。
  • :拼接/替换会生成新对象,循环中易分配放大→ GC 压力。
  • 实践:循环中用 StringBuilder(预估容量);批量拼接用 StringJoiner/Collectors.joining;流式写入 StringWriter/BufferedWriter慎用 intern() 处理动态值。

12 对象内存布局 & 访问方式

  • 布局对象头(Mark Word + Klass 指针;数组还有长度)+ 实例数据(父类→子类,遵循对齐)+ 对齐填充(HotSpot 默认8 字节对齐)。
  • Mark Word 含义:哈希、锁状态/偏向信息、GC 年龄等;64 位下开 Compressed Oops/Class Pointers32 位编码引用,降低内存占用/提高缓存友好。
  • 访问方式句柄(引用→句柄表项→{对象地址, 类型元数据地址},移动对象只改句柄,多一次间接);直接指针(HotSpot 采用,更快,移动需更新引用)。

13 ThreadLocal 为何“看起来泄漏” & 防范

  • 原因ThreadLocalMapkey(ThreadLocal)是弱引用value 是强引用;当 key 被 GC 回收后,value 仍被线程强引用着,线程池中线程长寿导致 value 长期不清。
  • 防范:① try{ set } finally{ remove(); };② 线程池统一清理(装饰 afterExecute)并避免放大对象;必要时禁用/谨慎用 InheritableThreadLocal

14 用 NMT 排查 Metaspace/Direct Memory

  1. 启动-XX:NativeMemoryTracking=summary(或 detail)可选 -XX:+PrintNMTStatistics

  2. 在线

    jcmd <pid> VM.native_memory baseline
    jcmd <pid> VM.native_memory summary.scale=MB
    jcmd <pid> VM.native_memory detail.diff
    

    关注:Class(=Metaspace)NIO(=Direct)、Thread/Code/Internal/Symbol。

  3. 第一动作:Metaspace 涨→看 class_loader_stats/动态生类点;Direct 涨→核对 MaxDirectMemorySize,定位 allocateDirect 持有链(Netty 可开 leakDetector)。


15 直接内存 vs 堆(实务四句)

  1. 管理者:堆→GC;直接内存→DirectByteBufferCleaner/Unsafe 释放,最终由 OS 回收。
  2. 场景:堆→业务对象/集合;直接内存→NIO/Netty/mmap 减少拷贝。
  3. 参数:堆→-Xms/-Xmx;直接内存→-XX:MaxDirectMemorySize
  4. 首证据:堆→OOME: Java heap space + GC 日志/heap dump;直接内存→OOME: Direct buffer memory + NMT NIO

16 PermGen → Metaspace(JDK8 迁移)

  • 差异:**PermGen(JDK8 前,堆内)**由 -XX:PermSize/MaxPermSize 控制;**Metaspace(JDK8+ 本地内存)**默认随系统增长,用 -XX:MaxMetaspaceSize 控制。
  • 典型 OOMOOME: PermGen space(旧) / OOME: Metaspace(新)。
  • 遇到 Metaspace OOM 首动:看 NMTclass loader stats,确认是否类加载器泄漏/动态生类过多。

17 任意 OOM 的“最小证据包”(5 条)

  1. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/heap.hprof —— OOM 自动落地 dump
  2. -Xlog:gc*:file=/var/gc.log:time,uptime,tags —— 持久化 GC 证据
  3. jcmd <pid> GC.heap_dump /tmp/heap.hprof —— 在线抓堆
  4. jcmd <pid> VM.native_memory summary.scale=MB —— 看本地内存分类
  5. jcmd <pid> Thread.print —— 线程快照(线程 OOME/卡死)

18 用 MAT 看 dump 的三步法

  1. Leak Suspects:看是否存在可疑泄漏链/最大保留大小持有者。
  2. Dominator Tree / Top Consumers:按Retained Size 找“内存大户”(集合/缓存/字节数组)。
  3. Histogram + Path to GC Roots:锁定异常类型后,沿强引用链/静态字段/ThreadLocal 找根因。

19 TLAB(线程本地分配缓冲)

  • 作用/好处:线程私有指针碰撞快速分配,无锁/无 CAS;提升缓存局部性、降低竞争。
  • 代价/局限内部碎片(零头浪费)、申请/回收 TLAB 的额外开销。
  • 参数/观察-XX:+/-UseTLAB-Xlog:gc+tlab=info-XX:+ResizeTLAB-XX:TLABSize
  • 回退场景:对象太大当前 TLAB 剩余不足→到共享 Eden 分配/申请新 TLAB;G1 的巨型对象可能绕过新生代。

20 方法区/常量池/字符串池的位置变迁(JDK6 → 7 → 8)

  • JDK6:方法区=PermGen(堆内)运行时常量池/字符串常量池在 PermGen;报错 OOME: PermGen space;调参 -XX:MaxPermSize
  • JDK7:仍有 PermGen,但字符串常量池迁至堆;堆压力增大可能导致 heap OOM
  • JDK8:移除 PermGen,用 Metaspace(本地内存)运行时常量池随类元数据在 Metaspace,字符串常量池在堆;报错 OOME: Metaspace;调参 -XX:MaxMetaspaceSize

21 遇到 OOM 的标准操作流程(6 步)

  1. 启动就留证:开启 HeapDumpOnOOMEGC 日志
  2. 线上取证jcmd ... GC.heap_dump / VM.native_memory / Thread.print,必要时开 JFR 5–10 分钟
  3. 快速研判:结合 OOME 文案/NMT 类别判断 heap / metaspace / direct / threads
  4. 离线定位:MAT 看 Leak Suspects → Dominator Tree → Path to GC Roots;若是 metaspace/direct,看 class loader/NIO
  5. 短期止血:限流降并发/缩小批量;谨慎小幅-Xmx;收紧缓存;保留证据后重启。
  6. 长期治理:修复引用链/类加载器/缓存策略;加 JFR/GC/NMT 周期快照与告警。
http://www.xdnf.cn/news/18530.html

相关文章:

  • 效果驱动复购!健永科技RFID牛场智能称重项目落地
  • AI资深 Java 研发专家系统解析Java 中常见的 Queue实现类
  • 手机惊魂
  • MySQL高可用之MHA
  • 【智慧城市】2025年中国地质大学(武汉)暑期实训优秀作品(1):智绘旅程构建文旅新基建
  • 稀土元素带来农业科技革命
  • 哈尔滨服务器托管,如何实现高效稳定运行?
  • OBCP第四章 OceanBase SQL 调优学习笔记:通俗解读与实践指南
  • comfyUI背后的一些技术——Checkpoints
  • React:Umi + React + Ant Design Pro的基础上接入Mock数据
  • Unity编辑器相关
  • 基于STM32设计的大棚育苗管理系统(4G+华为云IOT)_265
  • RabbitMQ:技巧汇总
  • 如何用 SolveigMM Video Splitter 从视频中提取 AAC 音频
  • leetcode_238 除自身以外的数组乘积
  • 实践题:智能客服机器人设计
  • 【Dify(v1.x) 核心源码深入解析】prompt 模块
  • centos下安装Nginx(搭建高可用集群)
  • 利用随机森林筛查 “癌症点”
  • yggjs_react使用教程 v0.1.1
  • Excel中运行VB的函数
  • 自然处理语言NLP:One-Hot编码、TF-IDF、词向量、NLP特征输入、EmbeddingLayer实现、word2vec
  • Docker安装elasticsearch以及Kibana、ik分词器
  • Day24 目录遍历、双向链表、栈
  • k8s集合
  • GIS在城乡供水一体化中的应用
  • CT02-20.有效的括号(Java)
  • Flutter 线程模型详解:主线程、异步与 Isolate
  • 机器学习中的两大核心算法:k 均值聚类与集成学习
  • Linux之Ansible自动化运维(二)