作为Java程序员,我们所有的程序都运行在Java虚拟机上,只有对Java虚拟机底层原理进行深入的学习,不管是JVM内存区域、堆的分代与垃圾回收算法、JVM性能调优。在全面理解JVM的运行机制后,我们就能成为更优秀的Javaer。

今天给大家讲讲面试中必问的一些JVM面试解析,同时对于JVM与性能调优,自己也整理过一份万字文档笔记,内容包括内存区域,垃圾回收,性能调优,GC,类加载机制以及平时工作问题的一些调优笔记记录!有需要的朋友麻烦帮忙转发下,添加VX【msbjy2019】即可获取免费领取方式!

 

1.JVM的内存模型以及分区情况和作用

如下图所示:

 

黄色部分为线程共有,蓝色部分为线程私有。

方法区

用于存储虚拟机加载的类信息,常量,静态变量等数据。

存放对象实例,所有的对象和数组都要在堆上分配。 是 JVM 所管理的内存中最大的一块区域。

Java 方法执行的内存模型:存储局部变量表,操作数栈,动态链接,方法出口等信息。生命周期与线程相同。

本地方法栈

作用与虚拟机栈类似,不同点本地方法栈为 native 方法执行服务,虚拟机栈为虚拟机执行的 Java 方法服务。

程序计数器

当前线程所执行的行号指示器。是 JVM 内存区域最小的一块区域。执行字节码工作时就是利用程序计数器来选取下一条需要执行的字节码指令。

2.如何判断对象已死?

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。

 

2.1 引用计数法

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

这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

public class ReferenceCountingGc {
    Object instance = null;
	public static void main(String[] args) {
		ReferenceCountingGc objA = new ReferenceCountingGc();
		ReferenceCountingGc objB = new ReferenceCountingGc();
		objA.instance = objB;
		objB.instance = objA;
		objA = null;
		objB = null;

	}
}

2.2可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

 

3.方法区回收

判断常量是否废弃可以判断是否有地方引用这个常量,如果没有引用则为废弃的常量。

判断类是否废弃需要同时满足如下条件:

  • 该类所有的实例已经被回收(堆中不存在任何该类的实例)。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象在任何地方没有被引用(无法通过反射访问该类的方法)。

3.垃圾回收算法有几种类型? 他们对应的优缺点又是什么?

常见的垃圾回收算法有:

标记-清除算法、复制算法、标记-整理算法、分代收集算法

3.1标记-清除算法

标记—清除算法包括两个阶段:“标记”和“清除”。 标记阶段:确定所有要回收的对象,并做标记。 清除阶段:将标记阶段确定不可用的对象清除。

缺点:

  1. 标记和清除的效率都不高。
  2. 会产生大量的碎片,而导致频繁的回收。

3.2复制算法

内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候, 把存活的对象复制到另一块上,然后把这块内存整个清理掉。

缺点:

  1. 需要浪费额外的内存作为复制区。
  2. 当存活率较高时,复制算法效率会下降。

3.3标记-整理算法

标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存。

缺点: 算法复杂度大,执行步骤较多

3.4分代收集算法

目前大部分 JVM 的垃圾收集器采用的算法。根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为新生代( Young Generation 和老年代( Tenured Generation ),永久代( Permanet Generation )。

老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

如下图所示:

 

  • Young:存放新创建的对象,对象生命周期非常短,几乎用完可以立即回收,也叫 Eden 区。
  • Tenured: young 区多次回收后存活下来的对象将被移到 tenured 区,也叫 old 区。
  • Perm:永久带,主要存加载的类信息,生命周期长,几乎不会被回收。

缺点: 算法复杂度大,执行步骤较多。

4.JVM 中垃圾收集器有哪些? 他们特点分别是什么?

新生代垃圾收集器

Serial 收集器

特点: Serial 收集器只能使用一条线程进行垃圾收集工作,并且在进行垃圾收集的时候,所有的工作线程都需要停止工作,等待垃圾收集线程完成以后,其他线程才可以继续工作。

使用算法:复制算法

ParNew 收集器

特点: ParNew 垃圾收集器是Serial收集器的多线程版本。为了利用 CPU 多核多线程的优势,ParNew 收集器可以运行多个收集线程来进行垃圾收集工作。这样可以提高垃圾收集过程的效率。

使用算法:复制算法

Parallel Scavenge 收集器

特点: Parallel Scavenge 收集器是一款多线程的垃圾收集器,但是它又和 ParNew 有很大的不同点。

Parallel Scavenge 收集器和其他收集器的关注点不同。其他收集器,比如 ParNew 和 CMS 这些收集器,它们主要关注的是如何缩短垃圾收集的时间。而 Parallel Scavenge 收集器关注的是如何控制系统运行的吞吐量。这里说的吞吐量,指的是 CPU 用于运行应用程序的时间和 CPU 总时间的占比,吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间)。如果虚拟机运行的总的 CPU 时间是 100 分钟,而用于执行垃圾收集的时间为 1 分钟,那么吞吐量就是 99%。

使用算法:复制算法

老年代垃圾收集器

Serial Old 收集器

特点: Serial Old 收集器是 Serial 收集器的老年代版本。这款收集器主要用于客户端应用程序中作为老年代的垃圾收集器,也可以作为服务端应用程序的垃圾收集器。

使用算法:标记-整理

Parallel Old 收集器

特点: Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本这个收集器是在 JDK1.6 版本中出现的,所以在 JDK1.6 之前,新生代的 Parallel Scavenge 只能和 Serial Old 这款单线程的老年代收集器配合使用。Parallel Old 垃圾收集器和 Parallel Scavenge 收集器一样,也是一款关注吞吐量的垃圾收集器,和 Parallel Scavenge 收集器一起配合,可以实现对 Java 堆内存的吞吐量优先的垃圾收集策略。

使用算法:标记-整理

CMS 收集器

特点: CMS 收集器是目前老年代收集器中比较优秀的垃圾收集器。CMS 是 Concurrent Mark Sweep,从名字可以看出,这是一款使用"标记-清除"算法的并发收集器。

CMS 垃圾收集器是一款以获取最短停顿时间为目标的收集器。如下图所示:

 

从图中可以看出,CMS 收集器的工作过程可以分为 4 个阶段:

  • 初始标记(CMS initial mark)阶段
  • 并发标记(CMS concurrent mark)阶段
  • 重新标记(CMS remark)阶段
  • 并发清除((CMS concurrent sweep)阶段

使用算法:复制+标记清除

G1 垃圾收集器

特点: 主要步骤:初始标记,并发标记,重新标记,复制清除。

使用算法:复制 + 标记整理

更多面试中问到的JVM问题,你知道多少呢!由于篇幅有限,可以看整理的JVM与性能调优笔记!有需要的朋友麻烦帮忙转发下,添加VX【msbjy2019】即可获取!

  • 内存溢出OOM异常排查
  • CPU资源占用过高
  • JVM参数调优
  • GC回收及原理
  • GC中的STW现象
  • ......