Java中的类加载和双亲委派原则

Java的类加载机制

1.类加载的过程

Java中的类加载和双亲委派原则_第1张图片
一般类的加载过程如上图所示:

①加载(Loading)

加载是指将类的class文件读到内存中,并为其创建一个java.lang.Class对象(每个类都有其独一无二的.Class对象),类加载由JVM中的类加载器完成,且其加载一般符合"双亲委派原则",(下文会简单的介绍类加载器和双亲委派原则,不要担心),除此之外,还可以自定义类加载器对类进行初始化;
通过不同的类加载器,可以从不同的源加载类的二进制数据文件:

1.从本地文件系统加载class文件。
2.从JAR包加载class文件,这种方式也是很常见的,JDBC编程时用到的数据库驱动类(com.mysql.jdbc.Driver)就放 在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
3.通过网络加载class文件。
4.把一个Java源文件动态编译,并执行加载。

②验证(Verification)

用于检测被加载的类是否具有正确的数据结构,并和其他类协调一致;验证的目的在于保证Class文件的字节流包含的信息符合当前虚拟机的要求,不会危害虚拟机的安全。其可以分为以下几种验证方式:
1)文件格式验证
主要验证字节流是否符合文件规范,并且可以被当前的虚拟机处理;
2)元数据验证
对字节码表述的信息进行验证,判断是否符合Java规范;
3)字节码验证
分析数据流和控制,确定语义合法,保证类方法在运行时不会有危害;
4)符号引用验证
主要是针对符号引用转换为直接引用的时候,去确定访问类型涉及到的引用情况,保证引用一定可以被访问的到;(符号引用的格式被明确规定在Class文件中)

③准备(Preparation)

类准备阶段负责为类的静态变量分配内存,并设置默认值;

④解析(Resolution)

将类中的二进制数据中的符号引用替换成直接引用。

符号引用以一组符号来描述所引用的对象,符号可以是任意形式的字面量,只要在使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中,在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中


直接引用:直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
(3)一个能间接定位到目标的句柄
直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了


⑤初始化

初始化是为类的静态变量赋予正确的值;参考第三步【准备】阶段,一个是置默认值,一个是置初始值;如下实例:

static a=3;
在准备阶段a只是准备好但是没有赋值,所以a=0;
在初始化阶段会为a赋初始值,所以a=3;


	但是这里得注,如果这样定义 final static a=3;这个时候a是一个宏变量,Java编辑器会在编译时
直接把这个变量赋值,往后不会再进行初始化;

2.类加载的时机

何时会出现类加载的过程尼?
1)访问类的实例(new对象的时候);
2)访问某个类或者接口的静态变量,或者对该静态变量赋值;
3)调用类的静态方法;
4)反射
5)初始化一个类的子类(会先初始化父类)父类static——>子类static——>父类构造——>子类构造
6)JVM启动时标明的启动类,即文件名和类名相同的类;

3.类加载器

类加载器负责加载所有的类,其为所有被载入的类生成一个java.lang.Class实例对象。一旦一个类被加载就不会被再次载入;因为每个类都有其独一无二的标识。在JVM中,一个类用其全限定类名和其类加载器作为唯一的标识。
JVM的类加载器分为下述三种


①启动类加载器(BootStrap Class Loader)
引导类装入器是用本地代码实现的类装入器,它负责将 /lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作


②扩展类加载器(Extension Class Loader)
扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中,其实就是加载扩展的jre。开发者可以直接使用标准扩展类加载器。


③系统类加载器(System Class Loader)
系统类加载器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器

4.类加载的双亲委派机制

JVM在加载加载类的时候默认采用的是双亲委派机制;通俗讲就是某个特定的类加载器在接收到加载类的任务请求时,首先将加载任务委托给父类加载器进行加载,然后父类加载器又向上委托,以此类推一直到加载任务处于BootStrap Class Loader中,其中如果父类加载器可以执行加载则加载成功或直接返回,若父类没有成功加载,则由子类加载器进行加载,再次以此类推;


这里盗用人家一个图,来展示以下加载过程
Java中的类加载和双亲委派原则_第2张图片

5.大牛的博客,可以去看看,讲的很清楚(内附测试代码)

https://blog.csdn.net/zhoudaxia/article/details/35824249

你可能感兴趣的:(理论)