|
1 | 1 | ## 一、类加载机制
|
2 |
| -###1.定义: |
| 2 | + |
| 3 | +### 1.定义: |
| 4 | + |
3 | 5 | 把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
|
| 6 | + |
4 | 7 | 在Java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点来实现的。
|
5 | 8 |
|
6 |
| -###2.类的生命周期: |
| 9 | +### 2.类的生命周期: |
| 10 | + |
7 | 11 | 加载,验证,准备,解析,初始化,使用和卸载。其中验证,准备,解析3个部分统称为连接。
|
8 | 12 |
|
9 | 13 | 这7个阶段发生顺序如下图:
|
|
24 | 28 |
|
25 | 29 | ④.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
|
26 | 30 |
|
27 |
| -⑤.当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出发初始化。 |
| 31 | +⑤.当使用JDK1.7的动态语言支持时,如果一个`java.lang.invoke.MethodHandle`实例最后的解析结果`REF_getStatic,REF_putStatic,REF_invokeStatic`的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出发初始化。 |
28 | 32 |
|
29 | 33 | ### 4.类加载的具体过程:
|
| 34 | + |
30 | 35 | **加载:**
|
31 | 36 |
|
32 | 37 | ①.通过一个类的全限定名来获取定义此类的二进制字节流
|
@@ -79,32 +84,39 @@ d.符号引用验证
|
79 | 84 |
|
80 | 85 | `<clinit>()`方法是由编译器自动收集类中的**所有类变量的赋值动作和静态语句块中的语句合并产生的**。
|
81 | 86 |
|
82 |
| -<clinit>()与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。 |
| 87 | +`<clinit>()`与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的`<clinit>()`方法执行之前,父类的`<clinit>()`方法已经执行完毕。 |
83 | 88 |
|
84 | 89 | **简单地说,初始化就是对类变量进行赋值及执行静态代码块。**
|
| 90 | + |
85 | 91 | ## 二、类加载器
|
86 | 92 | 通过上述的了解,我们已经知道了类加载机制的大概流程及各个部分的功能。其中加载部分的功能是将类的class文件读入内存,并为之创建一个java.lang.Class对象。这部分功能就是由类加载器来实现的。
|
| 93 | + |
87 | 94 | ### 1.类加载器分类:
|
| 95 | + |
88 | 96 | 不同的类加载器负责加载不同的类。主要分为两类。
|
89 | 97 |
|
90 |
| -**启动类加载器(Bootstrap ClassLoader):**由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中,即负责加载Java的核心类。 |
| 98 | +**启动类加载器(Bootstrap ClassLoader):** 由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中,即负责加载Java的核心类。 |
91 | 99 |
|
92 |
| -**其他类加载器:**由Java语言实现,继承自抽象类ClassLoader。如: |
| 100 | +**其他类加载器:** 由Java语言实现,继承自抽象类ClassLoader。如: |
93 | 101 |
|
94 |
| -**扩展类加载器(Extension ClassLoader):**负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库,即负责加载Java扩展的核心类之外的类。 |
| 102 | +**扩展类加载器(Extension ClassLoader):** 负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库,即负责加载Java扩展的核心类之外的类。 |
95 | 103 |
|
96 |
| -**应用程序类加载器(Application ClassLoader):**负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,通过ClassLoader.getSystemClassLoader()方法直接获取。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。 |
| 104 | +**应用程序类加载器(Application ClassLoader):** 负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,通过ClassLoader.getSystemClassLoader()方法直接获取。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。 |
97 | 105 |
|
98 | 106 | 以上2大类,3小类类加载器基本上负责了所有Java类的加载。下面我们来具体了解上述几个类加载器实现类加载过程时相互配合协作的流程。
|
| 107 | + |
99 | 108 | ### 2.双亲委派模型
|
| 109 | + |
100 | 110 | 双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
|
101 | 111 |
|
102 | 112 | 
|
103 | 113 |
|
104 | 114 | 这样的好处是不同层次的类加载器具有不同优先级,比如所有Java对象的超级父类java.lang.Object,位于rt.jar,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。即使用户自己编写一个java.lang.Object类并放入程序中,虽能正常编译,但不会被加载运行,保证不会出现混乱。
|
105 | 115 |
|
106 | 116 | ### 3.双亲委派模型的代码实现
|
| 117 | + |
107 | 118 | ClassLoader中loadClass方法实现了双亲委派模型
|
| 119 | + |
108 | 120 | ```java
|
109 | 121 | protected Class<?> loadClass(String name, boolean resolve)
|
110 | 122 | throws ClassNotFoundException
|
@@ -146,6 +158,7 @@ protected Class<?> loadClass(String name, boolean resolve)
|
146 | 158 | }
|
147 | 159 | }
|
148 | 160 | ```
|
| 161 | + |
149 | 162 | 整个流程大致如下:
|
150 | 163 |
|
151 | 164 | a.首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
|
|
0 commit comments