4.JVM面试题

73 阅读8分钟

1. 说说JVM的内存模型以及分区

JVM内存区域分为栈、堆、方法区

  1. 栈(线程私有)

  • 虚拟机栈:java方法执行时会创建一个栈帧,用于存储**局部变量表操作数栈动态链接方法出口**

    1. 局部变量表:存放方法参数和方法内定义的局部变量

    2. 操作数栈:保存计算过程中间结果,同时作为计算过程中变量临时的存储空间

    3. 动态链接:将方法调用的符号引用转化为方法的直接引用

    4. 方法出口:返回方法被调用的位置,继续执行下面的程序

  • 本地方法栈:本地方法栈和虚拟机栈类似,Java 虚拟机栈是给 JVM 使⽤的,本地⽅法栈是给本地⽅法使⽤的

  • 程序计数器:记录当前线程执行的行号

  1. 堆(线程共享):堆主要用于存放各种类的实例对象和数组

  2. 方法区(线程共享):主要是存储类信息,常量池(static 常量和 static 变量),编译后的代码(字节码)等数据

2.说说JVM中的堆内存

堆是JVM中最大的一块内存。在java中被分为两个区域:年轻代和老年代(java8 取消了永久代,采用了 Metaspace)。

img

  • 新生代
    1.新生代采用的是复制算法。
    2.新生代先分为eden,s0,s1三块区域,新对象一般都分配在eden区域。
    3.如果eden区域满了触动垃圾回收,就把eden存活的对象复制到S0区域,然后清除掉eden区域。
    4.在下一次垃圾收集时,如果eden区域满了,S0和eden区域的存活对象会被复制到S1中,然后eden和S0会被清空。
    5.在这个过程中,幸存的对象会随着每次触动垃圾收集,年龄也在增长。当这些存活的对象年龄达到了新生代年龄阈值(HotSpot默认的垃圾回收是15次),就会进入到下一个区域,老年代。
  • 老年代
    老年代中使用“标记-清除”或者“标记-整理”算法进行垃圾回收,回收次数相对较少,每次回收时间比较长。在垃圾收集整个过程中,不管是处于新生代,还是老年代,都必须记住**Stop the World 事件** ,就是说在这种事件发生时,所有的程序线程都要暂停,直到事件完成(比如这里就是完成了所有回收工作)为止。Major GC 也会触发STW(Stop the World)。通常,Major GC会慢很多,因为它涉及到所有存活对象。所以,对于响应性的应用程序,应该尽量避免Major GC。
  • 永久代(java8之后没有了,改为Metaspace)
    包含JVM用于描述应用程序中类和方法的元数据。永久代是由JVM在运行时根据应用程序使用的类来填充的。如果JVM发现某些类不再需要,并且其他类可能需要空间,则这些类可能会被回收。

3. 说说JVM类加载过程

JVM 的类加载分为五个阶段:

  1. 加载:JVM将字节码文件(.class)读入内存
  2. 验证:验证 Class 字节流的数据是否遵守JVM的规定
  3. 准备:正式为类变量(静态变量)分配内存并设置初始值,并非代码中设置的值
  4. 解析:将常量池中的符号引用解析为直接引用
  5. 初始化:真正执行类中定义的java代码

4. JVM类加载器有哪几种

  1. 引导类加载器BootStrap ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下核心类库。
  2. 扩展类加载器Extension ClassLoader:负责加载支撑JVM运行的位于JRE的lib目录下的 ext扩展目录中的JAR包
  3. 应用类加载器Application ClassLoader:负责加载ClassPath路径下的类包,主要就是加 载你自己写的那些类
  4. 自定义类加载器User ClassLoader:负责加载用户自定义路径下的类包

5. 什么是双亲委派模型

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。

  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);)。或者是调用bootstrap类加载器来加载。

  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

  • 双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载

  • 优点:

  • 安全性:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改

  • 唯一性:采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的引导类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象

6.垃圾回收时如何判断一个对象是否死亡?

  1. 引用计数法
引用计数描述的算法为: 给对象增加一个<font color=yellow>引用计数器</font>,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"

缺点:无法解决对象的循环引用问题

  1. 可达性分析算法:
将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾 对象,其余未标记的对象都是垃圾对象。

在Java语言中,可作为GC Roots的对象包含下面几种:(栈,方法区)

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象

  2. 方法区中静态属性引用的对象

  3. 方法区中常量引用的对象

  4. 本地方法栈中JNI(Native方法)引用的对象

7. 垃圾回收算法有哪些?

  1. 标记-清除算法

算法分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象

优点:

  1. 简单

缺点:

  1. 效率低
  2. 内存碎片问题
  1. 复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一 块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

优点:

  • 性能⾼

缺点:

  • 内存利用率低
  1. 标记-整理算法

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

优点:

  1. 不会产生内存碎片

缺点:

  1. 标记-整理算法要低于复制算法,移动过程中,需要全程暂停用户应用程序。即:STW。
  1. 分代收集算法:

现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除

8. 说说Minor GC、Major GC、Full GC

JVM在进行GC时,并非每次都对三个内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。

针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集Partial GC),一种是整堆回收Full GC

部分收集: 不是完整收集整个Java堆的垃圾收集。其中又分为:

  • 新生代收集(Minor GC / Young GC): 只是新生代(Eden \ S0,S1)的垃圾收集

  • 老年代收集(Major GC / Old GC): 只是老年代的垃圾收集

  • 混合收集(Mixed GC): 收集整个新生代以及部分老年代的垃圾收集

整堆收集(Full GC): 收集整个Java堆和方法区的垃圾收集

9. 常见的垃圾回收器有哪些?

img