Skip to content

本模块适合 Java后端研发(业务岗、架构岗)Java安卓原生研发等 Java 相关岗位的同学。

讲讲 jvm 的内存结构,细分到 JDK 7 和 JDK 8 的区别

Java 虚拟机(JVM)的内存结构主要分为以下几个区域:

  1. 程序计数器(Program Counter Register):线程私有,用来存储当前线程执行的字节码指令地址。
  2. Java 虚拟机栈(Java Virtual Machine Stacks):线程私有,每个线程都有一个栈,用来存储方法的局部变量、操作数栈、动态链接、方法出口等信息。
  3. 本地方法栈(Native Method Stack):线程私有,用来存储本地方法的信息。
  4. Java 堆(Java Heap):所有线程共享,用来存储对象实例。
  5. 方法区(Method Area):所有线程共享,用来存储类的结构信息、常量、静态变量等。
  6. 运行时常量池(Runtime Constant Pool):方法区的一部分,用来存储编译期生成的字面量和符号引用。
  7. 直接内存(Direct Memory):不是虚拟机运行时数据区的一部分,但是也会被频繁使用,主要是 NIO 中使用的堆外内存。

JDK 7 和 JDK 8 的内存结构区别主要在于永久代的移除和元空间的引入。

  • JDK 7 中,方法区是永久代(Perm)的实现,存储类的结构信息、常量、静态变量等。永久代的大小是有限的,当类的数量增多时,会导致永久代内存溢出。永久代的垃圾回收主要是通过Full GC来进行,效率较低。
  • JDK 8 中,永久代被移除,取而代之的是元空间(Metaspace),元空间是使用本地内存来存储一些元数据(常量池和静态变量则被移动至 Java 堆中)。元空间的大小是受限于本地内存,当类的数量增多时,会导致本地内存溢出。元空间的垃圾回收主要是通过Full GC来进行,效率较高。

JDK 8 中的元空间相比于 JDK 7 中的永久代有以下几个优点:

  1. 元空间使用本地内存,不受永久代大小的限制,可以动态调整。
  2. 元空间的垃圾回收效率高,不会出现永久代内存溢出的情况。
  3. 元空间的内存分配和回收更加灵活,可以避免内存泄漏。

讲讲 JVM 的垃圾回收时的可达性检测算法

Java 虚拟机的垃圾回收可达性分析算法主要采用的是根搜索算法。

根搜索算法是通过一系列的GC Roots对象作为起始点,从这些节点开始向下搜索,搜索过程中所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可达的,即为垃圾对象。

GC Roots对象包括以下几种:

  1. 虚拟机栈中引用的对象。
  2. 本地方法栈中引用的对象。
  3. 方法区中静态属性引用的对象。
  4. 方法区中常量引用的对象。

根搜索算法的优点是不需要暂停用户线程,可以并发执行,不会影响用户线程的执行。

可达性检测算法还包含了引用计数算法,但是引用计数算法无法解决循环引用的问题,会导致内存泄漏。

讲讲 JVM 的垃圾回收算法

先说说常见的垃圾回收算法:

  1. 标记 - 清除算法(Mark-Sweep):标记 - 清除算法是一种基础的垃圾回收算法,分为标记和清除两个阶段。首先标记所有存活对象,然后清除所有未标记的对象。标记 - 清除算法优点是实现简便,不需要移动对象,但由于算法过程中需要暂停应用,且会产生内存碎片,导致如果需要分配大对象时,可能会因为内存碎片不足而提前触发垃圾回收。
  2. 复制算法(Copying):复制算法是一种高效的垃圾回收算法,将内存空间划分为两块,每次只使用其中一块,当这一块内存用完时,将存活对象复制到另一块内存中,然后清除当前内存。复制算法优点是实现简单,不会产生内存碎片,但缺点是需要额外的内存空间。
  3. 标记 - 整理算法(Mark-Compact):标记 - 整理算法是一种高效的垃圾回收算法,结合了标记 - 清除算法和复制算法的优点,标记存活对象,然后将存活对象向一端移动,清除另一端的内存。标记 - 整理算法优点是不会产生内存碎片,但缺点是需要移动对象,可能会影响性能。
  4. 分代收集算法(Generational):分代收集算法是一种高效的垃圾回收算法,根据对象的存活周期将内存划分为不同的代,一般分为新生代和老年代。新生代使用复制算法,老年代使用标记 - 整理算法。分代算法优点是根据对象的存活周期选择合适的算法,提高了垃圾回收效率。

Java 虚拟机的垃圾回收算法主要采用的是分代收集算法。

在新生代中,分为 Eden 区、Survivor 区的 From 区和 Survivor 区的 To 区,比例为 8:1:1,使用复制算法。首先将 Eden 区存活的对象复制到 From 区,然后将 Eden 清空。当 From 区满时,将存活的对象复制到 To 区,清空 Eden 和 From 区,然后将 From 区和 To 区交换。循环往复直到 To 区放不下存活的对象。新生代发生的 GC 称为 Minor GC。

在老年代中,使用标记 - 整理算法,标记存活对象,然后将存活对象向一端移动,清除另一端的内存。老年代发生的 GC 称为 Major GC 或 Full GC。Major GC 的触发会连带触发 Minor GC。

讲讲 JVM 的垃圾回收器(不常考)

Java 虚拟机的垃圾回收器主要分为以下几种:

  1. 串行垃圾回收器(Serial GC):串行垃圾回收器是一种单线程的垃圾回收器,使用复制算法,适用于单核 CPU 的环境。串行垃圾回收器在新生代使用复制算法,在老年代使用标记 - 整理算法。
  2. 并行垃圾回收器(Parallel GC):并行垃圾回收器是一种多线程的垃圾回收器,使用复制算法,适用于多核 CPU 的环境。并行垃圾回收器在新生代使用复制算法,在老年代使用标记 - 整理算法。
  3. CMS 垃圾回收器(Concurrent Mark-Sweep GC):CMS 垃圾回收器是一种并发的垃圾回收器,使用标记 - 清除算法,适用于对响应时间有要求的场景。CMS 垃圾回收器在新生代使用复制算法,在老年代使用标记 - 清除算法。
  4. G1 垃圾回收器(Garbage-First GC):G1 垃圾回收器是一种面向服务端的垃圾回收器,使用分代收集算法,适用于大内存、多核 CPU 的环境。G1 垃圾回收器将堆内存划分为多个区域,每个区域独立进行垃圾回收,可以控制垃圾回收的时间和内存占用。

不同的垃圾回收器适用于不同的场景,可以根据应用的特点和需求选择合适的垃圾回收器。

讲讲常见的引用类型(不常考)

Java 中的引用类型主要分为以下几种:

  1. 强引用(Strong Reference):强引用是最常见的引用类型,只要存在强引用,垃圾回收器就不会回收对象。例如:
java
Object obj = new Object();
  1. 软引用(Soft Reference):软引用是一种相对弱化的引用类型,只有在内存不足时,垃圾回收器才会回收对象。例如:
java
SoftReference<Object> softRef = new SoftReference<>(new Object());
  1. 弱引用(Weak Reference):弱引用是一种更弱化的引用类型,只要发生垃圾回收,就会回收对象。例如:
java
WeakReference<Object> weakRef = new WeakReference<>(new Object());
  1. 虚引用(Phantom Reference):虚引用是一种最弱化的引用类型,无法通过虚引用获取对象,主要用来跟踪对象被回收的状态。例如:
java
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

引用类型的主要作用是帮助开发者更好地管理内存,避免内存泄漏和内存溢出。

讲讲 JVM 的类加载机制

Java 虚拟机的类加载机制主要分为以下几个步骤:(3、4、5 统称为链接)

  1. 加载(Loading):加载是指将类的字节码文件加载到内存中,并生成一个java.lang.Class对象。
  2. 验证(Verification):验证是指对字节码文件进行验证,确保字节码文件的正确性和安全性。
  3. 准备(Preparation):准备是指为类的静态变量分配内存,并初始化为默认值。
  4. 解析(Resolution):解析是指将类的符号引用解析为直接引用。
  5. 初始化(Initialization):初始化是指执行类的构造方法,初始化类的静态变量和静态代码块。

类加载机制主要遵循以下几个原则:

  1. 双亲委派模型(常见的代理模式):类加载器之间存在父子关系,子类加载器会委托父类加载器加载类,在父类加载器加载不了类时,子类加载器才会加载类,如果都加载不了类,会抛出ClassNotFoundException异常。(自下向上委托,自上向下加载)
  2. 全盘负责原则:类加载器负责加载类的所有依赖类,如果依赖类没有加载,会先加载依赖类,除非显式调用另一个类加载器加载类。
  3. 缓存机制:类加载器会缓存加载过的类,避免重复加载。

此外,例如 Tomcat 采用的加载方式虽然是一种代理模式,但是并不是双亲委派模型,而是先尝试自己加载,加载不到再委托父类加载器加载,和双亲委派模型有所区别。

类加载机制的主要作用是实现类的动态加载和动态链接,提高程序的灵活性和可扩展性。

什么时候才会触发类的初始化

Java 虚拟机在初始化一个类时,会按照以下顺序主动执行类的初始化:

  1. 当创建类的实例时,会触发类的初始化。
  2. 当访问类的静态变量时,会触发类的初始化。
  3. 当调用类的静态方法时,会触发类的初始化。
  4. 当使用java.lang.reflect包的方法对类进行反射调用时,会触发类的初始化。
  5. 当初始化一个类时,如果发现其父类还没有初始化,则会先触发其父类的初始化。

类的初始化主要是执行类的构造方法,初始化类的静态变量和静态代码块。其中,main 方法所在类被初始化属于第一种情况。

讲讲 JVM 的类加载器

Java 虚拟机的类加载器主要分为以下几种:

  1. 启动类(引导类)加载器(Bootstrap ClassLoader):启动类加载器是 Java 虚拟机的内置类加载器,负责加载JAVA_HOME/lib目录下的核心类库,如rt.jarcharsets.jar等。
  2. 扩展类加载器(Extension ClassLoader):扩展类加载器是 Java 虚拟机的内置类加载器,负责加载JAVA_HOME/lib/ext目录下的扩展类库,如junit.jarmysql-connector-java.jar等。
  3. 应用类加载器(Application ClassLoader):应用类加载器是 Java 虚拟机的内置类加载器,负责加载应用程序的类路径(classpath)下的类库,如-classpath-cp参数指定的类库。
  4. 自定义类加载器(Custom ClassLoader):自定义类加载器是开发者自定义的类加载器,继承自ClassLoader类,可以实现自定义的类加载逻辑。

类加载器主要负责加载类的字节码文件,并生成java.lang.Class对象,类加载器之间存在父子关系,子类加载器会委托父类加载器加载类,只有在父类加载器加载不了类时,子类加载器才会加载类。

简单讲讲 jvm 调优 (面试时尽可能说没做过,绝大部分学生不可能接触到 jvm 调优,会引发真实性问题)

Java 虚拟机的调优主要分为以下几个方面:

  1. 内存调优:主要是调整堆内存大小、新生代和老年代比例、永久代大小等,可以通过-Xms-Xmx-Xmn-XX:PermSize-XX:MaxPermSize等参数进行调优。
  2. 垃圾回收调优:主要是调整垃圾回收器的类型、堆内存分代比例、垃圾回收线程数等,可以通过-XX:+UseSerialGC-XX:+UseParallelGC-XX:+UseConcMarkSweepGC-XX:+UseG1GC等参数进行调优。
  3. 线程调优:主要是调整线程池的大小、线程栈大小、线程优先级等,可以通过-Xss-Xmx-Xms-XX:ThreadStackSize-XX:ThreadPriority等参数进行调优。
  4. 类加载调优:主要是调整类加载器的加载路径、类加载器的委托关系等,可以通过-classpath-cp等参数进行调优。
  5. JIT 调优:主要是调整即时编译器的编译级别、编译触发条件等,可以通过-XX:+TieredCompilation-XX:+PrintCompilation-XX:CompileThreshold等参数进行调优。

Java 虚拟机的调优主要是根据应用的特点和需求,调整虚拟机的参数,提高程序的性能和稳定性。