jvm类加载3

Java类加载过程(详细)

1、加载:查找并加载类的二进制数据(读入虚拟机)
2、连接:
(1)验证:确保被加载的类的正确性
(2)准备:Java虚拟机为类的静态变量分配内存,并将其初始化为默认值(例如,静态的整型值初始化为默认值为0,但是第三个阶段初始化阶段由程序员主观显示的赋值才是真正的初始化,这里Java虚拟机事先设置默认初值只是为了防止空异常)
(3)解析:在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用。
3、初始化:为类的静态变量赋正确的初始值(程序员主观初始化赋值,例static int a=1)
4、类实例化:
(1)为新的对象分配内存
(2)为实例变量赋默认值
(3)为实例变量赋正确的初始值
(4)Java编译器为它编译的每一个类文件都至少生成一个实例初始化方法,在Java的class文件中,这个实例初始化方法被称为“”。针对源代码中的每一个类的构造方法,Java编译器都产生一个“”方法。
5、卸载:从内存中销毁类

类的加载

1、类的加载的最终产品是位于内存中的class对象
2、class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口

3、有两种类型的类加载器
1)Java虚拟机自带的加载器
(1)根类加载器(BootStrap 启动类加载器),没有父类加载器,由C++实现,不是ClassLoader的子类,内建于Jvm中,是jvm的一部分,会加载java.lang.ClassLoad一起其他的Java平台类,当jvm启动时,启动类加载器会加载扩展类加载器和系统类加载器。(除启动类加载器之外,其他类加载器都是由Java实现。启动类加载器是特定于平台的及其指令,负责开启震哥哥加载过程),启动类加载器还负责加载提供jre正常运行所需要的基本组件,包括java.util和java.lang中的包等等。

(2)扩展类加载器(Extension),父类加载器是根类加载器(需要将class文件打包成jar包,才能加载)
(3)系统(应用)类加载器(System),父类加载器是扩展类加载器

注:除根类加载器没有父类加载器外,其他加载器都有且仅有一个父类加载器。当Java程序请求加载器时,首先会去请求其父类加载器,若父类加载器能完成加载,则由父类加载器完成,若不能,再由子类加载器完成,这种模式称为双亲加载机制。

2)用户自定义的类加载器
(1)java.lang.ClassLoader的子类
(2)用户可以定制类的加载方式

注:所有的用户自定义加载器都继承自CLassLoad类
注:类加载器并不需要等到某个类被“首次主动使用”时再加载它

4、jvm规范允许类加载器再预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)

5、如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

类的验证

1、类被加载后,进入连接阶段。连接就是将已经读入到内存中的类的二进制数据合并到虚拟机的运行时环境中去。

2、类的验证的主要内容
(1)类文件的结构检查
(2)语义检查
(3)字节码验证
(4)二进制兼容性的验证

类的初始化

步骤:
1)假如类还没有加载和连接,那么就先加载和连接
2)假如类存在父类,并且父类还没有初始化,那就先初始化直接父类(不适用于接口)
3)假如类中存在初始化语句,那就依次执行这些初始化并语句

类加载器的双亲委托机制

1、在双亲委托机制中,各个加载器按照父子关系形成了逻辑上的树形结构(物理上没有关系),除了根类加载器之外,其余的类加载器都有且仅有一个父加载器
2、当Java程序请求加载器时,首先会去请求其父类加载器,若父类加载器能完成加载,则由父类加载器完成,若不能,再由子类加载器完成,这种模式称为双亲委托机制。(若父类再有父类,就继续委托给父类,层层往上委托一直到根类加载器)
Image text
3、定义类加载器:能够成功加载你所要加载类的加载器
4、初始化加载器:所有能成功返回Class对象引用的类加载器(包括定义类加载器)

例子:

1
2
3
4
5
6
7
8
9
10
11
package jvm;

public class ClassLoadTest {

public static void main(String[] args) throws ClassNotFoundException {
//得到java.lang.String类
Class<?> clazz=Class.forName("java.lang.String");
//得到String类的类加载器
System.out.println(clazz.getClassLoader());
}
}

输出:

1
null

若一个类的类加载器是根加载器,则用null来表示,因为根加载器是由C++实现的(不过不同版本jvm有不同的表示)。
由输出知:String类的加载器是根加载器

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package jvm;

public class ClassLoadTest {

public static void main(String[] args) throws ClassNotFoundException {
//得到LittleTest对类
Class<?> clazz=Class.forName("jvm.LittleTest");
//得到LittleTest类的类加载器
System.out.println(clazz.getClassLoader());
}
}
//自定义内部类LittleTest
class LittleTest{

}

输出:

1
2
sun.misc.Launcher$AppClassLoader@2a139a55
//AppClassLoader应用类加载器

由输出知:内部类LittleTest的类加载器为应用(系统)类加载器

注:对于数组类型,它的类加载器是由Java虚拟机在运行期动态创建的,Java虚拟机返回的类加载器类型与数组中元素的类加载器类型一样(例如一个string类型的数组,String的类加载器是根类加载器,那么Java虚拟机为数组类型创建的类加载器也是根类加载器,),如果元素类型为原生类型,则没有类加载器(例如int类型)。

5、好处:
1)可以确保Java核心类库的安全:所有的Java应用都至少会引用java.lang.Object,也就是说说运行期java.lang.Object这个类会被加载到Java虚拟机中,如果这个加载过程是由Java自定义类加载器所完成的,那么很可能在jvm中存在多个不同版本的java.lang.Object类,而这些类是不兼容的,相互不可见(命名空间)
2)借助于双亲委托机制,Java核心库中的类加载工具都是由启动类加载器来统一完成的,从而确保了Java应用所使用的都是同一个版本的Java核心库,他们之间相互兼容。
3)可以确保Java核心库所提供的类不会被自定义的类所替代
3)不同的类加载器可以为相同名称的类创建额外的命名空间。相同名称的类可以并存在jvm中,只需要用不同的命名空间加载他们即可。不同类加载器所加载的类之间不兼容,相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间。

命名空间

1、每个类加载器都有自己的命名空间,命名空间由该加载器及其所有的父类加载器所加载的类组成
2、在同一个命名空间中不会出现类的完整名字(包括类的包名)相同的两个类,所以一个类只会被加载一次(再次加载价将返回同第一次已经加载的结果)
3、在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类,完整名字(包括类的包名)相同的两个类可以分别的不同命名空间被加载
4、同一个命名空间内的类是相互可见的
5、子加载器的命名空间包括所有父类的命名空间,因此子加载器加载的类能看见父加载器加载的类。例如系统类加载器加载的类能看见根类加载器加载的类。
6、由父加载器加载的类不能看见子加载器加载的类
7、如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类互不可见
8、在运行期,一个Java是由该类的全类名和用于加载该类的定义类加载器所共同决定的。如果同样名字(全类名)的类由两个不同的加载器所加载,那么这些类就是不同的,即便.class文件的字节码完全一样,并且从相同位置加载亦如此。

注:子加载器所加载的类能够访问父加载器所加载的类,父加载器所加载的类无法访问子加载器所加载的类

类的卸载

1、当一个类被加载、连接、初始化之后,它的生命周期就开始了。当这个类的class对象不再被引用,即不可触及时,class对象就会结束生命周期,该类在方法区内的数据也会被卸载,从而结束该类的生命周期。
2、一个类何时结束生命周期,取决于代表它的class对象何时结束生命周期。
3、由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。
4、Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。Java虚拟机会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的class对象,因此这些class对象始终是可触及的。
5、有用户自定义的类加载器所加载的类是可以被卸载的。

forName()方法分析