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

【面试特集】JVM 内存与对象

JVM 内存与对象面试题

参见实体页:[[jdk8]]、[[jdk21]],概念页:[[gc-evolution]]


一、运行时数据区

Q1: JVM 内存模型(运行时数据区)?⭐⭐⭐

区域线程共享存储内容异常
堆(Heap)对象实例、数组OutOfMemoryError
方法区(Metaspace)类信息、常量、静态变量、JIT 代码OutOfMemoryError
虚拟机栈栈帧(局部变量表、操作数栈、动态链接、返回地址)StackOverflowError / OOM
本地方法栈Native 方法的栈帧StackOverflowError / OOM
程序计数器当前执行字节码的行号(Native 方法为 undefined)唯一不会 OOM 的区域

JDK 8 的变化:

  • 永久代(PermGen)→ 元空间(Metaspace)
  • Metaspace 使用本地内存(Native Memory),不再受-Xmx限制
  • 字符串常量池从方法区移到堆中

堆内存分代(G1 之前):

Young Generation(默认占堆 1/3)

Eden(默认 8/10)

Survivor0(默认 1/10)

Survivor1(默认 1/10)

Old Generation(默认占堆 2/3)

Q2: 栈帧的结构?⭐⭐

每个方法调用对应一个栈帧,包含:

局部变量表:

  • 存储方法参数和局部变量
  • 以 Slot(32位)为单位,long/double 占 2 个 Slot
  • 实例方法的 Slot[0] 是this

操作数栈:

  • 字节码指令的操作区域(JVM 是基于栈的虚拟机)
  • iadd指令:弹出两个 int,压入结果

动态链接:

  • 指向运行时常量池中该方法的符号引用
  • 支持多态(运行时解析为实际方法)

返回地址:

  • 正常返回:调用者的 PC 值
  • 异常返回:通过异常表确定

StackOverflowError 原因:

  • 递归调用过深,栈帧超过-Xss(默认 512KB-1MB)
  • 虚拟线程的栈更小(初始 ~1KB),但可动态扩展

二、对象的内存布局

Q3: 对象在堆中的内存布局?⭐⭐⭐

对象头(Object Header)
├── Mark Word(8 bytes):锁状态、GC年龄、hashCode
└── Klass Pointer(4/8 bytes):指向类元数据(开启压缩指针为4字节)

实例数据(Instance Data)
字段值(父类字段在前)

对齐填充(Padding)
保证对象大小是8字节的倍数

Mark Word 内容(64位,不同锁状态):

锁状态内容
无锁hashCode(31) + 分代年龄(4) + 偏向锁标志(1) + 锁标志(2)
偏向锁线程ID(54) + epoch(2) + 分代年龄(4) + 1 + 01
轻量级锁指向栈中锁记录的指针(62) + 00
重量级锁指向Monitor的指针(62) + 10
GC标记空(62) + 11

对象大小计算示例:

// 开启压缩指针(-XX:+UseCompressedOops,默认开启)classDemo{inta;// 4 byteslongb;// 8 bytesObjectc;// 4 bytes(压缩指针)}// 对象头:8(Mark Word)+ 4(Klass Pointer)= 12 bytes// 实例数据:4 + 8 + 4 = 16 bytes(字段重排序:long先,避免填充)// 对齐填充:0 bytes(12+16=28,需填充4字节到32)// 总计:32 bytes

工具:jol-core(Java Object Layout)可精确查看对象内存布局

System.out.println(ClassLayout.parseInstance(newDemo()).toPrintable());

Q4: 对象的创建过程?⭐⭐⭐

1. 类加载检查
检查 new 指令的参数是否能在常量池中找到类的符号引用
检查类是否已加载、解析、初始化,否则先执行类加载

2. 分配内存
指针碰撞(Bump the Pointer):堆内存规整时(Serial/ParNew),移动指针
空闲列表(Free List):堆内存不规整时(CMS),维护可用内存列表
线程安全:TLAB(Thread Local Allocation Buffer)优先,无锁分配

3. 初始化零值
将分配的内存空间初始化为零值(int=0, boolean=false, ref=null)
保证字段不赋初值也能使用

4. 设置对象头
填写 Mark Word(hashCode、GC年龄、锁状态)
填写 Klass Pointer(指向类元数据)

5. 执行 方法
按照程序员的意愿初始化(构造方法)

TLAB 细节:

  • 每个线程在 Eden 区预先申请一块私有缓冲区(默认 Eden 的 1%)
  • 线程内分配对象直接在 TLAB 上移动指针,无需同步
  • TLAB 满了才需要申请新的 TLAB(此时需要同步)

三、字符串与常量池

Q5: String 的内存模型?intern() 的作用?⭐⭐⭐

字符串常量池(String Pool):

  • JDK 7 前:在方法区(PermGen)
  • JDK 7+:移到堆中(避免 PermGen OOM)

字面量 vs new:

Strings1="hello";// 常量池中创建/复用Strings2="hello";// 复用常量池中的 "hello"Strings3=newString("hello");// 堆中新建对象,内容指向常量池s1==s2// true(同一常量池引用)s1==s3// false(s3 是堆中新对象)s1==s3.intern()// true(intern() 返回常量池中的引用)

intern() 原理(JDK 7+):

  • 如果常量池中已有该字符串:返回常量池中的引用
  • 如果没有:将堆中该字符串对象的引用放入常量池(不复制对象),返回该引用

String 不可变的原因:

  • char[]数组被private final修饰(JDK 9+ 改为byte[]
  • 没有提供修改数组内容的方法
  • 好处:线程安全、可缓存 hashCode、可作为 HashMap key

StringBuilder vs StringBuffer:

  • StringBuilder:非线程安全,单线程拼接用
  • StringBuffer:线程安全(方法加 synchronized),多线程用(实际很少用)
  • 循环拼接字符串必须用 StringBuilder,+在循环内会每次创建新对象

Q6: 为什么 JDK 9 将 String 的 char[] 改为 byte[]?⭐⭐

原因:大多数字符串只包含 Latin-1 字符(ASCII),每个字符只需 1 字节,但 char 占 2 字节,浪费内存。

Compact Strings(JDK 9):

  • 新增coder字段:LATIN1(0)UTF16(1)
  • Latin-1 字符串:每个字符 1 字节,内存减半
  • 含非 Latin-1 字符:退回 UTF-16(每字符 2 字节)
  • 对外 API 不变,性能基本持平

四、OOM 类型与排查

Q7: 各种 OOM 的原因和解决方案?⭐⭐⭐

1. Java heap space

java.lang.OutOfMemoryError: Java heap space
  • 原因:堆内存不足,对象无法分配
  • 排查:jmap -dump+ MAT 分析,找内存泄漏或大对象
  • 解决:增大-Xmx,或修复内存泄漏

2. GC overhead limit exceeded

java.lang.OutOfMemoryError: GC overhead limit exceeded
  • 原因:GC 时间超过 98%,但回收内存不足 2%(连续 5 次)
  • 本质:内存泄漏导致堆几乎全是存活对象
  • 解决:同上,找内存泄漏

3. Metaspace

java.lang.OutOfMemoryError: Metaspace
  • 原因:类加载过多(动态代理、CGLIB、Groovy 脚本热加载)
  • 排查:jcmd {pid} VM.class_stats查看类数量
  • 解决:-XX:MaxMetaspaceSize=512m,或排查类加载泄漏

4. Direct buffer memory

java.lang.OutOfMemoryError: Direct buffer memory
  • 原因:NIO DirectByteBuffer 分配的堆外内存超限
  • 解决:-XX:MaxDirectMemorySize=1g,或排查 NIO 使用

5. Unable to create new native thread

java.lang.OutOfMemoryError: unable to create new native thread
  • 原因:线程数超过 OS 限制(/proc/sys/kernel/threads-max
  • 解决:减少线程数(用线程池),或ulimit -u增大限制

6. StackOverflowError

  • 原因:递归过深,栈帧超过-Xss
  • 解决:增大-Xss(如-Xss2m),或优化递归为迭代

五、JVM 参数速查

Q8: 常用 JVM 参数?⭐⭐⭐

内存设置:

-Xms2g# 初始堆大小(建议与 Xmx 相同,避免动态扩容)-Xmx2g# 最大堆大小-Xmn512m# Young 区大小(G1 不建议设置)-Xss512k# 每个线程栈大小-XX:MetaspaceSize=256m# Metaspace 初始大小(触发 GC 的阈值)-XX:MaxMetaspaceSize=512m# Metaspace 最大大小-XX:MaxDirectMemorySize=1g# 堆外内存上限

GC 选择:

-XX:+UseG1GC# G1(JDK 9+ 默认)-XX:+UseZGC# ZGC(JDK 15+ 生产可用)-XX:+ZGenerational# 分代 ZGC(JDK 21+,推荐)-XX:+UseParallelGC# Parallel GC(高吞吐批处理)

诊断:

-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/tmp/heap.hprof -Xlog:gc*:file=/var/log/gc.log:time,uptime:filecount=5,filesize=20m-XX:+PrintCompilation# 打印 JIT 编译信息

容器环境:

-XX:+UseContainerSupport# 自动感知容器内存限制(JDK 8u191+,默认开启)-XX:MaxRAMPercentage=75.0# 使用容器内存的 75%(替代固定 -Xmx)-XX:InitialRAMPercentage=50.0# 初始堆占容器内存的 50%

六、应用场景总结

问题现象排查命令可能原因
堆内存持续增长jstat -gcutil {pid} 1000内存泄漏
Full GC 频繁jmap -histo {pid}老年代对象过多/大对象
Metaspace OOMjcmd {pid} VM.class_stats类加载泄漏
线程数暴涨jstack {pid} | grep "Thread" | wc -l线程池配置错误
CPU 飙升top -H -p {pid}+jstack死循环/频繁 GC
响应延迟高jstack {pid} | grep BLOCKED锁竞争/死锁
http://www.jsqmd.com/news/816056/

相关文章:

  • 替换背景颜色怎么操作?一文讲清各类工具的最佳方案
  • Linux平台专业图像编辑新选择:Photoshop CC 2022安装完全指南
  • 合宙Air001开发板Arduino环境搭建与实战指南
  • Logisim-evolution:数字逻辑电路设计的终极免费仿真工具,从零开始掌握计算机组成原理
  • 大彩串口屏工程下载全攻略:从SD卡到串口联机,避坑指南与故障排查
  • 告别Oh My Zsh!用Zim+Powerlevel10k打造你的极速高颜值终端(附Nerd Font配置)
  • 如何轻松下载B站4K大会员视频?完整开源工具使用指南
  • 为什么92%的NotebookLM用户3个月内弃用?资深知识工程师曝光5大致命配置误区
  • 如何完整备份微信聊天记录?这个开源工具让你永久保存珍贵对话
  • 吞吐量骤降42%?响应延迟飙升至8.3s!Claude 3 Opus在企业级API网关下的隐性性能陷阱,工程师必须今天排查
  • 专业级容器化部署指南:3步实现Argos Translate离线翻译服务现代化
  • 2026 年四川优选无人机培训机构推荐:想学无人机,这 3 家值得提前了解 - 品牌企业推荐师(官方)
  • 别再为Excel成绩排名发愁了!用SUMPRODUCT和COUNTIF搞定并列排名(附详细公式拆解)
  • 实时语音克隆项目上线前夜崩溃?ElevenLabs API错误码详解,47个HTTP状态码+12类Rate Limit触发场景一文归总
  • 基于Node.js的ChatGPT Telegram机器人部署与优化指南
  • eNSP实战:从零构建企业级DHCP网络服务
  • 用Python的keyboard库写个游戏外挂?手把手教你监听键盘实现自动化
  • 3步终极方案:在Mac上实现NTFS磁盘完整读写权限
  • 基于上下文感知的动态内容切换:从原理到实战实现
  • 用Python脚本玩转Windshaper API:自动化生成风切变、阵风,搞定无人机飞控极限测试
  • 终极窗口管理方案:如何用Traymond一键隐藏窗口到系统托盘?
  • 收藏!小白程序员也能抓住的AI风口红利:AI大模型应用开发入门指南
  • i.MX8M Plus嵌入式平台Qt 5.15.2交叉编译实战指南
  • I2C_硬件I2C1 控制0.96寸OLED显示
  • 组件拥有的数据 (Source of Truth)
  • 汽车无钥匙门禁系统设计:NXP方案、低功耗与安全实现详解
  • 抖音无水印视频下载终极指南:douyin-downloader 让批量下载变得如此简单
  • 配置OpenClaw使用Taotoken作为其大模型供应商的实践指南
  • 嵌入式工程师如何构建Linux与FPGA协同的π型技术栈
  • 微信聊天记录导出终极指南:5步永久保存你的珍贵对话