前言
分类汇总 20+ 常见的 Java 虚拟机篇 经典后端面试题,并对题目进行了精炼总结,旨在帮助大家高效记忆,在面试中游刃有余,不至于陷入词穷的窘境。
Java 虚拟机篇
调优命令有哪些?
- jps:列出当前用户的Java进程。
- jstat:监控JVM统计信息,如垃圾回收、类加载等。
- jmap:生成堆转储快照,分析内存使用情况。
- jhat:分析堆转储文件,在浏览器中查看分析结果。
- jstack:生成线程转储,分析线程状态。
- jinfo:查看和修改正在运行的Java进程的JVM参数和系统属性。
讲下 JVM 的内存模型?
JVM内存模型主要分为堆Heap、方法区、虚拟机栈、本地方法栈、程序计数器。jdk1.8去掉了方法区,直接在内存中写入原空间。
-
堆:存储对象实例。
-
方法区:存储类信息、常量、静态变量。
-
栈:存储方法帧、局部变量。
-
程序计数器:存储当前线程执行的字节码指令地址。
为什么 Java 8 移除了永久代(PermGen)并引入了元空间(Metaspace)?
-
内存管理优化:永久代固定大小,难以调优,容易出现内存溢出。元空间动态调整大小,减少内存溢出风险。
-
性能提升:元空间使用本地内存,减少JVM内存压力,提高性能。
-
简化GC:永久代的垃圾回收复杂,元空间的引入简化了垃圾回收过程。
Java 中堆和栈的区别是什么?(栈内存和堆内存的区别?)
-
堆用于存储对象实例,栈用于存储方法帧和局部变量。
-
堆是线程共享的,栈是线程私有的。
-
堆内存管理由JVM自动完成,栈内存由编译器自动管理。
什么是 Java 中的直接内存?
Java中的直接内存是指通过 ByteBuffer.allocateDirect()
等方法分配的内存,不受JVM堆内存大小限制,直接使用操作系统内存,适用于高性能I/O操作。
什么是对象内存的分配与回收?
-
对象内存分配是指在堆内存中为新创建的对象分配空间,通常通过new关键字实现。大部分对象在分配时都是在Eden中,较大的对象直接分配到老年代中。
-
对象回收是指垃圾回收器识别并回收不再使用的对象,释放其占用的内存空间,以供后续对象分配使用。主要是发生在新生代的回收。
讲下垃圾回收机制?
垃圾回收机制是JVM自动管理内存的过程,通过识别和回收不再使用的对象,释放内存空间,确保内存的有效利用和系统的稳定运行。
垃圾回收算法有哪些?
-
标记-清除算法(Mark-Sweep):简单,但产生内存碎片。
-
复制算法(Copying):高效,但浪费一半空间。
-
标记-整理算法(Mark-Compact):解决碎片问题,但效率较低。
-
分代收集算法(Generational Collection):根据对象生命周期分代管理,优化回收效率。
什么是三色标记算法?
-
初始标记(白色):所有对象初始标记为白色。
-
并发标记(灰色):从GC Roots开始,标记可达对象为灰色,并递归标记其引用的对象。
-
重新标记(黑色):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
-
清理(白色):回收未被标记为黑色的白色对象。
垃圾回收收集器有哪些?
-
Serial收集器(单线程,适用于小内存)。
-
Parallel收集器(多线程,吞吐量优先)。
-
CMS收集器(低延迟,适用于响应时间敏感)。
-
G1收集器(分区管理,平衡吞吐量和延迟)。
-
ZGC收集器(极低延迟,适用于大内存)。
讲下JVM中回收的分代策略?
JVM分代策略将堆内存分为新生代和老年代,新生代进一步分为Eden区和两个Survivor区。新对象分配在Eden区,经过Minor GC后存活对象移至Survivor区,多次存活后晋升至老年代,老年代进行Major GC或Full GC。
Java 中如何判断对象是否是垃圾?
-
引用计数法:每个对象有一个引用计数器,引用增加时计数加一,引用减少时计数减一。计数为零的对象被视为垃圾。但这种方法无法解决循环引用问题。
-
可达性分析:通过GC Roots的引用链判断对象是否可达,不可达的对象被视为垃圾。这种方法有效避免了循环引用问题,是Java垃圾回收的基础。
什么是 STW?
在垃圾回收过程中,特别是Full GC时,可能会出现Stop-The-World(STW)现象,即所有应用线程暂停,等待垃圾回收完成。这是为了确保一致的内存视图,避免数据不一致。STW时间的长短直接影响系统性能和响应性。
什么是 Java 中的 logging write barrier?
是垃圾回收器用于记录对象引用变化的一种机制。当程序修改对象引用时,write barrier 会记录这些变化,以便垃圾回收器在后续的标记阶段能够正确地识别和处理这些引用关系。
GC的触发条件?
-
堆内存不足,无法分配新对象.
-
达到垃圾收集器的配置阈值,如 Eden 区满触发 Minor GC。
-
老年代空间不足触发 Major GC 或 Full GC。
-
系统调用 System.gc() 建议 JVM 进行垃圾回收。
讲下什么是对象的访问?
对象访问在Java中通常通过引用来进行。当创建一个对象时,会在堆内存中分配空间,并返回一个引用(通常是内存地址)。程序通过这个引用访问对象的属性和方法。
对象访问的方式有哪些?
-
句柄访问:引用存储句柄地址,通过句柄访问对象,属于间接访问。
-
直接指针访问:引用直接存储对象地址,访问时没有中间层,属于直接访问。
什么是直接访问和间接访问?
-
直接访问:通过变量名直接访问对象的属性和方法。
-
间接访问:通过方法调用或传递引用,间接访问对象。
讲下对象的引用程度?
-
强引用(Strong Reference):最常见的引用类型,如Object obj = new Object()。只要对象存在强引用,垃圾回收器就不会回收该对象。
-
软引用(Soft Reference):通过SoftReference类实现,在内存不足时,垃圾回收器可能会回收这些对象,适用于实现内存敏感的缓存。
-
弱引用(Weak Reference):通过WeakReference类实现,垃圾回收器在下次回收时会回收这些对象,不管内存是否充足,适用于实现规范化映射。
-
虚引用(Phantom Reference):通过PhantomReference类实现,无法通过虚引用获取对象,主要用于对象回收前的清理操作,如管理直接内存。
为什么新生代被划分为 S0、S1 和 Eden 区?
-
Eden 区:新创建的对象首先分配在 Eden 区。
-
S0 和 S1 区:Eden 区满后,存活对象被复制到 Survivor 区(S0 或 S1),另一个 Survivor 区为空。经过多次复制后,仍然存活的对象晋升到老年代。
JVM 新生代垃圾回收如何避免全堆扫描?
-
分代假设:假设大多数对象生命周期短,主要在新生代回收,老年代对象较少变动。
-
复制算法:新生代采用复制算法,将 Eden 区和 Survivor 区的存活对象复制到另一个 Survivor 区,清空原区域,减少扫描范围。
-
晋升机制:经过多次复制后仍然存活的对象晋升到老年代,减少新生代垃圾回收的负担。
-
卡表(Card Table):老年代到新生代的引用通过卡表记录,避免全堆扫描,只扫描卡表中被标记的区域。
怎么进行 JVM 调优?
-
调整堆内存大小和分代比例。
-
选择合适的垃圾收集器。
-
配置GC参数,如暂停时间、吞吐量目标。
-
监控和分析GC日志,优化内存使用和回收策略。
-
根据应用特性调整线程池、锁机制等,提升整体性能。
怎么去做Java的性能优化?
-
代码优化,减少不必要的对象创建和内存消耗。
-
使用合适的数据结构和算法。
-
合理使用缓存和连接池。
-
JVM调优,如调整内存分配、选择合适的垃圾收集器。
-
并发优化,如使用线程池、锁优化。
-
监控和分析系统瓶颈,针对性优化。
了解过什么是Java 的逃逸分析吗?
逃逸分析是JVM的一种优化技术,用于分析对象的作用域,判断对象是否在方法外被引用。如果对象未逃逸,JVM可以进行栈上分配、标量替换等优化,减少堆内存使用和垃圾回收压力,提高程序性能。
什么是 Java 中的指令重排?
Java 中的指令重排是指编译器和处理器为了优化性能而对指令顺序进行调整,不改变单线程语义,但可能导致多线程环境下的数据竞争和内存可见性问题。
如何在 Java 中进行内存泄漏分析?
-
监控工具:使用如 VisualVM、JProfiler 等工具监控应用的内存使用情况。
-
Heap Dump:生成堆转储文件(Heap Dump),分析对象的分布和引用关系。
-
内存分析:使用 MAT(Memory Analyzer Tool)等工具分析堆转储文件,查找占用内存大的对象和潜在的内存泄漏点。
-
代码审查:检查代码中可能的内存泄漏点,如未关闭的资源、静态集合类等。
-
定期 GC:通过定期执行 System.gc() 或配置 JVM 参数,观察内存回收情况。