欢迎光临散文网 会员登陆 & 注册

JVM面试总结(二)

2022-04-27 22:25 作者:吾之利剑  | 我要投稿

    JVM是面试必问的模块,整个JVM我个人感觉可以分为内存模型、类加载机制、GC垃圾回收和性能优化四个大块;

今天主要总结一下类加载机制;

1、介绍一下类加载的过程;*

    一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将依次会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。

1、加载

    加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。 在加载阶段,JVM需要完成3件事:    1、通过类的全限定名获取该类的二进制字节流; 2、将字节流所代表的静态存储结构转化为方法区的运行时数据结构; 3、在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2、验证

    验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。

    1、文件格式验证:

    第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。

    2、元数据验证:

    第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求。

    3、字节码验证:

    第三阶段是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的。

    4、符号引用验证:

    符号引用验证可以看作是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验,主要看该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。

3、准备

    准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

    主要注意变量在内存中的位置,他们都在方法区,但是JDK8以后一起随元空间到堆中了

4、 解析

    解析是连接阶段的第三步,是虚拟机将常量池内的符号引用替换为直接引用的过程。

5、初始化

    类的初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。

    之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。

     注意:

    1、当有父类且父类为初始化的时候,先去初始化父类;

    2、再进行子类初始化语句。

2、Java什么时候需要对类进行初始化?

    1、遇到new,getstatic,putstatic,或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这四条指令单最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰,已在变异期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

    2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先出法其初始化。

    3、当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先出法其父类的初始化。

    4、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

    5、当使用JDK1.7的动态语言支持时,如果一个Java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_outStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

3、什么时候不会发生类的初始化(类的被动引用)

    1、当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化。

    2、通过数组定义类引用,不会触发此类的初始化。

    3、引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

4、介绍一下对象的实例化过程;*

    对象实例化过程,就是执行类构造函数对应在字节码文件中的<init>()方法(实例构造器),<init>()方法由非静态变量、非静态代码块以及对应的构造器组成。

<init>()方法可以重载多个,类有几个构造器就有几个<init>()方法;

<init>()方法中的代码执行顺序为:父类变量初始化、父类代码块、父类构造器、子类变量初始化、子类代码块、子类构造器。

执行顺序依次是 静态变量、静态代码块、普通变量、普通代码块、构造器。

具有父类的子类的实例化顺序如下

    


5、Java对象创建时机

  1. 使用new关键字创建对象

  2. 使用Class类的newInstance方法(反射机制)

  3. 使用Constructor类的newInstance方法(反射机制)

  4. 使用Clone方法创建对象

  5. 使用(反)序列化机制创建对象

6、什么是类加载器 ,有哪些类加载器?

在加载阶段:

    类加载器是一段代码,通过此代码将类以二进制字节流的形式读取到JVM中;

    类加载器通过一个类的全限定名来获取描述该类的二进制字节流;

    类加载器实现的功能是即为加载阶段获取二进制字节流的时候。

JVM提供了以下3种系统的类加载器:

    1、启动类加载器(Bootstrap ClassLoader): 最顶层的类加载器,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。

    2、扩展类加载器(Extension ClassLoader): 负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。

    3、应用程序类加载器(Application ClassLoader): 也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路径(classpath)上的类库。如果没有自定义类加载器,一般这个就是默认的类加载器。

类加载器之间的层次关系如下:


7、什么是双亲委派模型,有什么作用?*

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

双亲委派模型有两个好处:

  1. 向上委托给父类加载,父类加载不了再自己加载

  2. 避免重复加载,防止Java核心API被篡改

8、双亲委派机制会被破坏吗?

    双亲委派机制原则在loadclass()方法中。只需要绕开loadclass()方法中即可。

    1、自定义类加载器 ,重写loadclass()方法

    2、使用线程上下文类加载器;

    JDBC使用线程上下文加载器打破了双亲委派机制。原因是JDBC只提供了接口,并没有提供实现。

9、扩展

有时候,面试会围绕如何破坏双亲委派机制来问 ,如tomcat 为了实现隔离性打破双亲委派机制。

1、 如何自定义自己的类加载器

2、ClassLoader 中 的loadClass()、findClass()、defineClass()区别?

3、 加载一个类采用Class.forName()和ClassLoader有什么区别

4、你了解 TomCat的类的加载机制吗?

5、为什么Tomca要打破双亲委派机制模型?

6、有没有听说过热加载或热部署,如何自己实现一个热加载?



    以上内容仅供参考,请合理利用搜索引擎!


JVM面试总结(二)的评论 (共 条)

分享到微博请遵守国家法律