征服面试官:Java 虚拟机(JVM)掌握这篇面试题汇总,吊打面试官!

1、简述 JVM 垃圾回收机制

在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

2、JVM 的内存结构

image

  • Java 堆(Heap) 是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
  • 方法区(Method Area) 方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 程序计数器(Program Counter Register) 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
  • JVM 栈(JVM Stacks) 与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • 本地方法栈(Native Method Stacks) 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。

线程私有是不会发生 gc,并且他们是随线程生随线程灭,即程序计数器、本地方法栈和虚拟机栈。

3、如何判断对象可以被回收?

  • 引用计数算法

    原理:通过一个计数器对对象进行计数,对象被引用时+1,引用失效时-1;当计数为 0 时则说明可以被回收;

    缺点:很难解决对象的相互循环引用问题

  • 可达性分析算法
    Java 虚拟机所采用的算法;

    原理:通过一些列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。

    那么哪些对象可以被称为 gc roots 呢----虚拟机栈(栈中的本地变量列表)/方法区静态属性/方法区常量引用/本地方法栈中 JNI 所引用的的对象都是可以作为 gc roots 的

4、GC 回收机制–如何回收

  • 标记清除算法

    清除算法分成 2 个阶段–标记和清除; 标记阶段对所有存活的阶段进行标记,标记完成后,再扫描整个空间未标记对象,直接回收不存活的对象.

    优点:大多数情况下比较高效,缺点是会造成内存碎片,碎片太多导致后面过程中对大内存的分配无足够空间时而提前猝发一次垃圾回收动作;

  • 复制算法

    将可用内存将容量划分成大小相等的 2 块,每次清理时将其中 A 内存还存活的对象复制到 B 内存里面,然后再把 A 中清理掉;

    优点:高效且并不产生碎片

    缺点:牺牲了一半的内存为代价适用存活对象少,回收对象多

  • 标记整理算法

    该算法标记阶段和标记清除算法一样,完成标记后它不是直接清理可回收对象,而是将存活对象都向一端移动最后清理掉端边界意外的内存;

    适用于存活对象多,回收对象少的情况

  • 分代收集算法

    整合了复制算法和标记整理算法,根据新生代和老年代的不同特性采取上面的不同算法

    新生代 生命周期短,每次回收时都有大量垃圾对象需要回收 复制算法

    老年代 每次只有少量的对象需要回收 标记整理算法

深入理解分代回收算法

  • 复制算法中内存划分其实并不是按照 1:1 来划分老年代和新生代,而是按照 8:1:1 分一个大的 Eden 区和两个小的 survivor 的空间
  • 为什么需要 2 个 Survivor 区 新生代一般经历 15 次 Gc 就可以移到老年代.当第一次 gc 时,我们可以把 Eden 的存活对象放入 Survivor A 空间,第二次 Gc 时,Survivor A 也要使用复制算法,存活对象放到 Survivor B 上,第三次 gc 时,又将 Survivor B 对象复制到 Survivor A 上如此循环往复;
  • 为什么 Eden 这么大,因为新生代中存活的对象,需要转移的 Survivor 的对象不多,算是缓解了复制算法的缺点;

5、JVM 的两个内存:栈内存和堆内存的区别是什么?

Java 把内存划分成两种:一种是栈内存,一种是堆内存。两者的区别是:

  • 栈内存:在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
  • 堆内存:堆内存用来存放由 new 创建的对象和数组。在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。

6、类加载器相关

  • 启动类加载器:Bootstrap ClassLoader,负责加载存放在 JDK\jre\lib(JDK 代表 JDK 的安装目录,下同)下,或被-Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库
  • 扩展类加载器:Extension ClassLoader,该加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 DK\jre\lib\ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如 javax.*开头的类),开发者可以直接使用扩展类加载器。
  • 应用程序类加载器:Application ClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器

7、Dalvik 和 Java 虚拟机(JVM)有什么区别?

  • Dalvik 基于寄存器
  • JVM 基于栈
  • 基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。
  • JVM 字节码中,局部变量会被放入局部变量表中,继而被压入堆栈供操作码进行运算,当然 JVM 也可以只使用堆栈而不显式地将局部变量存入变量表中。
  • Dalvik 字节码中,局部变量会被赋给 65536 个可用的寄存器中的任何一个,Dalvik 指令直接操作这些寄存器,而不是访问堆栈中的元素。

8、GC 会造成什么影响?

  • 在开始学习 GC 之前你应该知道一个词:stop-the-world。不管选择哪种 GC 算法,stop-the-world 都是不可避免的。
  • 也就是说,当垃圾回收开始清理资源时,其余的所有线程都会被停止。所以,我们要做的就是尽可能的让它执行的时间变短。如果清理的时间过长,在我们的应用程序中就能感觉到明显的卡顿。

9、什么情况下 GC 会执行?

总的来说,有两个条件会触发主 GC:

  • 当应用程序空闲时,即没有应用线程在运行时,GC 会被调用。因为 GC 在优先级最低的线程中进行,所以当应用忙时,GC 线程就不会被调用,但以下条件除外。
  • Java 堆内存不足时,GC 会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM 就会强制地调用 GC 线程,以便回收内存用于新的分配。若 GC 一次之后仍不能满足内存分配的要求,JVM 会再进行两次 GC 作进一步的尝试,若仍无法满足要求,则 JVM 将报“out of memory”的错误,Java 应用将停止。

由于是否进行主 GC 由 JVM 根据系统环境决定,而系统环境在不断的变化当中,所以主 GC 的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主 GC 是反复进行的。

猜你喜欢

转载自blog.csdn.net/jaynm/article/details/107854707