【Java 面试 八股文】JVM 虚拟机篇

JVM 虚拟机篇

  • [1. JVM组成](#1. JVM组成)
    • [1.1 JVM由那些部分组成,运行流程是什么?](#1.1 JVM由那些部分组成,运行流程是什么?)
    • [1.2 什么是程序计数器?](#1.2 什么是程序计数器?)
    • [1.3 你能给我详细的介绍Java堆吗?](#1.3 你能给我详细的介绍Java堆吗?)
    • [1.4 Java 虚拟机栈](#1.4 Java 虚拟机栈)
      • [1.4.1 Java Virtual machine Stacks (java 虚拟机栈)](#1.4.1 Java Virtual machine Stacks (java 虚拟机栈))
      • [1.4.2 栈和堆的区别](#1.4.2 栈和堆的区别)
      • [1.4.3 垃圾回收是否涉及栈内存?](#1.4.3 垃圾回收是否涉及栈内存?)
      • [1.4.4 栈内存分配越大越好吗?](#1.4.4 栈内存分配越大越好吗?)
      • [1.4.5 方法内的局部变量是否线程安全?](#1.4.5 方法内的局部变量是否线程安全?)
      • [1.4.6 栈内存溢出情况](#1.4.6 栈内存溢出情况)
    • [1.5 能不能解释一下方法区?](#1.5 能不能解释一下方法区?)
      • [1.5.1 概述](#1.5.1 概述)
      • [1.5.2 运行时常量池](#1.5.2 运行时常量池)
      • [1.5.3 方法区中的方法的执行过程?](#1.5.3 方法区中的方法的执行过程?)
    • [1.6 你听过直接内存吗?](#1.6 你听过直接内存吗?)
  • [2. 类加载器](#2. 类加载器)
    • [2.1 什么是类加载器](#2.1 什么是类加载器)
    • [2.2 类加载器种类](#2.2 类加载器种类)
    • [2.3 什么是双亲委派模型?](#2.3 什么是双亲委派模型?)
    • [2.4 JVM为什么采用双亲委派机制(作用)](#2.4 JVM为什么采用双亲委派机制(作用))
    • [2.5 说一下类装载的执行过程](#2.5 说一下类装载的执行过程)
  • [3. 垃圾回收](#3. 垃圾回收)
    • [3.1 对象什么时候可以被垃圾器回收](#3.1 对象什么时候可以被垃圾器回收)
    • [3.2 VM 垃圾回收算法有哪些?](#3.2 VM 垃圾回收算法有哪些?)
      • [3.2.1 标记清除算法](#3.2.1 标记清除算法)
      • [3.2.2 标记整理算法](#3.2.2 标记整理算法)
      • [3.2.3 复制算法](#3.2.3 复制算法)
    • [3.3 分代回收算法](#3.3 分代回收算法)
      • [MinorGC、 Mixed GC 、 FullGC的区别是什么](#MinorGC、 Mixed GC 、 FullGC的区别是什么)
    • [3.4 说一下 JVM 有哪些垃圾回收器?](#3.4 说一下 JVM 有哪些垃圾回收器?)
      • [3.4.1 串行垃圾收集器](#3.4.1 串行垃圾收集器)
      • [3.4.2 并行垃圾收集器](#3.4.2 并行垃圾收集器)
      • [3.4.3 CMS(并发)垃圾收集器](#3.4.3 CMS(并发)垃圾收集器)
      • [3.4.4 G1垃圾回收器](#3.4.4 G1垃圾回收器)
    • [3.5 强引用、软引用、弱引用、虚引用的区别?](#3.5 强引用、软引用、弱引用、虚引用的区别?)
  • [4. JVM 实践](#4. JVM 实践)
    • [4.1 JVM 调优的参数都有哪些?](#4.1 JVM 调优的参数都有哪些?)
    • [4.2 java内存泄露的排查思路?](#4.2 java内存泄露的排查思路?)
    • [4.3 服务器CPU持续飙高,你的排查方案与思路?](#4.3 服务器CPU持续飙高,你的排查方案与思路?)

1. JVM组成

1.1 JVM由那些部分组成,运行流程是什么?

难易程度:☆☆☆

出现频率:☆☆☆☆

JVM是什么

Java Virtual Machine Java程序的运行环境(java二进制字节码的运行环境)

  • 一次编写,到处运行
  • 自动内存管理,垃圾回收机制

从图中可以看出 JVM 的主要组成部分

  • ClassLoader(类加载器)
  • Runtime Data Area(运行时数据区,内存分区)
  • Execution Engine(执行引擎)
  • Native Method Library(本地库接口)

运行流程:

  1. 类加载器(ClassLoader)把Java代码转换为字节码
  2. 运行时数据区(Runtime Data Area)把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层系统去执行,而是有执行引擎运行
  3. 执行引擎(Execution Engine)将字节码翻译为底层系统指令,再交由CPU执行去执行,此时需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的功能。

1.2 什么是程序计数器?

难易程度:☆☆☆

出现频率:☆☆☆☆

程序计数器(PC Register):线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。

javap -verbose xx.class 打印堆栈大小,局部变量的数量和方法的参数。

1.3 你能给我详细的介绍Java堆吗?

难易程度:☆☆☆

出现频率:☆☆☆☆

线程共享的区域:Java堆 (Heap) 是Java虛拟机中内存管理的一个重要区域,主要用于存放对象实例数组 。当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。

  • 新生代(Young Generation):新生代分为Eden Space和Survivor Space。在Eden Space中,大多数新创建的对象首先存放在这里。Eden区相对较小,当Eden区满时,会触发一次 Minor GC(新生代垃圾回收)。在Survivor Spaces中,通常分为两个相等大小的区域,称为 S0(Survivor 0) 和 S1(Survivor1)。在每次Minor GC后,存活下来的对象会被移动到其中一个Sunvivor空间,以继续它们的生命周期。
  • 老年代(Old Generation/Tenured Generation):存放过一次或多次Minor GC仍存活的对象会被移动到老年代。老年代中的对象生命周期较长,因此Major Gc(也称为Ful GC,涉及老年代的垃圾回收)发生的频率相对较低,但其执行时间通常比Minor Gc长。老年代的空间通常比新生代大,以存储更多的长期存活对象。
  • 元空间保存的类信息、静态变量、常量、编译后的代码。

为了避免方法区出现OOM,所以在java8中将堆上的方法区【永久代】给移动到了本地内存上,重新开辟了一块空间,叫做元空间。那么现在就可以避免掉OOM的出现了。

1.4 Java 虚拟机栈

与程序计数器一样,Java 虚拟机栈(后文简称栈)也是线程私有的,它的生命周期和线程相同,随着线程的创建而创建,随着线程的死亡而死亡。

1.4.1 Java Virtual machine Stacks (java 虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈,先进后出。
  • 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存。每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出。
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

1.4.2 栈和堆的区别

  • 在JVM内存模型中,栈(Stack)主要用于管理线程的局部变量和方法调用的上下文,而堆(Heap)则是
    用于存储所有类的实例和数组。堆会GC垃圾回收,而栈不会。
  • 栈内存是线程私有的,而堆内存是线程共有的。
  • 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常
    • 栈空间不足: java.lang.StackOverFlowError。
    • 堆空间不足: java.lang.OutOfMemoryError。

1.4.3 垃圾回收是否涉及栈内存?

垃圾回收主要指就是堆内存,当栈帧弹栈以后,内存就会释放。

1.4.4 栈内存分配越大越好吗?

未必,默认的栈内存通常为1024k。栈帧过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个,如果把栈内存改为2048k,那么能活动的栈帧就会减半。

1.4.5 方法内的局部变量是否线程安全?

  • 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
  • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

1.4.6 栈内存溢出情况

  • 栈帧过多导致栈内存溢出,典型问题:递归调用
  • 栈帧过大导致栈内存溢出(不容易出现)

1.5 能不能解释一下方法区?

难易程度:☆☆☆

出现频率:☆☆☆

1.5.1 概述

  • 方法区(Method Area)是各个线程共享的内存区域
  • 主要存储类的信息、运行时常量池
  • 虚拟机启动的时候创建,关闭虚拟机时释放
  • 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace

1.5.2 运行时常量池

  • 常量池:可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池 ,并把里面的符号地址变为真实地址

1.5.3 方法区中的方法的执行过程?

当程序中通过对象或类直接调用某个方法时,主要包括以下几个步骤:

  • 解析方法调用 :JVM会根据方法的符号引用找到实际的方法地址
  • 栈帧创建 :在调用一个方法前,JVM会在当前线程的Java虚拟机栈中为该方法分配一个新的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    执行方法 :执行方法内的字节码指令,涉及的操作可能包括局部变量的读写、操作数栈的操作、跳转控制、对象创建、方法调用等。
    返回处理 :方法执行完毕后,可能会返回一个结果给调用者,并清理当前栈帧,恢复调用者的执行环境。

1.6 你听过直接内存吗?

难易程度:☆☆☆

出现频率:☆☆☆

  • 并不属于JVM中的内存结构,不由JVM进行管理。是虚拟机的系统内存
  • 常见于 NIO 操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受 JVM 内存回收管理

2. 类加载器

难易程度:☆☆☆☆

出现频率:☆☆☆

2.1 什么是类加载器

JVM只会运行二进制文件,而类加载器(ClassLoader)的主要作用就是将字节码文件加载到JVM中,从而让Java程序能够启动起来。

2.2 类加载器种类

类加载器根据各自加载范围的不同,划分为四种类加载器:

  • 启动类加载器(BootStrap ClassLoader):该类并不继承ClassLoader类,其是由C++编写实现。负责加载Java的核心库(如JAVA_HOME/jre/lib目录下的类库)。
  • 扩展类加载器(ExtClassLoader):该类是ClassLoader的子类,主要加载JAVA_HOME/jre/lib/ext目录中的类库。
  • 应用类加载器(AppClassLoader):该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。
  • 自定义类加载器:开发者自定义类继承ClassLoader,实现自定义类加载规则。

2.3 什么是双亲委派模型?

难易程度:☆☆☆☆

出现频率:☆☆☆☆

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就返回成功;只有父类加载器无法完成此加载任务时,才由下一级去加载。

2.4 JVM为什么采用双亲委派机制(作用)

难易程度:☆☆☆

出现频率:☆☆☆

  1. 保证类的唯一性:通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。
  2. 保证安全性:为了安全,保证类库API不会被修改。例如,恶意代码无法自定义一个Java.lang.System类并加载到IM中,因为这个请求会被委托给启动类加载器,而启动类加载器只会加载标准的Java库中的类。

2.5 说一下类装载的执行过程

  1. 加载:查找和导入class文件
    • 通过类的全名,获取类的二进制数据流。
    • 解析类的二进制数据流为方法区内的数据结构(Java类模型)
    • 创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口
  2. 验证:保证加载类的准确性
    • 格式检查:文件格式是否错误、语法是否错误、字节码是否合规
      • 文件格式验证
      • 元数据验证
      • 字节码验证
    • 符号引用验证:Class文件在其常量池中通过字符串记录自己将要使用的其它类或者方法,检查它们是否存在
  3. 准备:为类变量分配内存并设置类变量初始值
    • static变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成
    • static变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成
    • static变量是final的引用类型,那么赋值也会在初始化阶段完成
  4. 解析:把类中的符号引用转换为直接引用
    • 比如:方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法。
  5. 初始化:对类的静态变量,静态代码块执行初始化操作
    • 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
    • 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
  6. 使用:JVM 从入口方法开始执行用户的程序代码
    • 调用静态类成员信息(比如:静态字段、静态方法)
    • 使用new关键字为其创建对象实例
  7. 卸载:当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象
    • 最后负责运行的 JVM 也退出内存

3. 垃圾回收

垃圾回收(Garbage Collection,Gc)是自动管理内存的一种机制,它负责自动释放不再被程序引用的对象所占用的内存,这种机制减少了内存泄漏和内存管理错误的可能性。

3.1 对象什么时候可以被垃圾器回收

如果一个或多个对象没有任何的引用指向它了,那么这个对象现在就是垃圾,如果定位了垃圾,则有可能会被垃圾回收器回收。

如果要定位什么是垃圾,有两种方式来确定,第一个是引用计数法,第二个是可达性分析算法

  • 引用计数
    • 原理: 为每个对象分配一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器减1。当计数器为0时,表示对象不再被任何变量引用,可以被回收。
    • 缺点: 不能解决循环引用的问题,即两个对象相互引用,但不再被其他任何对象引用,这时引用计数器不会为0,导致对象无法被回收。
  • 可达性分析算法
    • 原理: 从一组称为GC Roots(垃圾收集根)的对象出发,向下追溯它们引用的对象,以及这些对象用的其他对象,以此类推。如果一个对象到GC Roots没有任何引用链相连(即从GC Roots到这个对不可达),那么这个对象就被认为是不可达的,可以被回收。
    • GC Roots对象包括: 虚拟机栈(栈帧的本地变量表)中引用的对象、方法区中类静态属性引用的对象、本地方法栈中JNI (Java Native Interface)引用的对象、活跃线程的引用等。

3.2 VM 垃圾回收算法有哪些?

难易程度:☆☆☆

出现频率:☆☆☆☆

3.2.1 标记清除算法

标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。

  1. 根据可达性分析算法得出的垃圾进行标记
  2. 对这些标记为可回收的内容进行垃圾回收

标记清除算法也是有缺点的:通过标记清除算法清理出来的内存,碎片化较为严重 ,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的

3.2.2 标记整理算法

标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的直接清理可回收对象,而是将存活对象都向内存另一端移动,然后清理边界以外的垃圾,从而解决了碎片化的问题

优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响

3.2.3 复制算法

将原有的内存空间一分为二,每次只用其中的一块 ,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。

如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。 缺点:内存利用率不足。

3.3 分代回收算法

堆被分为了两份:新生代和老年代【1:2】

分代回收是将内存划分成了新生代和老年代。分配的依据是对象的生存周期,或者说经历过的 GC 次数。对象创建时,一般在新生代申请内存,当经历一次 GC之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值(默认是 15)后,如果对象还存活,那么该对象会进入老年代。

MinorGC、 Mixed GC 、 FullGC的区别是什么

  • MinorGC【young GC】
    • 发生在新生代的垃圾回收
    • 当 Eden 区空间不足时,JVM 触发依次Minor GC,将Eden区和一个Survivor区中的存活对象移动到另一个Survivor区或老年区。
    • 频率高,暂停时间短(STW)
  • Mixed GC
    • 新生代 + 老年代部分区域的垃圾回收,G1 收集器特有
  • FullGC
    • 新生代 + 老年代完整垃圾回收
    • 暂停时间长(STW),应尽力避免

STW(Stop-The-World):暂停所有应用程序线程,等待垃圾回收的完成

3.4 说一下 JVM 有哪些垃圾回收器?

难易程度:☆☆☆☆

出现频率:☆☆☆☆

在jvm中,实现了多种垃圾收集器,包括:

  • 串行垃圾收集器
  • 并行垃圾收集器
  • CMS(并发)垃圾收集器
  • G1垃圾收集器

3.4.1 串行垃圾收集器

SerialSerial Old 串行垃圾收集器,是指使用单线程进行垃圾回收,堆内存较小,适合个人电脑

  • Serial 作用于新生代,采用复制算法
  • Serial Old 作用于老年代,采用标记-整理算法
  • 垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。
  • 优点是简单高效

3.4.2 并行垃圾收集器

Parallel NewParallel Old 是一个并行垃圾回收器,JDK8默认使用此垃圾回收器

  • Parallel New作用于新生代,采用复制算法
  • Parallel Old作用于老年代,采用标记-整理算法
  • 垃圾回收时,多个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。

3.4.3 CMS(并发)垃圾收集器

CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,该回收器是针对老年代 垃圾回收的,是一款以获取最短回收停顿时间为目标的收集器,停顿时间短,用户体验就好。其最大特点是在进行垃圾回收时,应用仍然能正常运行。

3.4.4 G1垃圾回收器

  • 应用于新生代和老年代,在JDK9之后默认使用G1
  • 划分成多个区域(弱化了分代的概念),每个区域都可以充当 eden,survivor,old, humongous,其中 humongous 专为大对象准备
  • 采用复制算法
  • 响应时间与吞吐量兼顾
  • 分成三个阶段:新生代回收、并发标记、混合收集
  • 如果并发失败(即回收速度赶不上创建新对象速度),会触发 Full GC

3.5 强引用、软引用、弱引用、虚引用的区别?

难易程度:☆☆☆☆

出现频率:☆☆☆

  • 强引用: 只要所有 GC Roots 能找到,就不会被回收
  • 软引用: 需要配合SoftReference使用,当垃圾多次回收,内存依然不够的时候会回收软引用对象
  • 弱引用: 需要配合WeakReference使用,只要进行了垃圾回收,就会把弱引用对象回收
  • 虚引用: 必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

4. JVM 实践

4.1 JVM 调优的参数都有哪些?

通常在linux系统下直接加参数启动springboot项目
nohup java -Xms512m -Xmx1024m -jar xxxx.jar --spring.profiles.active=prod &

nohup : 用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行

参数 & :让命令在后台执行,终端退出后命令仍旧执行。

1)设置堆的初始大小和最大大小 ,为了防止垃圾收集器在初始大小、最大大小之间收缩堆而产生额外的时间,通常把最大、初始大小设置为相同的值。

-Xms:设置堆的初始化大小

-Xmx:设置堆的最大大小

2)设置虚拟机栈的位置

3)年轻代中 Eden 区和两个 Survivor 区的大小比例

4)设置垃圾回收器

4.2 java内存泄露的排查思路?

第一,可以通过jmap指定打印他的内存快照 dump文件,不过有的情况打印不了,我们会设置vm参数让程序自动生成dump文件

第二,可以通过工具去分析 dump文件,jdk自带的VisualVM就可以分析

第三,通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题

第四,找到对应的代码,通过阅读上下文的情况,进行修复即可

4.3 服务器CPU持续飙高,你的排查方案与思路?

第一可以使用使用 top 命令查看占用cpu的情况

第二通过top命令查看后,可以查看是哪一个进程占用cpu较高,记录这个 进程id

第三可以通过ps 查看当前进程中的 线程信息,看看哪个线程的cpu占用较高

第四可以jstack命令打印进行的id,找到这个线程,就可以进一步定位问题代码的行号

相关推荐
mghio6 小时前
Dubbo 中的集群容错
java·微服务·dubbo
uhakadotcom8 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
uhakadotcom10 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试