面试题:JVM
图
什么是JVM?Java程序是如何在JVM上运行的?什么是类加载器?JVM内存区域分为哪些部分?分别存储了哪些内容?什么是类初始化过程?垃圾收集算法有哪些?各有什么优缺点?如何调节JVM的性能参数?常用的有哪些参数?

什么是JVM?

  • Java虚拟机,负责将Java源代码编译成字节码,并在不同的操作系统和硬件平台上执行这些字节码
  • 由于JVM提供了抽象的硬件和操作系统接口,因此Java程序可以跨平台运行,无需考虑底层硬件和操作系统的细节
  • JVM还提供了垃圾回收、安全性、异常处理等高级特性,使得Java成为一种安全、可靠、易于开发和维护的语言

Java程序是如何在JVM上运行的?

Java程序在JVM上运行的过程如下:

  1. 编写Java源代码:首先,开发人员使用Java编写源代码。Java源代码是纯文本文件,其扩展名为.java
  2. 编译Java源代码:使用Java编译器将源代码转换为字节码(即.class文件)。这个过程被称为编译
  3. 加载字节码:JVM负责加载字节码,并将其转换为可执行代码。这个过程被称为类加载
  4. 验证字节码:在执行字节码之前,JVM会对其进行验证,以确保它不会执行恶意代码或破坏系统
  5. 解释字节码:一旦字节码被验证,JVM就会解释它,并将其转换为本地机器代码。这个过程被称为解释
  6. 执行本地机器代码:一旦字节码被解释为本地机器代码,JVM会开始执行它
  7. 垃圾回收:在程序执行期间,JVM会监视内存的使用情况,并且自动清除不再使用的对象。这个过程被称为垃圾回收
  8. 终止程序:当Java程序结束时,JVM会关闭并终止程序的执行

JVM内存区域分为哪些部分?分别存储了哪些内容?

  1. 程序计数器(Program Counter Register):程序计数器是一个小的内存区域,它用于指示当前线程所执行的字节码的行号。在Java多线程环境中,每个线程都拥有一个独立的程序计数器
  2. Java虚拟机栈(Java Virtual Machine Stacks):Java虚拟机栈也是一个线程私有的内存区域,其主要作用是存储线程执行方法时的局部变量、操作数栈、动态链接等信息。当线程调用一个方法时,会在栈中创建一个新的栈帧,当方法返回时,该栈帧将被销毁
  3. 本地方法栈(Native Method Stack):本地方法栈与Java虚拟机栈类似,不同之处在于本地方法栈为本地方法服务,而Java虚拟机栈为Java方法服务
  4. Java堆(Java Heap):Java堆是Java虚拟机中最大的内存区域,它被所有线程共享。Java堆存储了Java对象实例和数组,由垃圾回收器来管理
  5. 方法区(Method Area):方法区也是被所有线程共享的内存区域,它用于存储已加载的类、常量、静态变量、即时编译器编译后的代码等数据。在Java虚拟机规范中,方法区被称为“永久代”(Permanent Generation),但在JDK8之后,永久代已被元空间(Metaspace)取代
  6. 运行时常量池(Runtime Constant Pool):运行时常量池属于方法区的一部分,它用于存储编译期生成的常量以及运行期间产生的字符串常量。在Java虚拟机规范中,运行时常量池也被称为“动态常量池”

什么是类加载器?有几种类加载器?

类加载器(Class Loader)是Java虚拟机(JVM)的一个子系统,用于在运行时动态加载Java类。在Java中,所有的类都需要被加载到内存中才能被执行,而类加载器就是负责将类加载到内存中的组件

Java中的类加载器可以分为以下三类:

  1. 启动类加载器(Bootstrap Class Loader):也称为“引导类加载器”,是JVM自带的类加载器,它主要负责加载JRE(Java Runtime Environment)核心库中的类
  2. 扩展类加载器(Extension Class Loader):也称为“扩展类加载器”,它是由启动类加载器的子类,负责加载JAVA_HOME/jre/lib/ext目录下的jar包中的类
  3. 应用程序类加载器(Application Class Loader):也称为“系统类加载器”,它是由扩展类加载器的子类,负责加载应用程序classpath路径下的类

除了以上三种类加载器外,还可以通过Java代码自定义类加载器来实现特定需求的类加载。在实际开发中,类加载器常被用于实现插件化、热部署等功能

什么是类初始化过程?

类初始化是指在对Java类进行首次使用之前,JVM必须先对该类进行初始化的过程。Java虚拟机规定,在以下情况下会触发类的初始化:

  1. 当创建类的实例时,即通过new关键字创建对象时
  2. 当调用类的静态方法或访问类的静态变量时
  3. 当使用java.lang.reflect包中的方法对类进行反射调用时
  4. 当初始化一个类的子类时,如果父类没有被初始化,则会先触发父类的初始化
  5. Java虚拟机启动时会自动初始化所有被标记为启动类的类

类初始化的过程一般分为以下几个步骤:

  1. 分配内存空间:当JVM初始化一个类时,它首先为该类分配足够的内存空间,用来存放该类的各种数据结构和常量池等信息
  2. 设置默认值:JVM会给类的各种数据类型成员设置默认初始值,比如整型默认值为0、布尔类型默认值为false等
  3. 执行静态代码块:JVM会执行类中所有的静态代码块,这些静态代码块中通常用来执行一些静态初始化操作,比如给静态变量赋初值等
  4. 执行静态变量赋值语句:JVM会执行类中所有静态变量的赋值语句,这些语句用来给静态变量赋初始值

垃圾收集算法有哪些?各有什么优缺点?

Java中主要有以下几种垃圾收集算法:

  1. 标记-清除算法(Mark-and-Sweep):标记-清除算法是最基本的垃圾收集算法,其流程分为两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。这种算法实现简单,但存在内存碎片的问题
  2. 复制算法(Copying):复制算法将可用内存空间划分为两个大小相等的区域,每次只使用其中一个区域。当这个区域的内存使用完之后,就将所有还存活的对象复制到另一个区域中,然后将当前区域中的所有对象全部清空。这种算法不会存在内存碎片的问题,但由于需要把存活的对象复制到新的内存区域中,因此它在效率上相对较低
  3. 标记-整理算法(Mark-and-Compact):标记-整理算法是对标记-清除算法的改进,它在标记过程中同样标记出所有需要回收的对象,并将所有存活的对象向一端移动,然后直接清除掉边界以外的内存。这种算法能够避免内存碎片的问题,但对于存活对象比较多的情况,需要移动对象的成本也比较高
  4. 分代收集算法(Generational Collection):分代收集算法是目前大部分Java虚拟机采用的垃圾收集算法。该算法根据对象存活周期的不同将内存划分为几个不同的区域。一般来讲,Java堆被划分为年轻代和老年代两部分。年轻代用来存放生命周期较短的对象,采用复制算法进行垃圾回收;老年代则用来存放生命周期较长的对象,采用标记-清除或标记-整理算法进行垃圾回收。这种算法能够针对不同生命周期的对象采取不同的垃圾回收策略,从而提高了垃圾回收的效率

如何调节JVM的性能参数?常用的有哪些参数?它们的作用是什么?

调节JVM的性能参数可以通过设置JVM的启动参数来实现。常用的JVM参数有以下几个:

  1. -Xms和-Xmx:这两个参数分别用于指定JVM堆内存的初始大小和最大大小。例如,-Xms128m表示将JVM堆内存的初始大小设置为128MB;-Xmx512m表示将JVM堆内存的最大大小设置为512MB
  2. -XX:NewSize和-XX:MaxNewSize:这两个参数用于指定年轻代的初始大小和最大大小。默认情况下,年轻代大小为整个Java堆的1/3。例如,-XX:NewSize=64m表示将年轻代的初始大小设置为64MB;-XX:MaxNewSize=256m表示将年轻代的最大大小设置为256MB
  3. -XX:PermSize和-XX:MaxPermSize(JDK8及之前版本)或-XX:MetaspaceSize和-XX:MaxMetaspaceSize(JDK8及之后版本):这些参数用于指定永久代(或元空间)的初始大小和最大大小。例如,-XX:PermSize=64m表示将永久代的初始大小设置为64MB;-XX:MaxPermSize=128m表示将永久代的最大大小设置为128MB
  4. -XX:+UseConcMarkSweepGC(CMS Garbage Collector)和-XX:+UseParallelGC(Parallel Garbage Collector):这些参数用于指定垃圾收集器的类型。例如,-XX:+UseConcMarkSweepGC表示使用CMS垃圾收集器;-XX:+UseParallelGC表示使用并行垃圾收集器
  5. -XX:+HeapDumpOnOutOfMemoryError:这个参数用于在Java堆内存溢出时自动生成堆转储快照文件(heap dump)