JVM整体架构
JVM(Java Virtual Machine)是Java程序运行的核心环境,主要由以下几个部分组成:
1. 程序计数器(Program Counter)
- 特点:线程私有,每个线程都有独立的程序计数器
- 作用:记录当前线程正在执行的字节码指令地址(行号)
- 重要性:支持线程切换和恢复,确保程序能够正确执行
2. 虚拟机栈(JVM Stack)
- 特点:线程私有,采用先进后出(LIFO)的数据结构
- 组成:由多个栈帧组成,每个方法调用创建一个栈帧
- 栈帧内容:局部变量表、操作数栈、动态链接、方法返回地址
虚拟机栈常见问题
垃圾回收是否涉及栈内存?
不涉及。垃圾回收主要针对堆内存,栈帧在方法执行完毕后自动弹出释放内存。
栈内存分配越大越好吗?
不是。默认栈内存通常为1024KB,过大会导致可创建的线程数减少。例如:512MB总内存,1024KB栈大小可创建512个线程,改为2048KB则只能创建256个线程。
方法内的局部变量是否线程安全?
- 如果局部变量没有逃离方法作用范围,则线程安全
- 如果局部变量引用了对象并逃离方法作用范围,需要考虑线程安全问题
栈内存溢出情况
- 栈帧过多导致溢出(典型:无限递归调用)
- 单个栈帧过大导致溢出
3. 堆内存(Heap)
- 特点:线程共享区域,JVM中最大的内存区域
- 作用:存储对象实例和数组
- 异常:当堆内存不足时抛出OutOfMemoryError异常
- 分代结构:通常分为年轻代(Eden、Survivor)和老年代
4. 方法区(Method Area)
- 特点:线程共享的内存区域
- 存储内容:类信息、运行时常量池、静态变量、编译后的代码
- 生命周期:虚拟机启动时创建,关闭时释放
- 异常:内存不足时抛出OutOfMemoryError: Metaspace
JDK版本差异
JDK 1.7 vs 1.8的重要变化:
- JDK 1.7:存在永久代(PermGen),存储类信息、静态变量、常量等
- JDK 1.8:移除永久代,改为元空间(Metaspace),使用本地内存,有效防止内存溢出
5. 直接内存(Direct Memory)
- 特点:不属于JVM内存结构,不由JVM管理
- 本质:使用系统内存,常见于NIO操作
- 特性:分配回收成本较高,但读写性能优秀
- 用途:主要用作数据缓冲区
内存管理要点
垃圾回收机制
- 主要针对堆内存进行回收
- 栈内存随方法调用结束自动释放
- 方法区在JDK 1.8后使用元空间,减少OOM风险
线程安全考虑
- 程序计数器、虚拟机栈:线程私有,天然线程安全
- 堆内存、方法区:线程共享,需要考虑并发访问问题
- 直接内存:需要程序员手动管理,注意内存泄漏
面试重点
- 内存区域划分:能清楚说明各个内存区域的作用和特点
- 线程安全性:理解哪些区域是线程私有的,哪些是共享的
- 内存溢出:了解各种OutOfMemoryError的产生原因和解决方案
- JDK版本差异:特别是永久代到元空间的变化
- 性能调优:理解不同内存区域对性能的影响
补充知识点
常见JVM参数
-Xms
:初始堆大小-Xmx
:最大堆大小-Xss
:栈大小-XX:MetaspaceSize
:元空间初始大小-XX:MaxMetaspaceSize
:元空间最大大小
内存泄漏常见场景
- 长生命周期对象持有短生命周期对象的引用
- 静态集合类引用大量对象
- 监听器和回调函数未正确移除
- 线程池中的线程未正确关闭