虚拟机的类加载机制是指 把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。类的加载连接和初始化过程都是在程序运行期间完成的。
类的生命周期:
加载->连接(验证,准备,解析)->初始化->使用->卸载。解析有时候可以在初始化之后执行,这是为了支持Java语言的动态绑定。
虚拟机没有规定什么时候开始类加载过程的第一个阶段,加载,但是对于初始化阶段,虚拟机规定了5中情况下必须进行“初始化”,而加载,验证,准备自然需要在此之前。
1.使用new关键字进行实例化对象时,读取或者设置一个类的静态字段(被final修饰,在编译器就放入常量池的静态字段除外),以及调用一个类的静态方法时。
2.使用java.lang.reflect包的方法进行反射调用的时候,如果类没有进行过初始化,需要先出发其初始化。
3.当初始化一个类的时候,如果发现其父类还没有进行初始化,需要先初始化其父类。
4.虚拟机启动时,用户需要制定一个要执行的主类(包含main()方法那个类),虚拟机会先初始化这个类。
5.JDK 1.7的动态语言支持。
加载:
加载一共需要完成三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。
验证:
验证是连接阶段的第一步,这一阶段的目的就是为了确保Class文件的直接流中包含的信息符合当前虚拟机的要求。这也是对虚拟机的一种保护。
准备:
这个阶段很容易和初始化阶段搞混,准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量的内存都在方法区中进行分配。
这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配到堆中。初始值是数据类型的零值。
解析:
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
直接引用是直接指向目标的指针,相对偏移量或者一个能间接定位到目标的句柄。如果有了直接引用,引用的目标必定已经存在内存中了。
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量。
初始化:
类的初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的Java代码。
在准备阶段,变量已经赋过一次系统要求的初始值,而初始化阶段,则根据程序员的需求去赋值。
类加载器
"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到了虚拟机外部去实现,以便让程序自己决定如何去获取所需要的类,实现这个动作的模块称为“类加载器”。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,通俗的说,对于两个类,只要加载他们的类加载器不同,这两个类就必定不相等。
双亲委派模型
在虚拟机的角度来讲,只存在两种不同类的加载器,一种是启动类加载器,这个加载器使用C++实现,是虚拟机的一部分,另一种就是所有其他类的加载器,由Java语言实现。
从Java开发人员的角度来讲,类加载器可以分为三种。
启动类加载器,负责加载<JAVA_HOME>\lib目录中
扩展类加载器,负责加载<JAVA_HOME>\lib\ext,开发者可以直接使用
应用程序类加载器,负责加载用户类路径(ClassPath)上所指定的类库,一般情况下,这个就是程序中默认的类加载器。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次都是如此,因此所有加载请求都会传到顶层的启动类加载器中,只有父类加载器无法完成这个加载请求时(在他的类路径中找不到所需要的类)。子类加载器才会尝试去加载。
这保证了类的唯一性。