校招面试重点汇总之JVM(中大厂必备)

一、什么是JVM?组成部分有哪些?

JVM是Java虚拟机(Java Virtual Machine)的缩写,它是Java程序运行的环境。Java源代码需要被编译成字节码才能在JVM上运行。JVM是跨平台的,这意味着Java程序可以在任何支持Java虚拟机的平台上运行,而不需要重新编译源代码。

JVM由三个主要组件组成:类装载器(Class Loader)、运行时数据区(Runtime Data Area)和执行引擎(Execution Engine),其介绍如下:

  • (1)类装载器:类装载器负责从文件系统或网络中加载类文件到JVM中。Java中的类是动态加载的,也就是说,它们在程序运行时被加载,而不是在编译时静态加载。类装载器负责将类文件加载到JVM中,并将其转换为运行时数据结构。

  • (2)运行时数据区:运行时数据区是JVM的内存空间,它分为不同的区域:
    ①程序计数器(Program Counter Register):它是一个小的内存区域,它保存了当前线程执行的指令地址。当线程执行Java方法时,程序计数器记录的是当前正在执行的指令的地址。
    ②Java虚拟机栈(Java Virtual Machine Stacks):它保存线程的方法调用和本地变量。每个线程都有一个私有的Java虚拟机栈,它在线程创建时被创建。当线程调用一个方法时,一个新的栈帧被创建并推入该线程的Java虚拟机栈中。当方法执行完毕时,栈帧被弹出并销毁。
    ③本地方法栈(Native Method Stack):本地方法栈与Java虚拟机栈类似,只不过它是为本地方法服务的。它也用于存储本地方法的局部变量、操作数栈等信息。
    ④堆(Heap):它是JVM中最大的一块内存,它用于存储Java对象。当程序创建一个新的Java对象时,它被分配在堆上,并返回一个指向该对象的引用。Java虚拟机的垃圾回收器负责回收不再使用的对象,以释放堆空间。
    ⑤方法区(Method Area):它用于存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等。它是所有线程共享的内存区域。

  • (3)执行引擎
    执行引擎负责解释字节码指令并执行相应的操作。Java字节码指令是JVM的指令集,它包括各种操作,例如:算术运算、类型转换、对象创建、方法调用等。执行引擎负责将字节码指令翻译为机器指令并执行。

二、JVM内存模型是什么?

JVM内存模型是JVM对于内存的抽象,描述了Java程序在内存中的分配、访问和释放的规则。JVM内存模型定义了Java程序运行时的内存结构和各个内存区域的作用,它为Java程序提供了一种可预测的内存访问机制,确保了多线程程序的正确性和一致性。

JVM内存模型分为以下几个部分:

  • 程序计数器(Program Counter Register):程序计数器是一块较小的内存区域,它保存了当前线程执行的字节码指令的地址。在多线程情况下,每个线程都有一个独立的程序计数器,它们之间互不干扰。

  • Java虚拟机栈(Java Virtual Machine Stacks):Java虚拟机栈用于存储方法的局部变量、操作数栈、方法出口等信息。每个线程都拥有一个独立的Java虚拟机栈,它在线程创建时就被创建,当线程执行结束时,它所使用的Java虚拟机栈也会被销毁。

  • 本地方法栈(Native Method Stack):本地方法栈与Java虚拟机栈类似,只不过它是为本地方法服务的。它也用于存储本地方法的局部变量、操作数栈等信息。

  • 堆(Heap):堆是Java虚拟机中最大的一块内存区域,它用于存储Java对象。所有的Java对象都在堆中进行分配,当一个对象不再被引用时,它占用的内存会被JVM的垃圾回收机制回收。

  • 方法区(Method Area):方法区用于存储类的信息、常量池、静态变量、编译后的代码等信息。它在JVM启动时被创建,与堆一样,是被所有线程共享的。(运行时常量池(Runtime Constant Pool)是方法区的一部分,它用于存储编译时期生成的字面量和符号引用。与方法区一样,运行时常量池也是被所有线程共享的)

三、JDK、JRE和JVM的关系

具体关系如下图:

校招面试重点汇总之JVM(中大厂必备)_第1张图片
简单点可以总结为:JVM是Java程序的运行环境,JRE是Java程序运行所需的环境,而JDK则是用于Java应用程序开发的工具包。在开发Java应用程序时,需要使用JDK来编写、编译和测试代码,在部署Java应用程序时,只需要JRE就可以了

四、Java程序执行流程

先看下面的图:
校招面试重点汇总之JVM(中大厂必备)_第2张图片
其解释大概如下:Java文件经过编译之后变成class字节码文件,通过类加载器搬运到java虚拟机,然后虚拟机中判断当前代码是否为热点代码,如果是热点代码就去即时编译器,不是热点代码就去java解释器,接着就到了运行期系统,执行代码(包括管理程序的内存、执行字节码指令、处理异常、加载类等操作),最后作用于操作系统和硬件。

注1:什么是热点代码?
被多次调用的方法;被多次执行的循环体;

注2:如何判断热点代码?
基于采样的热点探测; 基于计数器的热点探测:采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数。如果执行次数超过一定阈值,就认为它是热点方法

五、类加载器过程

从类被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期分为7个阶段:,加载(Loading) ——> 验证(Verification) ——> 准备(Preparation) ——> 解析(Resolution) ——> 初始化(Initialization) ——> 使用(Using) ——> 卸载(Unloading)。其介绍分别如下:

  • 加载阶段:JVM首先需要加载Java字节码文件,并将字节码文件转换成JVM内部的数据结构,包括类、接口、方法等。在加载阶段,JVM还会进行一些安全性检查,以确保Java代码的安全性。

  • 验证阶段:在验证阶段,JVM会对字节码进行校验,以确保字节码的正确性和安全性。它会检查字节码的格式、语义以及类之间的关系,以确保代码的安全性和正确性。

  • 准备阶段:在准备阶段,JVM会为Java程序的静态变量分配内存空间,并设置默认的初始值。同时,JVM还会为Java程序的常量池分配内存空间,并将常量池中的符号引用解析成直接引用。

  • 解析阶段:在解析阶段,JVM会将类、方法和接口的符号引用解析成直接引用,以便于程序执行。同时,JVM还会进行一些优化处理,以提高程序的执行效率。

  • 初始化阶段:在初始化阶段,JVM会执行Java程序的静态初始化代码,即执行静态变量赋值语句和静态代码块。在初始化阶段,JVM还会进行一些安全性检查,以确保Java代码的安全性。

  • 执行阶段:在执行阶段,JVM会按照字节码中的指令一条一条地执行程序。当JVM执行到一个方法时,它会将该方法的局部变量表和操作数栈压入方法栈中,并执行该方法。当方法执行完毕后,JVM会将方法的返回值压入操作数栈中,并弹出方法栈。

  • 卸载阶段:在程序执行结束后,JVM会将程序的内存空间释放,包括卸载类、回收垃圾等操作。

六、类加载器当中的双亲委派机制

Java中的类加载器采用了一种特殊的委派机制,称为双亲委派机制。它是指当一个类加载器(称为子类加载器)被请求加载某个类时,它首先不会自己去加载这个类,而是把请求委派给父类加载器去完成,如果父类加载器还存在父类加载器,则继续向上委托,依次递归,直到最顶层的类加载器(Bootstrap ClassLoader)去加载,如果最顶层的类加载器无法加载该类,则由下层的类加载器进行加载。当所有的父类加载器都无法完成类的加载请求时,子类加载器才会自己去加载。

优点:

  • (1)避免了重复加载类的问题,同时也保证了Java程序的稳定性和安全性,因为如果某个类已经被父类加载器加载了,那么子类加载器再去加载同一个类时,就会直接使用父类加载器已经加载的那个类,而不会再次加载。
  • (2)可以保证Java核心API的安全性,因为Java核心API由Bootstrap ClassLoader加载,而Bootstrap ClassLoader是由JVM自带的,是最高级别的类加载器,所有的类加载请求都会先经过它。如果Java应用程序中的代码试图去加载某个核心API类,那么双亲委派机制就会让Bootstrap ClassLoader去加载这个类,从而确保了Java核心API的安全性。
  • (3)可以保证类的唯一性,因为同一个类在不同的类加载器中只会被加载一次,即使这些类加载器是不同的,也会使用同一个类对象。这样就避免了在Java程序中出现多个不同版本的同名类的问题。

注:什么时候需要破坏双亲委派机制呢?
当父类加载器需要加载的class文件由于受到加载范围的限制,父类加载器无法加载到需要的文件,这个时候就需要破坏双亲委派机制,然后委托子类加载器进行加载。

七、什么是字节码?Java中如何生成字节码?

字节码(bytecode)是Java程序在编译后生成的中间代码,它是一种由单字节(byte)指令组成的指令集,可以被Java虚拟机(JVM)解释执行。

Java中可以通过编译器将Java源代码编译成字节码,编译器会将Java源代码编译成一种中间格式,即.class文件。Java编译器在编译时将Java源代码分析并生成与之对应的字节码指令序列,这些指令序列被写入到.class文件中。

注:Java还提供了一些工具来生成字节码,例如ASM、Javassist、CGLib等,这些工具可以在运行时动态生成字节码,可以用于实现一些动态代理、AOP等高级特性。

八、什么是内存泄漏和内存溢出?有什么区别?

内存泄漏(Memory Leak)指的是程序在运行时分配的内存空间,在不再需要时没有被正确释放,导致这部分内存永远无法被使用。当程序运行时间较长、分配的内存越来越多时,内存泄漏就会导致可用内存越来越少,直到程序最终耗尽所有可用内存而崩溃。

  • 内存泄漏通常是由程序中存在未正确关闭的文件、网络连接、数据库连接等资源所引起的,也可能是由于程序中的对象没有被正确地释放而导致的。

内存溢出(Memory Overflow)则是指程序在运行时分配的内存空间已经达到了最大限制,无法再分配更多的内存空间,导致程序崩溃。

  • 通常情况下,内存溢出是由于程序中存在某种资源消耗过大的问题,例如过多的递归、创建大量的对象等。

内存泄漏和内存溢出的区别:

  • 内存泄漏是指程序中分配的内存空间没有被正确释放,导致这部分内存永远无法被使用;而内存溢出则是指程序在运行时分配的内存空间已经达到了最大限制,无法再分配更多的内存空间,导致程序崩溃。
  • 内存泄漏通常是逐渐累积的,随着程序运行时间的增长,内存泄漏会越来越严重,程序最终耗尽所有可用内存而崩溃;而内存溢出则通常在程序运行较短时间后就会出现,由于内存空间已经达到最大限制而导致程序崩溃。

九、介绍下Java中垃圾回收?

Java使用垃圾回收(Garbage Collection,简称GC)来自动管理内存。垃圾回收器负责自动监视和释放程序不再使用的对象,从而减轻内存管理的负担,减少很多错误的出现。

那如何监测一个对象不再使用了呢?

  • 判断一个对象是否可被回收的标准是“是否存在引用指向该对象”。如果一个对象没有任何引用指向它,即使它还占用着一定的内存空间,也会被垃圾收集器认为是垃圾,从而被回收。因此,Java虚拟机需要进行“引用计数”的操作,即统计每个对象被引用的次数,当某个对象的引用次数为0时,就可以将其回收。
  • 但是,“引用计数”方式存在很多问题,比如无法解决循环引用的问题、检测成本大等,所以目前主流的垃圾回收算法是基于“可达性分析”的方式。垃圾回收器会把一些特殊的对象作为GC Roots,如虚拟机栈中引用的对象、类静态属性引用的对象等等,并以此为起点开始遍历对象图谱,最终将不可达到的对象标记为垃圾进行回收

十、什么是GC算法?Java中有哪些GC算法?

GC(Garbage Collection)算法是一种自动内存管理技术,用于识别不再使用的内存资源并回收这些资源,以便程序在运行期间能够更有效地使用内存空间。Java中的垃圾收集器(Garbage Collector)就是通过GC算法来判断哪些对象可以被回收,以及何时回收。

Java中常用的GC算法包括:

  • (1)标记-清除算法(Mark and Sweep)
    标记-清除算法是最基本的垃圾收集算法,主要分为标记和清除两个阶段。标记阶段遍历所有可到达的对象,将其标记为“活跃”对象;清除阶段则回收未被标记的对象所占用的内存空间。该算法存在的问题包括:对象存储空间的碎片化、标记和清除两个过程可能会造成较长的停顿时间等。

  • (2)复制算法(Copying)
    复制算法将可用的内存空间分成两块,每次只使用其中一块。当这一块空间被占满后,将已经标记为“活跃”对象的部分复制到另一个空闲的块中,并将原有的空间全部回收释放。该算法能够快速进行垃圾回收,并且不存在对象碎片化的问题,但是需要额外的空间。

  • (3)标记-整理算法(Mark and Compact)
    标记-整理算法也是分为标记和清除两个阶段,但与标记-清除算法不同的是,它在清除未被标记的对象所占用的内存空间之后,还会将所有活跃对象紧凑排列,使得内存空间得以优化利用、避免碎片化。该算法存在的问题主要是实现难度较大,且需要暂停应用程序。

  • (4)分代收集算法(Generational Collection)
    分代收集算法是一种更加细致的垃圾回收策略,它根据对象的生命周期将可达对象分为新生代和老年代两部分。新生代中的对象生命周期较短,垃圾回收频率较高;而老年代中的对象生命周期较长,垃圾回收频率较低。该算法能够有效地减少垃圾回收的次数,并减少应用程序的停顿时间。

注:在实际应用中,Java虚拟机还可能使用混合(Mixed)算法,将多种垃圾回收算法组合使用,以达到更好的性能和效果。

你可能感兴趣的:(校招之路,jvm,面试,java)