Java 虚拟机面试题 - 草稿

点赞关注,不再迷路,你的支持对我意义重大!

Hi,我是丑丑。本文 「Java 路线」导读 —— 他山之石,可以攻玉 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)


前言

面试季又来了,Java 基础知识又可以拿出来复习了~ “基础不牢,地动山摇”,这些内容不难,但必须要会,一起加油吧。



1、基本概念


2、内存管理

说一说虚拟机内存分配模型 / 运行时数据区域

Java 虚拟机的运行时数据区域主要分为线程私有区域和线程共享区域两类。其中私有区域包括「程序计数寄存器」、「Java 虚拟机栈」和「本地方法栈」,共享区域包括「Java 堆」和「方法区」。

  • 程序计数寄存器:记录的是当前线程下一条准备执行执行的字节码行号,程序计数器保证了程序可以正确地进行线程切换。当一个线程的时间片用完,或者其他线程提前抢夺 CPU 时间片时,当前线程就会挂起,而将来挂起的线程获得时间片时,就需要通过程序计数器恢复到正确的指令位置。

  • Java 虚拟机栈描述的是 Java 方法执行的内存模型,每个方法从调用到结束的过程,都对应着一个栈帧入栈到出栈的过程。

  • 本地方法栈与 Java 虚拟机栈类似,区别在于执行的是 native 方法。当虚拟机调用 native 方法时,不会在虚拟机栈中创建栈帧,而是直接动态链接调用 native 方法。

  • 方法区包含类信息、静态变量、静态常量、运行时常量池和即时编译器生成的代码(Class 文件常量池和运行时常量池容易混淆,其实一个是静态的,一个是动态的。Class 文件常量池指编译后存放在 Class 文件中的字面量 & 符号引用,在类加载之后会进入运行时常量池。另外,常量也可以在运行时加入,比如通过 String#intern())。

  • Java 堆是虚拟机最大的一块内存,绝大多数对象存储在堆上(Class 对象存储在方法区,满足逃逸分析的对象在栈上分配)。Java 堆体现了 “动静分离” 的思想,在堆中存放的是生命周期较短的对象,而方法区中存放的是生命周期较长的对象,将两种数据分开存储有利于更高效地内存管理。


Java 虚拟机栈帧包含哪些内容?

Java 虚拟机栈帧包含:局部变量表 & 操作数栈 & 动态连接 & 返回地址。局部变量表存储局部变量,操作数栈存放字节码指令的操作数,动态连接???,返回地址存放函数调用位置的下一行指令,用于在方法正常返回时返回到上一层方法继续执行。


方法区和永生代的区别?

方法区是虚拟机规范约定的运行时数据区域,而永生代和元空间是方法区在不同虚拟机上的具体实现。在 JDK 1.7 之前,HotSpot 虚拟机使用永久代来实现方法区。永久代中存储的都是生命周期较长的数据,可以跟堆一起执行垃圾回收,但回收率比新生代低。考虑到永生代内存空间有限,经常出现内存溢出异常,从 JDK 1.8 开始,HotSpot 虚拟机使用元空间来实现方法区,元空间利用了本地内存存储,扩展了方法区的内存上限。不过元空间也不是完美的,因为机器内存总归是有限的,大量占用本地内存也会挤压堆内存的上限。


对象的内存布局?

对象的内存布局主要包含 3 个区域:对象头 & 实例数据 & 对齐填充。其中对象头主要包含 Mark Work 标志位,如果采用「直接指针」的对象访问,那么对象头里还包含类型指针。如果是数组对象,那么对象头还包含数组的长度。实例数据区存储了「本类声明的实例字段」和「从父类继承的实例字段」(类字段存储在方法区)。

相关深入文章:
Java 虚拟机 | 拿放大镜看对象
Java 虚拟机 | 内存分配模型


3、内存管理

垃圾回收机制的优缺点?

  • 优点:不再需要为每个 new 操作编写对应的 delete / free 操作,程序不容易出现内存泄漏或内存溢出问题;
  • 风险:垃圾回收处理程序本身也占用系统资源(CPU 资源 / 内存),增大程序暂停时间。

说一下垃圾回收的过程?

垃圾回收算法可以分为四类基本算法:引用计数算法、标记-清理算法、标记-整理算法和复制算法。其它的垃圾回收算法都是对基础算法的改进或组合。比如主流的虚拟机垃圾回收算法采用分代回收模型:即在新生代选用复制算法(对象存活率低),而老生代选用 “标记 - 清理” 或 “标记 - 整理” 算法(对象存活率高,并且没有额外空间进行分配担保);

  • 引用计数法的优点是及时(当对象变成垃圾后,程序可以立刻感知,马上回收)和最大暂停时间短(GC 可与应用交替运行),缺点是计数器更新频繁、堆利用率低和实现复杂,最大的缺陷是无法回收循环引用对象。

  • 标记-清理算法的优点是实现简单,缺点是效率不稳定和内存碎片化,导致堆利用率不高。当内存碎片化到一定程度,小内存不再足以分配对象内存是,又会触发一次垃圾回收动作。

  • 标记-整理算法多了一个整理步骤,可以避免内存碎片化和快速分配对象(由于空闲分块是一个连续内存,不再需要向标记-清理算法那样遍历空闲列表)。缺点是移动对象会延长 GC 暂停时间,降低虚拟机吞吐量。

  • 复制算法也具有避免内存碎片化和快速分配对象的优点(存活对象和新分配对象都被压缩到 tospace 的一端,不再需要向标记-清理算法那样遍历空闲列表)。缺点是复制对象会延长 GC 暂停时间和堆利用率低(把堆二等分只能利用其中的一半,堆利用率最高仅为 50 %)

Java 虚拟机面试题 - 草稿_第1张图片

有哪些优化垃圾回收效率的方法?

  • 复制算法:复制算法把堆二等分只能利用其中的一半,堆利用率最高仅为 50 %,可以优化空间划分方法。即:把堆内存划分为三块区域(一块 Eden 区和两块 Survivor 区,对应的比例为 8:1:1),对象只在 Eden 区分配,当 Eden 区占满后,将 Eden 区和 from Survivor 区的存活对象全部赋值到 to Survivor 区,并在复制完成后互换 from Survivor 区和 to Survivor 区的指针。改进后堆利用率提升到最高 90%。

  • 标记-清理算法:标记-清理为了保证彻底清理所有垃圾对象,会 stop-the-world,GC 最大暂停时间长,导致虚拟机的吞吐量降低。使用并发标记清理 CMS 可以降低 GC 暂停时间。CMS 主要分为 4 个步骤:初始标记 -> 并发标记 -> 重新标记 -> 并发清除。CMS 的优点是缩短 GC 暂停时间,提供虚拟机吞吐量,缺点是 CPU 敏感和浮动垃圾(由于并发清理的过程中用户线程还在运行,需要额外预留出一块空间来允许浮动垃圾产生)。

    • 1、初始标记(短暂 stop-the-world):仅仅标记被 GC Root 直接引用的对象,由于 GC Root 相对较少,这个过程速度很块;
    • 2、并发标记(耗时):继续遍历 GC Root 引用链上的对象,这个过程比较耗时,所以采用并发处理;
    • 3、重新标记(短暂 stop-the-world):为了修正并发标记期间用户线程导致的引用关系变化,需要暂停用户线程重新标记;
    • 4、并发清除(耗时):由于清除对象的过程比较耗时,所以采用并发处理。
Java 虚拟机面试题 - 草稿_第2张图片
image

如何判断对象已死?

判断对象是否存活的方法有「引用计数法」和「可达性分析法」。引用计数法指创建对象时额外分配一个引用计数器,该计数器记录了指向对象的引用个数,当一个新引用指向对象时计数器加 1,当一个引用不再指向对象时,计数器减 1,直到计数器清 0,该对象判定为垃圾对象。可达性分析法指根据引用关系形成一条引用链,当一个对象存在到 GC Root 的引用链时,则为存活对象,否则判定为垃圾对象。

引用计数法的优点是及时(当对象变成垃圾后,程序可以立刻感知,马上回收)和最大暂停时间短(GC 可与应用交替运行),缺点是计数器更新频繁、堆利用率低和实现复杂,最大的缺陷是无法回收循环引用对象。

可达性分析法的优点是可以回收循环引用对象和实现简单,缺点是回收不及时(只有执行 GC 才能感知垃圾对象)和最大暂停时间长(在 GC 期间,整个需要 stop-the-world)。


GC Root 有哪些?

  • 1、Java 虚拟机栈帧中的本地变量表
  • 2、本地方法栈中引用的对象
  • 3、方法区类静态变量引用的对象
  • 4、方法区常量池中引用的对象
  • 5、同步锁(synchronized 关键字)持有的对象

相关深入文章:Java 虚拟机 | 垃圾回收机制


你可能感兴趣的:(Java 虚拟机面试题 - 草稿)