面试必问系列之JVM类加载过程及双亲委派模型 全球今亮点

来源:一只IT攻城狮 2023-03-16 21:57:57


【资料图】

一、类加载过程

类的7个生命周期:加载 ->验证 ->准备 ->解析 ->初始化 ->使用 ->卸载

1、加载

加载阶段主要做三件事:

1)加载指的是将类的class文件读入到内存(也就是加载到方法区),并为之创建一个java.lang.Class对象。

2) 类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

3)通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:

从本地文件系统加载class文件,大部分程序的类加载方式。从zip/jar/war/Ear等包中读取,如JDBC编程时数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。从网络中获取二进制字节流.典型就是Applet.。运行时计算生成(动态代理)。把一个Java源文件动态编译,并执行加载。其它文件生成。由JSP文件中生成对应的Class类。

2、验证

验证阶段主要完成四种校验:

检验被加载的类是否有正确的内部结构,并和其他类协调一致,确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。(验证阶段是操作二进制文件流阶段,剩下的阶段都是内存方法区的事了。验证阶段会损耗一些性能,如果所运行的代码已经被反复使用和验证过,那么可以使用-Xverify:none参数关闭,以缩短类加载时间,但是不建议。)

文件格式验证:主要验证字节流是否符合Class文件格式规范cafe babe,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据对方法体的验证。符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。

3、准备

类准备阶段完成两件事情:

为已在方法区中的类的静态成员变量分配内存;为静态成员变量设置初始值,初始值为0、false、null等

4、解析

解析是指虚拟机将类的二进制数据中的符号引用替换成直接引用的过程。

符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用(真实地址):是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。

5、初始化

初始化是真正执行类中定义的java程序代码,为类的静态变量赋予正确的初始值。

“有且只有”,这5种场景中的行为称为对一个类进行主动引用:

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

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

③.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

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

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

二、类加载器

在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。

JVM预定义加载器主要有三种:

1、启动类加载器(bootstrap class loader)

启动类加载器负责将存放在\lib目录中的,也就是中jre/lib/rt.jar里所有的class,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。

2、扩展类加载器(Extension ClasLoader):

这个加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

3、应用程序类加载器(系统类加载器)

这个类加载器是由sun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是ClassLoader中的getSystemClassLoader方法的返回值,所以也叫系统类加载器。它负责加载用户类路径classpath上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

三、类加载机制

JVM的类加载机制主要有如下3种:

1、全盘负责

就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。

2、双亲委派

先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类,依次向上找。

双亲委派模型:

双亲委派优点(安全稳定):

1)当一个类收到类加载请求时,他首先不自己尝试去加载,而是层层向上委派给自己的父类,每一层加载器都如此,因此所有的请求都应该传送到启动类加载器中。

2)具备了带有优先级的层次关系,通过层级关系避免类的重复加载,最主要的为了安全,java核心api中定义的类型不会被随意篡改。

3)打破双亲委派模型:

打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。

3、缓存机制

缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

关键词:

为你推荐

Copyright   2015-2022 大西洋家具网 版权所有  备案号:沪ICP备2020036824号-2   联系邮箱: 562 66 29@qq.com