写点什么

java 培训 JVM 基础面试题分享

作者:@零度
  • 2022 年 3 月 18 日
  • 本文字数:5100 字

    阅读完需:约 17 分钟

为什么要学习 JVM?

深入理解 JVM 可以帮助我们从平台角度提高解决问题的能力,例如,有效防止内存泄漏(Memory leak),优化线程锁的使用 (Thread Lock),更加高效的进行垃圾回收 (Garbage collection),提高系统吞吐量 (throughput),降低延迟(Delay),提高其性能(performance)等。

你是如何理解 JVM 的?

JVM 是 Java Virtual Machine 的缩写,顾名思义,它是一个虚拟计算机,是硬件计算机的抽象(虚构)实现,是 JAVA 平台的一部分,如图所示(见图中的最底端):



JVM 是 Java 程序能够实现跨平台的基础(Java 的跨平台本质上是通过不同平台的 JVM 实现的),它的作用是加载 Java 程序,把字节码(bytecode)翻译成机器码再交由 CPU 执行。如图所示:



程序在执行之前先要把 Java 代码(.java)转换成字节码(.class),JVM 通过类加载器(ClassLoader)把字节码加载到内存中,但字节码文件是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine) 将字节码翻译成底层机器码,再交由 CPU 去执行java培训机构

市场上有哪些主流的 JVM 呢?

JVM 是一种规范,基于这种规范,不同公司做了具体实现,BEA 公司研发 JRockit VM ,后在 2008 年由 Oracle 公司收购;IBM 公司研发了 J9 VM ,只应用于 IBM 内部。Sun 公司研发了 HotSpot VM ,后在 2010 年由 Oracle 公司收购。目前是甲骨文公司最主流的一款 JVM 虚拟机,也是我们现在最常用的一种。

JVM 的体系结构是怎样的?

JVM 的体系结构,如图所示:


类加载系统 (ClassLoader System)负责加载类到内存;运行时数据区 (Runtime Data Area)负责存储对象数据信息;执行引擎(Execution Engine)负责调用对象执行业务;本地库接口(Native Interface)负责融合不同的编程语言为 Java 所用。

JVM 有哪些运行模式吗?

JVM 有两种运行模式 Server 与 Client。两种模式的区别在于,Client 模式启动速度较快,Server 模式启动较慢;但是启动进入稳定期之后 Server 模式的程序运行速度比 Client 要快很多。这是因为 Server 模式启动的 JVM 采用的是重量级的虚拟机,对程序采用了更多的优化;而 Client 模式启动的 JVM 采用的是轻量级的虚拟机。所以 Server 启动慢,但稳定后速度比 Client 远远要快。

现在 64 位的 jdk 中默认都是 server 模式(可通过 java -version 进行查看)。当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器, 而 server 模式启动的虚拟机采用相对重量级,代号为 C2 的编译器.c1、c2 都是 JIT 编译器, C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。

JVM 运行时内存结构是怎样的?

不同虚拟机实现可能略微有所不同,但都会遵从 Java 虚拟机规范,Java 8 虚

拟机规范规定,Java 虚拟机所管理的内存将会包括以下几个区域,如图所示:



  • Java 堆(Heap)

Java 堆(Java Heap)是 JVM 中内存最大的一块,被所有线程共享的,在虚拟机启动时创建,主要用于存放对象实例,大部分对象实例也都是在这里分配。随着 JIT 编译器的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化的技术将会导致一些微妙的变化,所有的对象都分配在堆上渐渐变得不那么绝对了。小对象未逃逸还可以在直接在栈上分配。如果在堆中没有内存完成实例分配,并且堆已不可以再进行扩展时,系统底层运行时将会抛出 OutOfMemoryError。Java 虚拟机规范规定,Java 堆可以处在物理上不连续的内存空间中,只要逻辑上连续即可,就像我们的磁盘空间一样。在实现上也可以是固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是可扩展的,通过 -Xmx 和 -Xms 参数定义堆内存大小就java培训

  • 方法区(Method Area)

方法区(Methed Area)是一种规范,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。不同 jdk,方法区的实现不同,HotSpot 虚拟机在 JDK 8 中使用 Native Memory 来实现方法区。当方法无法满足内存分配需求时会抛出 OutOfMemoryError 异常。

  • Java 虚拟机栈(VM Stack)

Java 虚拟机栈(Java Virtual Machine Stacks)描述的是 Java 方法执行时的内存模型,每个方法在被线程调用时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。如果线程请求的栈深度大于虚拟机所允许的栈深度就会抛出 StackOverflowError 异常。如果虚拟机是可以动态扩展的,如果扩展时无法申请到足够的内存就会抛出 OutOfMemoryError 异常。

  • JVM 本地方法栈 (Native Method Stack)

本地方法栈(Native Method Stack)与虚拟机栈的作用类似,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的。在 Java 虚拟机规范中对于本地方法栈没有特殊的要求,虚拟机可以自由的实现它,因此在 Sun HotSpot 虚拟机直接把本地方法栈和虚拟机栈合二为一了。

  • JVM 程序计数器(Program Counter Register)

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解析器的工作是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于 JVM 的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,也就是任何时刻,一个处理器(或者说一个内核)都只会执行一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每个线程都有独立的程序计数器。

如果线程正在执行 Java 中的方法,程序计数器记录的就是正在执行虚拟机字节码指令的地址,如果是 Native 方法,这个计数器就为空(undefined),因此该内存区域是唯一一个在 Java 虚拟机规范中没有规定 OutOfMemoryError 的区域。

如何理解 JVM 中的 GC 系统?

追踪仍然使用的所有对象,并将其余对象标记为垃圾,然后进行回收,这个过程称之为 GC(垃圾回收).所有的 GC 系统可从 GC 判断策略(例如引用计数,对象可达性分析),GC 收集算法(标记-清除,标记-清除-整理,标记-复制-清除,分代),GC 收集器(例如 Serial,Parallel,CMS,G1)等方面进行学习

JVM 引用链中可以作为 Root 的对象?

  • Java 虚拟机栈中的引用对象;

  • 本地方法栈中 JNI(既一般说的 Native 方法)引用的对象;

  • 方法区中类静态常量的引用对象;

  • 方法区中常量的引用对象。

JVM 中常见垃圾回收算法有哪些?

  • 引用计数器算法

这个算法是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象的时候,计数器就加 1,与之相反,每当引用失效的时候就减 1。也就是以计数来判断对象是否为垃圾。例如:


引用计数法,有一个很大的缺陷就是循环引用,例如:



  • 可达性分析算法

这个算法的核心思路就是通过一系列的“GC Roots”对象作为起始点,从这些对象开始往下搜索,搜索所经过的路径称之为“引用链”。当一个对象到 GC Roots 没有任何引用链相连的时候,证明此对象是可以被回收的。例如:



  • 复制算法

这个算法是将内存分为大小相同的两块,当这一块使用完了,就把当前存活的对象复制到另一块,然后一次性清空当前区块。此算法的缺点是只能利用一半的内存空间。例如:



  • 标记-清除算法

这个算法执行分两阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。例如:



  • 标记-整理算法

这个算法结合了“标记-清除”和“复制”两个算法的优点。第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把存活对象“压缩”复制到堆的其中一块空间中,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题,例如:



JVM 对象引用都有哪些类型?

不管是引用计数法还是可达性分析算法都与对象的“引用”有关[说说 Java 中的四大引用类型。],这说明对象的引用决定了对象的生死,对象的引用关系如下。

  • 强引用

在代码中普遍存在的,类似 Object obj = new Object() 这类引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

  • 软引用

是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象,JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。



  • 弱引用

非必需对象,但它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。



  • 虚引用

也称为幽灵引用或幻影引用,是最弱的一种引用关系,无法通过虚引用来获取一个对象实例,为对象设置虚引用的目的只有一个,就是当这个对象被收集器回收时收到一条系统通知。

JVM 垃圾回收器的分类都有哪些?

  • 新生代回收器

Serial、ParNew、Parallel Scavenge

  • 老年代回收器

Serial Old、Parallel Old、CMS

  • 整堆回收器

G1 垃圾回收器

分代垃圾回收器的组成部分有哪些?

分代垃圾回收器是由新生代(Young Generation)和老生代(Tenured Generation)组成的,默认情况下新生代和老生代的内存比例是 1:2。

新生代的组成部分有哪些?:

新生代是由:Eden、Form Survivor、To Survivor 三个区域组成的,它们内存默认占比是 8:1:1,如图所示:



新生代垃圾回收是怎么执行的?

第一步将 Eden 和 From Survivor 活着的对象复制到 To Survivor 区;第二步将清空 Eden 和 From Survivor 分区;第三步将 From Survivor 和 To Survivor 分区交换(From 变 To,To 变 From)。当新生代的 Survivor 分区为 2 个的时候,不论是空间利用率还是程序运行的效率都是最优的。

谈谈 JVM 中的 CMS 垃圾回收器?

CMS(Concurrent Mark and Sweep)是并发标记和清除垃圾收集器。它会使用空闲列表(free-lists)管理内存空间的回收,不对老年代进行整理。其优点是在标记、清除阶段的大部分工作和应用线程一起并发执行。可以降低延迟,缩短停顿时间,提高服务的响应时间。当然也有缺陷,主要表现在,对 CPU 资源要求敏感,无法清除浮动垃圾(浮动垃圾指的是 CMS 清除垃圾的时候,还有用户线程产生新的垃圾,这部分未被标记的垃圾叫做“浮动垃圾”,只能在下次 GC 的时候进行清除),还会产生大量空间碎片。

谈谈 JVM 中的是 G1 垃圾回收器?

G1(Garbage-First GC)是一款实时收集器,其设计目标是将 STW 停顿时间和分布变成可预期以及可配置的。可以说是一种兼顾吞吐量和停顿时间的 GC 实现。G1 可以直观的设定停顿时间的目标,相比于 CMS ,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。

使用 G1 收集器时,Java 堆的内存布局与其他收集器有很大差别,它将整个 Java 堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region 的集合,例如:



这样的划分使得 GC 不必每次都去收集整个堆空间, 而是以增量的方式来处理,每次只处理一部分小堆区,称为此次的回收集(collection set). 每次暂停都会收集所有年轻代的小堆区, 同时也可能只包含一部分老年代小堆区。

G1 的另一项创新, 是在并发阶段估算每个小堆区存活对象的总数。用来构建回收集(collection set)的原则是: 垃圾最多的小堆区会被优先收集。这也是 G1 名称的由来:garbage-first。

G1 解决了 CMS 中的各种疑难问题, 包括暂停时间的可预测性, 并终结了堆内存的碎片化。对单业务延迟非常敏感的系统来说, 如果 CPU 资源不受限制,那么 G1 可以说是 HotSpot 中最好的选择, 特别是在最新版本的 Java 虚拟机中。当然,这种降低延迟的优化也不是没有代价的: 由于额外的写屏障(write barriers)和更积极的守护线程, G1 的开销会更大。所以, 如果系统属于吞吐量优先型的,又或者 CPU 持续占用 100%, 而又不在乎单次 GC 的暂停时间, 那么 CMS 是更好的选择。

JVM 垃圾回收的调优参数有哪些?

-Xmx:512 设置最大堆内存为 512 M;

-Xms:256 初始堆内存(最小堆)为 256 M;

-XX:MaxNewSize 设置最大年轻代内存;

-XX:MaxTenuringThreshold=6 设置新生代对象经过 6 次 GC 晋升到老年代;

-XX:PretrnureSizeThreshold 设置大对象的值,超过这个值的大对象直接进入老生代;

-XX:NewRatio 设置分代垃圾回收器新生代和老生代内存占比;

-XX:SurvivorRatio 设置新生代 Eden、Form Survivor、To Survivor 占比。

JVM 现代并发 GC 有什么调优原则

第一要空间换时间与效率,针对 G1 & ZGC 加大堆内存(更多的空余空间)的配置往往更有利 于 GC 达到目标暂停时间。第二要知道低暂停不代表高吞吐量,并发 GC 是保证并发阶段 GC 的同时业务线程依然有几率获得 CPU 时间片,但同时也意味着 GC 会与业务线程抢占计算资源,且往往更多的并发阶段为了处理更多的同步问题,也会占用更多的计算资源。第三是 GC 调优永远要考虑机器资源,对应系统应用场景等等,至少目前没有银弹。

文章来源于 jason

用户头像

@零度

关注

关注尚硅谷,轻松学IT 2021.11.23 加入

还未添加个人简介

评论

发布
暂无评论
java培训JVM基础面试题分享_Java_@零度_InfoQ写作平台