Java 虚拟机面试题全面解析(干货)

时间:2019-03-21
本文章向大家介绍Java 虚拟机面试题全面解析(干货),主要包括Java 虚拟机面试题全面解析(干货)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Java 虚拟机面试题全面解析(干货)

本文固定链接:https://www.zybuluo.com/Yano/note/321063 
本文 PDF 下载:http://download.csdn.net/detail/yano_nankai/9469648 
LeetCode题解:https://github.com/LjyYano/LeetCode 
我的博客:http://blog.csdn.net/yano_nankai

周志明著的《深入理解 Java 虚拟机》的干货~如有错误,欢迎指出 O(∩_∩)O 转载请保留以上信息。

1. JDK 是什么?

JDK 是用于支持 Java 程序开发的最小环境。

Java 程序设计语言

Java 虚拟机

Java API类库

2. JRE 是什么?

JRE 是支持 Java 程序运行的标准环境。

Java SE API 子集

Java 虚拟机

3. Java历史版本的特性?

Java Version SE 5.0

  • 引入泛型
  • 增强循环,可以使用迭代方式;
  • 自动装箱与自动拆箱
  • 类型安全的枚举
  • 可变参数
  • 静态引入;
  • 元数据(注解);
  • 引入Instrumentation
  • 支持脚本语言
  • 引入JDBC 4.0 API
  • 引入Java Compiler API
  • 可插拔注解;
  • 增加对Native PKI(Public Key Infrastructure)Java GSS(Generic Security Service)KerberosLDAP(Lightweight Directory Access Protocol)的支持;
  • 继承Web Services
  • 做了很多优化。
  • switch语句块中允许以字符串作为分支条件;
  • 在创建泛型对象时应用类型推断;
  • 在一个语句块中捕获多种异常;
  • 支持动态语言;
  • 支持try-with-resources
  • 引入Java NIO.2开发包;
  • 数值类型可以用2进制字符串表示,并且可以在字符串表示中添加下划线;
  • 钻石型语法;
  • null的自动处理。
  • 函数式接口
  • Lambda表达式
  • 接口的增强

Java Version SE 6

Java Version SE 7

Java 8

4. 运行时数据区域包括哪些?

程序计数器

Java 虚拟机栈

本地方法栈

Java

方法区

运行时常量池

直接内存

程序计数器(线程私有)

程序计数器(Program Counter Register)是一块较小的内存空间,可以看作是当前线程所执行字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器完成。

由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程之间的计数器互不影响,独立存储。

如果线程正在执行的是一个 Java 方法,计数器记录的是正在执行的虚拟机字节码指令的地址;

如果正在执行的是 Native 方法,这个计数器的值为空。

程序计数器是唯一一个没有规定任何 OutOfMemoryError 的区域。

Java 虚拟机栈(线程私有)

Java 虚拟机栈(Java Virtual Machine Stacks)是线程私有的,生命周期与线程相同。 
虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧(Stack Frame),存储

局部变量表

操作栈

动态链接

方法出口

每一个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

这个区域有两种异常情况:

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度

OutOfMemoryError:虚拟机栈扩展到无法申请足够的内存时

本地方法栈(线程私有)

虚拟机栈为虚拟机执行 Java 方法(字节码)服务。

本地方法栈(Native Method Stacks)为虚拟机使用到的 Native 方法服务。

Java 堆(线程共享)

Java 堆(Java Heap)是 Java 虚拟机中内存最大的一块。Java 堆在虚拟机启动时创建,被所有线程共享。

作用:存放对象实例。垃圾收集器主要管理的就是 Java 堆。Java 在物理上可以不连续,只要逻辑上连续即可。

方法区(线程共享)

方法区(Method Area)被所有线程共享,用于存储已被虚拟机加载类信息、常量、静态变量、即时编译器编译后的代码等数据。

Java 堆一样,不需要连续的内存,可以选择固定的大小,更可以选择不实现垃圾收集。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。保存 Class 文件中的符号引用、翻译出来的直接引用。运行时常量池可以在运行期间将新的常量放入池中。

5. Java 中对象访问是如何进行的?

Object obj =  new  Object();

对于上述最简单的访问,也会涉及到 Java 栈、Java 堆、方法区这三个最重要内存区域。

Object obj

如果出现在方法体中,则上述代码会反映到 Java 栈的本地变量表中,作为 reference 类型数据出现。

new  Object()

反映到 Java 堆中,形成一块存储了 Object 类型所有对象实例数据值的内存。Java堆中还包含对象类型数据的地址信息,这些类型数据存储在方法区中。

6. 如何判断对象是否死去

引用计数法

根搜索算法

什么是引用计数法?

给对象添加一个引用计数器,每当有一个地方引用它,计数器就+1,;当引用失效时,计数器就-1;任何时刻计数器都为0的对象就是不能再被使用的。

引用计数法的缺点?

很难解决对象之间的循环引用问题。

什么是根搜索算法?

通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链Reference Chain),当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。

7. Java 4种引用方式?

JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为

强引用 Strong Reference

软引用 Soft Reference

弱引用 Weak Reference

虚引用 Phantom Reference

强引用

Object obj =  new  Object();

代码中普遍存在的,像上述的引用。只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

软引用

用来描述一些还有用,但并非必须的对象。软引用所关联的对象,有在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围,并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存异常。提供了 SoftReference 类实现软引用。

弱引用

描述非必须的对象,强度比软引用更弱一些,被弱引用关联的对象,只能生存到下一次垃圾收集发生前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。提供了 WeakReference 类来实现弱引用。

虚引用

一个对象是否有虚引用,完全不会对其生存时间够成影响,也无法通过虚引用来取得一个对象实例。为一个对象关联虚引用的唯一目的,就是希望在这个对象被收集器回收时,收到一个系统通知。提供了 PhantomReference 类来实现虚引用。

8. 有哪些垃圾收集算法?

标记-清除算法

复制算法 

标记-整理算法 

分代收集算法

标记-清除算法(Mark-Sweep

什么是标记-清除算法?

分为标记清除两个阶段。首先标记出所有需要回收的对象,在标记完成后统一回收被标记的对象。

有什么缺点?

1. 效率问题。标记和清除过程的效率都不高。 
2. 空间问题。标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能导致,程序分配较大对象时无法找到足够的连续内存,不得不提前出发另一次垃圾收集动作。

复制算法(Copying新生代

将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将存活着的对象复制到另一块上面,然后再把已经使用过的内存空间一次清理掉。

优点?

复制算法使得每次都是针对其中的一块进行内存回收,内存分配时也不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

缺点?

将内存缩小为原来的一半。在对象存活率较高时,需要执行较多的复制操作,效率会变低。

应用?

商业的虚拟机都采用复制算法来回收新生代。因为新生代中的对象容易死亡,所以并不需要按照1:1的比例划分内存空间,而是将内存分为一块较大的 Eden 空间两块较小的 Survivor 空间。每次使用 Eden 和其中的一块 Survivor

当回收时,将 Eden Survivor 中还存活的对象一次性拷贝到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才用过的 Survivor 空间。Hotspot 虚拟机默认 Eden Survivor 的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%80% + 10%),只有10%的内存是会被浪费的。

标记-整理算法(Mark-Compact-老年代

标记过程仍然与标记-清除算法一样,但不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后直接清理掉边界以外的内存

分代收集算法

根据对象的存活周期,将内存划分为几块。一般是把 Java 分为新生代老年代,这样就可以根据各个年代的特点,采用最适当的收集算法。

新生代:每次垃圾收集时会有大批对象死去,只有少量存活,所以选择复制算法,只需要少量存活对象的复制成本就可以完成收集。

老年代:对象存活率高、没有额外空间对它进行分配担保,必须使用标记-清理标记-整理算法进行回收。

9. Minor GC Full GC有什么区别?

Minor GC:新生代 GC,指发生在新生代的垃圾收集动作,因为 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般回收速度较快。 
Full GC:老年代 GC,也叫 Major GC,速度一般比 Minor GC  10 倍以上。

10. Java 内存

为什么要将堆内存分区?

对于一个大型的系统,当创建的对象及方法变量比较多时,即堆内存中的对象比较多,如果逐一分析对象是否该回收,效率很低。分区是为了进行模块化管理,管理不同的对象及变量,以提高 JVM 的执行效率。

堆内存分为哪几块?

Young Generation Space 新生区(也称新生代)

Tenure Generation Space养老区(也称旧生代)

Permanent Space 永久存储区

分代收集算法

内存分配有哪些原则?

对象优先分配在 Eden

大对象直接进入老年代

长期存活的对象将进入老年代

动态对象年龄判定

空间分配担保

Young Generation Space (采用复制算法)

主要用来存储新创建的对象,内存较小,垃圾回收频繁。这个区又分为三个区域:一个 Eden Space 和两个 Survivor Space

当对象在堆创建时,将进入年轻代的Eden Space

垃圾回收器进行垃圾回收时,扫描Eden SpaceA Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制 Old Gen

扫描A Suvivor Space时,如果对象已经经过了几次的扫描仍然存活JVM认为其为一个Old对象,则将其移到Old Gen

扫描完毕后,JVMEden SpaceA Suvivor Space清空,然后交换AB的角色(即下次垃圾回收时会扫描Eden SpaceB Suvivor Space

Tenure Generation Space(采用标记-整理算法)

主要用来存储长时间被引用的对象。它里面存放的是经过几次在 Young Generation Space 进行扫描判断过仍存活的对象,内存较大,垃圾回收频率较小。

Permanent Space

存储不变的类定义、字节码和常量等。

11. Class文件

Java虚拟机的平台无关性

Class文件的组成?

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目间没有任何分隔符。当遇到8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。

魔数与Class文件的版本

每个Class文件的4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件OxCAFEBABE

接下来是Class文件版本号:第5,6字节是次版本号(Minor Version),第7,8字节是主版本号(Major Version)。

使用JDK 1.7编译输出Class文件,格式代码为:

前四个字节为魔数,次版本号是0x0000,主版本号是0x0033,说明本文件是可以被1.7及以上版本的虚拟机执行的文件

33JDK1.7

32JDK1.6

31JDK1.5

30JDK1.4

2FJDK1.3

12. 类加载器

类加载器的作用是什么?

类加载器实现类的加载动作,同时用于确定一个类。对于任意一个类,都需要由加载它的类加载器这个类本身一同确立其在Java虚拟机中的唯一性。即使两个类来源于同一个Class文件,只要加载它们的类加载器不同,这两个类就不相等。

类加载器有哪些?

启动类加载器Bootstrap ClassLoader):使用C++实现(仅限于HotSpot),是虚拟机自身的一部分。负责将存放在\lib目录中的类库加载到虚拟机中。其无法被Java程序直接引用。

扩展类加载器Extention ClassLoader)由ExtClassLoader实现,负责加载\lib\ext目录中的所有类库,开发者可以直接使用。

应用程序类加载器Application ClassLoader):由APPClassLoader实现。负责加载用户类路径(ClassPath)上所指定的类库

13. 类加载机制

什么是双亲委派模型?

双亲委派模型(Parents Delegation Model)要求除了顶层的启动类加载器外,其余加载器都应当有自己的父类加载器。类加载器之间的父子关系,通过组合关系复用。 
工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有到父加载器反馈自己无法完成这个加载请求(它的搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载。

为什么要使用双亲委派模型,组织类加载器之间的关系?

Java类随着它的类加载器一起具备了一种带优先级的层次关系。比如java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,因此Object类在程序的各个类加载器环境中,都是同一个类。

如果没有使用双亲委派模型,让各个类加载器自己去加载,那么Java类型体系中最基础的行为也得不到保障,应用程序会变得一片混乱。

什么是类加载机制

Class文件描述的各种信息,都需要加载到虚拟机后才能运行。虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

14. 虚拟机和物理机的区别是什么?

这两种机器都有代码执行的能力,但是:

物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面的。

虚拟机的执行引擎是自己实现的,因此可以自行制定指令集和执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。

15. 运行时栈帧结构

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构, 存储了方法的

局部变量表

操作数栈

动态连接

方法返回地址

每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

16. Java 方法调用

什么是方法调用?

方法调用唯一的任务是确定被调用方法的版本<