JVM学习笔记
一、JVM基础概念
1.1 JVM定义与作用
JVM(Java Virtual Machine,Java虚拟机)是运行Java字节码的虚拟计算机,它屏蔽了不同操作系统的底层差异,实现了“一次编写,到处运行”的Java核心特性。JVM的核心作用包括:加载解析字节码、管理内存、执行字节码、处理异常、垃圾回收等。
JVM与操作系统、硬件的关系:Java源码(.java)经编译器(javac)编译为字节码文件(.class),JVM负责将字节码翻译为本地机器指令(不同OS对应不同JVM实现,如HotSpot、J9等),最终由硬件执行。
1.2 JVM架构核心组成
JVM架构主要分为五大核心模块,各模块协同工作完成字节码的执行与资源管理:
类加载器子系统(ClassLoader Subsystem):负责加载.class文件到内存,完成“加载-链接-初始化”三个阶段。核心组件包括引导类加载器(Bootstrap ClassLoader,加载JDK核心类库,如rt.jar)、扩展类加载器(Extension ClassLoader,加载jre/lib/ext目录下的类库)、应用程序类加载器(Application ClassLoader,加载用户应用程序的类),以及自定义类加载器(继承ClassLoader实现)。
运行时数据区(Runtime Data Area):JVM在运行时分配的内存区域,也是面试核心考点,分为线程共享区和线程私有区:
线程共享区(所有线程共用,随JVM启动/退出创建/销毁):方法区(Method Area,存储类元信息、常量、静态变量、即时编译后的代码等,JDK8后由元空间Metaspace实现,物理内存分配,无OOM上限)、堆(Heap,存储对象实例和数组,是垃圾回收的主要区域,分为年轻代[Eden区、Survivor0区、Survivor1区]和老年代)。线程私有区(每个线程独立拥有,随线程创建/销毁):程序计数器(Program Counter Register,记录当前线程执行的字节码指令地址,无OOM)、虚拟机栈(VM Stack,存储方法调用的栈帧,每个栈帧包含局部变量表、操作数栈、动态链接、方法出口等,栈深度不足会抛出StackOverflowError,内存不足会抛出OutOfMemoryError)、本地方法栈(Native Method Stack,为本地方法[如Object.wait()]提供内存空间,与虚拟机栈功能类似,也可能抛出StackOverflowError和OutOfMemoryError)。
执行引擎(Execution Engine):将字节码翻译为本地机器指令并执行,核心组件包括解释器(Interpreter,逐行解释字节码,启动快但执行慢)、即时编译器(JIT Compiler,将频繁执行的热点代码编译为本地机器码,执行快但编译耗时),以及垃圾回收器(Garbage Collector,负责回收堆和方法区中无引用的对象,释放内存)。
本地方法接口(Native Method Interface,JNI):提供Java程序调用本地方法(C/C++编写)的接口,用于与操作系统底层交互(如文件IO、网络通信等)。
本地方法库(Native Method Library):存储本地方法的实现,供JNI调用。
1.3 类加载机制
类加载机制是指将.class文件加载到内存,并对其进行验证、准备、解析、初始化,最终形成可使用的类对象的过程,核心遵循“双亲委派模型”:
加载(Loading):通过类的全限定名(如java.lang.String)找到对应的.class文件,将其字节流读取到内存,生成一个代表该类的Class对象(存储在方法区)。
链接(Linking):分为三个阶段:
验证(Verification):检查.class文件的字节流是否符合JVM规范(如文件格式验证、元数据验证、字节码验证、符号引用验证),防止恶意字节码攻击。准备(Preparation):为类的静态变量分配内存,并设置默认初始值(如int默认0、boolean默认false、引用类型默认null),不包含实例变量(实例变量在对象创建时分配在堆中)。
解析(Resolution):将类中的符号引用(如类名、方法名、字段名)转换为直接引用(内存地址)。
初始化(Initialization):执行类的静态代码块(static{})和静态变量的显式赋值语句,初始化顺序遵循“父类优先于子类、静态变量按声明顺序、静态代码块按顺序执行”。
双亲委派模型:当一个类加载器收到类加载请求时,首先委托给父类加载器加载,只有父类加载器无法加载(找不到对应的.class文件)时,才由当前类加载器自己加载。优势:避免类重复加载、保证核心类库的安全性(如防止用户自定义java.lang.String类覆盖核心类)。
1.4 垃圾回收(GC)核心概念
垃圾回收是指JVM自动回收堆和方法区中无引用对象的内存空间,核心解决“哪些对象需要回收”“何时回收”“如何回收”三个问题:
垃圾判定算法:
引用计数法(Reference Counting):为每个对象维护一个引用计数器,被引用时计数器+1,引用失效时-1,计数器为0则认为是垃圾。缺点:无法解决循环引用问题(如A引用B,B引用A,计数器均不为0,但实际无外部引用)。可达性分析算法(Reachability Analysis):JVM主流采用的算法,以“GC Roots”为起点,通过引用链遍历对象,无法到达的对象即为垃圾。GC Roots包括:虚拟机栈中局部变量表的引用对象、本地方法栈中本地方法的引用对象、方法区中静态变量和常量的引用对象、活跃线程的引用对象等。
引用类型(JDK1.2后):
强引用(Strong Reference):如Object obj = new Object(),只要强引用存在,对象不会被回收,即使OOM也不回收。软引用(Soft Reference):用于缓存场景,当内存不足时,JVM会回收软引用关联的对象。
弱引用(Weak Reference):用于临时缓存,只要发生GC,无论内存是否充足,都会回收弱引用关联的对象。
虚引用(Phantom Reference):无实际引用意义,仅用于监听对象被GC回收的事件,必须与ReferenceQueue配合使用。
GC算法:
标记-清除算法(Mark-Sweep):分为标记(标记垃圾对象)和清除(回收垃圾对象内存)两个阶段。优点:简单高效;缺点:产生内存碎片,后续大对象分配可能失败。标记-复制算法(Mark-Copy):将堆内存分为两块,只使用其中一块,GC时标记存活对象,复制到另一块未使用的内存,然后清空原使用块。优点:无内存碎片;缺点:内存利用率低(仅50%),适合年轻代(存活对象少)。
标记-整理算法(Mark-Compact):标记存活对象后,将存活对象向内存一端移动,然后清空另一端的垃圾内存。优点:无内存碎片、内存利用率高;缺点:移动对象耗时,适合老年代(存活对象多)。
分代收集算法(Generational Collection):结合上述算法的优势,根据对象存活周期将堆分为年轻代和老年代:年轻代采用标记-复制算法(存活对象少,复制成本低),老年代采用标记-清除或标记-整理算法(存活对象多,避免频繁复制)。
垃圾收集器:JVM的GC实现,不同收集器适用于不同场景,常见的有:Serial(串行收集器,单线程GC,适合单CPU环境)、ParNew(Serial的并行版本,多线程GC,年轻代专用)、Parallel Scavenge(并行收集器,注重吞吐量,年轻代专用)、Serial Old(Serial的老年代版本,串行标记-整理)、Parallel Old(Parallel Scavenge的老年代版本,并行标记-整理)、CMS(Concurrent Mark Sweep,并发标记清除,注重响应时间,老年代专用,并发GC,减少停顿)、G1(Garbage-First,区域化分代式收集器,兼顾吞吐量和响应时间,可管理整个堆)、ZGC/Shenandoah(低延迟收集器,适合大内存、低停顿场景)。
二、JVM常用命令及示例
JVM提供了一系列命令行工具(位于JDK的bin目录下),用于监控JVM运行状态、排查内存泄漏、分析GC情况等,是面试和工作中必备的工具,以下是常用命令及具体示例:
2.1 jps:Java进程状态工具
作用:列出当前运行的Java进程(类似ps命令),输出进程ID(PID)和主类名。
语法:jps [选项]
常用选项:
-l:输出主类的全限定名(若为Jar包,输出Jar包路径)。
-v:输出JVM启动参数。
-m:输出Java进程的启动参数(main方法的参数)。
示例:
1 | # 列出所有Java进程的PID和主类名 |
2.2 jstat:JVM统计监控工具
作用:实时监控JVM的内存使用、GC情况、类加载情况等统计信息。
语法:jstat [选项] 进程ID [间隔时间(毫秒)] [查询次数]
常用选项:
-class:监控类加载、卸载数量、总空间及类加载耗时。
-gc:监控堆内存各区域(Eden、Survivor、Old)的使用情况及GC次数、耗时。
-gcutil:监控堆内存各区域的使用率及GC统计信息(百分比形式,更直观)。
-gccapacity:监控堆内存各区域的最大容量、最小容量、当前容量。
-gcnew:监控年轻代GC情况。
-gcold:监控老年代GC情况。
示例:
1 | # 监控进程12345的类加载情况,每1000毫秒查询一次,共查询5次 |
2.3 jmap:JVM内存映像工具
作用:生成JVM的内存快照(heap dump),查看堆内存中对象的分布情况,排查内存泄漏。
语法:jmap [选项] 进程ID
常用选项:
-heap:查看堆内存的详细配置(如分代大小、GC算法、收集器等)及使用情况。
-histo:查看堆内存中对象的统计信息(类名、实例数量、占用内存大小)。
-dump:format=b,file=文件名.hprof:生成堆内存快照(二进制格式),后续可通过jhat或VisualVM分析。
-permstat(JDK7及之前)/ -clstats(JDK8+):查看永久代/元空间的类加载器统计信息。
示例:
1 | # 查看进程12345的堆内存配置及使用情况 |
2.4 jhat:JVM堆快照分析工具
作用:分析jmap生成的堆内存快照(.hprof文件),提供Web界面查看对象分布、引用关系等,排查内存泄漏。
语法:jhat [选项] 堆快照文件名.hprof
常用选项:
-port:指定Web服务端口(默认7000)。
-exclude:排除指定包名的类,减少分析数据量。
示例:
1 | # 分析heap_dump.hprof文件,启动Web服务,端口7001 |
2.5 jstack:JVM线程快照工具
作用:生成JVM的线程快照(线程堆栈),查看线程的运行状态(如RUNNABLE、BLOCKED、WAITING)、调用栈信息,排查线程死锁、线程阻塞、CPU占用过高等问题。
语法:jstack [选项] 进程ID
常用选项:
-F:强制生成线程快照(当进程无响应时使用)。
-l:显示线程的锁信息(如持有锁、等待锁的情况)。
-m:显示线程的本地方法调用栈(结合本地方法库分析)。
示例:
1 | # 生成进程12345的线程快照,包含锁信息,保存到thread_dump.txt |
2.6 jinfo:JVM配置信息工具
作用:查看或修改JVM的运行时配置参数(如-Xms、-Xmx、-XX:XXX等)。
语法:jinfo [选项] 进程ID
常用选项:
-flags:查看JVM的所有启动参数(包括默认参数和用户指定参数)。
-sysprops:查看Java系统属性(System.getProperties()的内容)。
-flag 参数名:查看指定参数的值(如-flag MaxHeapSize)。
-flag [+/-]参数名:动态开启/关闭指定的布尔型参数(如-flag +UseG1GC)。
-flag 参数名=值:动态修改指定参数的值(仅部分参数支持,如-flag HeapDumpPath=/tmp/)。
示例:
1 | # 查看进程12345的所有JVM启动参数 |
三、JVM相关面试题
3.1 基础概念类
**问题1:什么是JVM?JVM的核心作用是什么?**答:JVM(Java Virtual Machine)是运行Java字节码的虚拟计算机,它屏蔽了不同操作系统的底层差异,实现“一次编写,到处运行”。核心作用包括:加载解析.class文件、管理运行时内存(堆、栈、方法区等)、执行字节码(通过解释器或JIT编译器)、垃圾回收(自动回收无引用对象内存)、处理异常、提供本地方法调用接口(JNI)等。
**问题2:JVM的核心架构由哪些部分组成?各部分的作用是什么?**答:JVM核心架构包括五大模块:类加载器子系统:负责将.class文件加载到内存,完成加载、链接、初始化过程,遵循双亲委派模型。
运行时数据区:存储JVM运行时的各种数据,分为线程共享区(方法区、堆)和线程私有区(程序计数器、虚拟机栈、本地方法栈)。
执行引擎:将字节码翻译为本地机器指令并执行,包含解释器、JIT编译器和垃圾回收器。
本地方法接口(JNI):提供Java程序调用本地方法(C/C++编写)的接口,用于与操作系统交互。
本地方法库:存储本地方法的实现,供JNI调用。
**问题3:什么是双亲委派模型?它的优势是什么?如何打破双亲委派模型?**答:双亲委派模型是类加载器的加载规则:当一个类加载器收到加载请求时,首先委托给父类加载器加载,只有父类加载器无法加载(找不到.class文件)时,才由当前类加载器自己加载。
优势:① 避免类重复加载(同一类被不同类加载器加载会视为不同类);② 保证核心类库的安全性(防止用户自定义核心类,如java.lang.String,覆盖JDK原生类)。
打破双亲委派模型的方式:① 重写ClassLoader的loadClass()方法(不遵循委托逻辑);② 使用线程上下文类加载器(Thread.getContextClassLoader()),如JDBC加载驱动时,打破了核心类加载器无法加载用户应用类的限制。**问题4:JVM运行时数据区分为哪些部分?各部分的作用是什么?可能发生哪些OOM?**答:运行时数据区分为线程共享区和线程私有区:
线程共享区(所有线程共用):
① 方法区(元空间,JDK8+):存储类元信息、常量、静态变量、JIT编译后的代码等。可能发生OOM:元空间内存不足(如大量动态生成类,未及时卸载)。
② 堆:存储对象实例和数组,是GC主要区域。可能发生OOM:堆内存不足(如创建大量对象,GC无法回收,如内存泄漏)。线程私有区(每个线程独立拥有):
① 程序计数器:记录当前线程执行的字节码指令地址,无OOM(内存占用极小,JVM直接管理)。
② 虚拟机栈:存储方法调用的栈帧(局部变量表、操作数栈等)。可能发生:StackOverflowError(栈深度不足,如递归调用过深)、OutOfMemoryError(栈内存不足,如创建大量线程,每个线程栈占用过多内存)。
③ 本地方法栈:为本地方法提供内存空间,与虚拟机栈类似,可能发生StackOverflowError和OutOfMemoryError。
3.2 垃圾回收类
**问题1:JVM如何判断一个对象是垃圾?有哪些垃圾判定算法?**答:JVM判断对象为垃圾的核心标准是“对象无引用可达”。常用垃圾判定算法有两种:
① 引用计数法:为每个对象维护引用计数器,被引用时+1,引用失效时-1,计数器为0则为垃圾。缺点:无法解决循环引用问题(如A引用B,B引用A,计数器均不为0,但无外部引用)。
② 可达性分析算法:JVM主流采用,以GC Roots为起点,通过引用链遍历对象,无法到达的对象即为垃圾。GC Roots包括:虚拟机栈局部变量表的引用对象、本地方法栈本地方法的引用对象、方法区静态变量和常量的引用对象、活跃线程的引用对象等。**问题2:Java中的引用类型有哪些?各有什么特点?**答:JDK1.2后将引用分为四类,优先级从高到低:
① 强引用:如Object obj = new Object(),只要强引用存在,对象不会被GC回收,即使OOM也不回收。
② 软引用:用于缓存场景(如图片缓存),当内存不足时,JVM会回收软引用关联的对象。通过SoftReference类实现。
③ 弱引用:用于临时缓存,只要发生GC,无论内存是否充足,都会回收弱引用关联的对象。通过WeakReference类实现。
④ 虚引用:无实际引用意义,仅用于监听对象被GC回收的事件,必须与ReferenceQueue配合使用。通过PhantomReference类实现。**问题3:常见的GC算法有哪些?各有什么优缺点?适用场景是什么?**答:常见GC算法包括:
① 标记-清除算法(Mark-Sweep):
优点:简单高效,无需移动对象;
缺点:产生内存碎片,后续大对象分配可能失败;
适用场景:无(仅作为基础算法,后续算法基于其优化)。
② 标记-复制算法(Mark-Copy):
优点:无内存碎片,实现简单;
缺点:内存利用率低(仅50%),复制对象耗时;
适用场景:年轻代(存活对象少,复制成本低)。
③ 标记-整理算法(Mark-Compact):
优点:无内存碎片,内存利用率高;
缺点:移动对象耗时,需要暂停线程(STW);
适用场景:老年代(存活对象多,避免频繁复制)。
④ 分代收集算法(Generational Collection):
优点:结合上述算法优势,根据对象存活周期分代处理,兼顾效率和内存利用率;
缺点:实现复杂;
适用场景:所有主流JVM(如HotSpot),是目前最常用的GC算法。**问题4:常见的垃圾收集器有哪些?各有什么特点?适用场景是什么?**答:常见垃圾收集器及特点、适用场景:
① Serial(串行收集器):
特点:单线程GC,STW时间长,实现简单,内存占用少;
适用场景:单CPU环境、小型应用(如桌面应用)。
② ParNew(并行收集器):
特点:Serial的并行版本,多线程GC,年轻代专用,可与CMS配合使用;
适用场景:多CPU环境,需要减少年轻代GC停顿时间的应用。
③ Parallel Scavenge(并行收集器):
特点:多线程GC,年轻代专用,注重吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+GC时间)),可自动调节堆大小和GC频率;
适用场景:后台计算类应用(如大数据处理),对吞吐量要求高,对响应时间要求低。
④ CMS(Concurrent Mark Sweep):
特点:并发GC,老年代专用,注重响应时间(STW时间短),采用标记-清除算法;
缺点:CPU占用高,产生内存碎片,无法处理浮动垃圾(GC过程中产生的新垃圾);
适用场景:Web应用、分布式服务等对响应时间要求高的应用。
⑤ G1(Garbage-First):
特点:区域化分代式收集器,可管理整个堆,兼顾吞吐量和响应时间,采用标记-整理算法(无内存碎片),可预测GC停顿时间;
适用场景:大内存应用(如堆内存10GB+),对响应时间和吞吐量均有要求的应用(如电商系统)。
⑥ ZGC/Shenandoah:
特点:低延迟收集器,STW时间极短(毫秒级以下),支持大内存(如百GB级堆内存);
适用场景:金融、电信等对延迟要求极高的核心业务系统。**问题5:什么是STW(Stop The World)?如何减少STW对应用的影响?**答:STW是指GC过程中,JVM暂停所有用户线程的执行,只执行GC线程,目的是避免GC过程中对象引用关系发生变化,保证GC的正确性。STW会导致应用响应延迟,影响用户体验。
减少STW影响的方式:
① 选择合适的垃圾收集器(如CMS、G1、ZGC等低延迟收集器);
② 优化JVM参数(如调整堆大小、分代比例、GC触发阈值等,减少GC频率和每次GC的停顿时间);
③ 减少大对象的创建(大对象直接进入老年代,容易触发老年代GC,STW时间长);
④ 避免内存泄漏(内存泄漏会导致堆内存不足,频繁触发GC,增加STW次数);
⑤ 使用并发GC(如CMS、G1的并发标记阶段,不暂停用户线程)。
3.3 性能调优与问题排查类
- **问题1:什么是内存泄漏?如何排查JVM内存泄漏?**答:内存泄漏是指对象不再被应用程序使用,但由于存在无效引用,导致GC无法回收该对象,长期积累会导致堆内存不足,最终触发OOM。
排查内存泄漏的步骤:
① 监控JVM内存使用:通过jstat -gcutil命令实时监控堆内存使用率,若老年代使用率持续上升,可能存在内存泄漏。
② 生成堆内存快照:通过jmap -dump:format=b,file=xxx.hprof命令生成堆快照。
③ 分析堆快照:使用jhat或VisualVM工具分析堆快照,查看:
- 哪些对象占用内存过多(实例数多、内存占比高);
- 这些对象的引用链(找到持有这些对象的无效引用,即内存泄漏的根源,如未关闭的连接、静态集合存储大量对象未清理等)。
④ 验证问题:根据分析结果,检查代码中对应的引用逻辑,修复无效引用(如关闭连接、清理静态集合),重新部署后监控内存使用,确认泄漏问题解决。
- **问题2:如何排查JVM线程死锁问题?**答:排查线程死锁的步骤:
① 识别死锁现象:应用响应缓慢、CPU利用率低、线程无法正常执行任务。
② 生成线程快照:通过jstack -l 进程ID命令生成线程快照(-l选项显示锁信息)。
③ 分析线程快照:jstack会自动检测死锁并标注“Deadlock detected”,查看死锁线程的信息:
- 每个死锁线程等待的锁(ownable synchronizer);
- 每个死锁线程持有的锁(被其他线程等待的锁);
- 线程的调用栈(找到死锁发生的代码位置)。
④ 修复死锁:根据分析结果,调整代码中的锁获取顺序(如所有线程按相同顺序获取多个锁)、减少锁的持有时间、避免嵌套锁等,重新部署后验证问题解决。
**问题3:JVM常用的调优参数有哪些?请举例说明。**答:JVM调优参数主要分为三类:堆内存参数、GC参数、其他参数,常用示例:
① 堆内存参数:
-Xms:初始堆内存大小(如-Xms512m,JDK1.8后默认初始堆为物理内存的1/64);
-Xmx:最大堆内存大小(如-Xmx1024m,建议与-Xms设置为相同值,避免频繁调整堆大小);
-Xmn:年轻代内存大小(如-Xmn384m,年轻代=Eden+Survivor,默认年轻代占堆的1/3);
-XX:SurvivorRatio:Eden区与Survivor区的比例(如-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1,默认8);
-XX:MetaspaceSize:元空间初始大小(如-XX:MetaspaceSize=256m,JDK8+,替代永久代);
-XX:MaxMetaspaceSize:元空间最大大小(如-XX:MaxMetaspaceSize=1024m,默认无上限,物理内存限制)。
② GC参数:
-XX:+UseG1GC:使用G1收集器(JDK9后默认);
-XX:+UseConcMarkSweepGC:使用CMS收集器;
-XX:+UseParallelGC:使用Parallel Scavenge收集器(JDK8默认);
-XX:MaxGCPauseMillis:G1收集器的最大GC停顿时间(如-XX:MaxGCPauseMillis=200,单位毫秒);
-XX:+HeapDumpOnOutOfMemoryError:OOM时自动生成堆快照(如-XX:HeapDumpPath=/tmp/heap_dump.hprof指定保存路径);
-XX:ParallelGCThreads:并行GC的线程数(如-XX:ParallelGCThreads=4,默认与CPU核心数相同)。
③ 其他参数:
-XX:+PrintGCDetails:打印GC详细日志;
-XX:+PrintGCTimeStamps:打印GC发生的时间戳;
-Xss:每个线程的栈大小(如-Xss1m,默认1m,影响线程数量,栈越大可创建的线程越少)。**问题4:如何分析JVM GC日志?*答:分析GC日志的步骤:
① 开启GC日志:通过JVM参数开启,如-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xlog:gc:file=gc.log:time,level,tags:filecount=5,filesize=100m(JDK9+),将GC日志输出到文件。
② 关键日志字段解读:
- 日志时间戳:如“2.345”表示JVM启动后2.345秒发生GC;
- GC类型:Young GC(年轻代GC,如YGC、ParNew)、Full GC(老年代GC,如FGC、CMS);
- 内存变化:如“Eden Space: 51200K->0K(61440K)”表示Eden区从51200K使用量变为0K(GC回收),总容量61440K;
- GC耗时:如“0.0250000 secs”表示GC耗时25毫秒;
- 堆内存整体使用:如“Heap after GC: 61440K->30720K(102400K)”表示GC后堆内存使用量从61440K变为30720K,总容量102400K。
③ 分析重点: - GC频率:年轻代GC是否过于频繁(如每秒多次,可能是年轻代过小或创建对象过快);
- GC耗时:单次GC停顿时间是否过长(如Full GC耗时超过1秒,影响响应时间);
- 内存变化:老年代使用量是否持续上升(可能存在内存泄漏);
- Full GC次数:Full GC耗时远大于Young GC,应尽量减少Full GC次数(如通过调整老年代大小、优化对象生命周期)。
④ 工具辅助:使用GC Easy、GCEasy、VisualVM等工具导入GC日志,自动生成分析报告(如GC频率、耗时统计、内存泄漏预警等),提升分析效率。
3.4 进阶类
**问题1:什么是JIT编译器?它的作用是什么?**答:JIT(Just-In-Time)编译器是JVM执行引擎的组成部分,作用是将频繁执行的“热点代码”(如循环、频繁调用的方法)从字节码编译为本地机器码,从而提升代码执行效率。
JVM的执行方式:初始时通过解释器逐行解释字节码(启动快,执行慢),当JVM检测到某段代码被频繁执行(达到热点阈值),则触发JIT编译,将其编译为本地机器码,后续执行该代码时直接运行机器码(执行快,编译耗时)。
热点代码的判定:通过“采样计数器”(统计代码被调用的次数)和“循环计数器”(统计循环执行的次数)判定,当达到预设阈值(如方法被调用10000次,循环执行1000次),则标记为热点代码。**问题2:JDK8中JVM的内存模型有哪些变化?为什么要做这些变化?**答:JDK8中JVM内存模型的核心变化是:将永久代(PermGen)替换为元空间(Metaspace)。
变化细节:
- 永久代(JDK7及之前):存储类元信息、常量、静态变量等,内存分配在JVM堆中,有固定大小限制(通过-XX:PermSize和-XX:MaxPermSize指定),容易发生PermGen OOM(如动态生成大量类时)。
- 元空间(JDK8+):存储类元信息等,内存分配在本地物理内存(而非JVM堆),默认无上限(受物理内存限制),可通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定初始和最大大小。
变化原因:
① 永久代大小难以确定(不同应用的类数量差异大,容易设置过小导致OOM,设置过大浪费内存);
② 永久代的垃圾回收效率低(类元信息的回收条件苛刻,容易积累导致内存泄漏);
③ 元空间使用本地内存,避免了永久代的内存限制,提升了JVM的稳定性和可扩展性;
④ 契合JDK的模块化发展(如Jigsaw项目),更好地支持类的动态加载和卸载。
- **问题3:什么是逃逸分析?JVM如何利用逃逸分析优化性能?**答:逃逸分析是JVM的一种静态分析技术,用于分析对象的引用范围(即对象是否会“逃逸”出当前方法或线程)。
对象逃逸的情况:
- 方法逃逸:对象被返回给方法的调用者(如return obj);
- 线程逃逸:对象被存储到线程共享的变量中(如静态变量、堆中的对象字段)。
JVM利用逃逸分析的优化手段:
① 栈上分配(Stack Allocation):若对象未逃逸(仅在当前方法内使用),则将对象分配在虚拟机栈的局部变量表中,而非堆中。栈上分配的
(注:文档部分内容可能由 AI 生成)


