Java 运行环境为了优化系统,提高程序的执行速度,在 JRE 运行的开始会将 Java 运行所需要的基本类采用预先加载( pre-loading )的方法全部加载要内存当中,因为这些单元在 Java 程序运行的过程当中经常要使用的,主要包括 JRE 的 rt.jar 文件里面所有的 .class 文件。

当 java.exe 虚拟机开始运行以后,它会找到安装在机器上的 JRE 环境,然后把控制权交给 JRE , JRE的类加载器会将 lib 目录下的 rt.jar 基础类别文件库加载进内存,这些文件是 Java 程序执行所必须的,所以系统在开始就将这些文件加载,避免以后的多次 IO 操作,从而提高程序执行效率。

相对于预先加载,我们在程序中需要使用自己定义的类的时候就要使用依需求加载方法( load-on-demand ),就是在 Java 程序需要用到的时候再加载,以减少内存的消耗,因为 Java 语言的设计初衷就是面向嵌入式领域的。

而当且仅当以下四种请款之一发生时,才会进行类的加载:

1、遇到new、putstatic 、invokestatic、getstatic 时,若类未进行初始化,则先触发其初始化。

2、进行类的反射时,若类未进行初始化,则先触发其初始化。

3、初始化一个类时,若父类还未初始化,则先触发父类的初始化。

4、虚拟机启动时,包含main()方法的类,需要先进行初始化。

除此之外的任何情况,都不会发生初始化,而是被动引用。

 当遇到以上任何一种情况时,便发生以下过程:

1、加载。

在加载阶段,需要做三件事:

1) 通过一个类的全限定名(包括包名的类名),获取定义此类的二进制字节流。

2) 将这个字节流所代表的静态存储结构,转化为方法区的运行时数据结构。

3) 在Java堆生成一个代表这个类的java.lang.Class 对象,作为方法区这些数据的访问入口。

在这个阶段,在方法区和堆中都生成了该对象的空间。但此时的对象并没有数据。

2、验证。

为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全。

有文件格式验证、元数据验证、字节码验证和符号引用验证。

3、准备。

准备阶段是为类变量分配内存并设置类变量初始值的阶段,而分配的都是在方法区的内存。没有堆中的空间分配,故不是实例变量值(new Object()中的值)的分配,而是类变量(static 修饰的静态变量)的分配。但这时候并未开始执行任何Java方法,故静态变量的值为初始值(例如static int value = 123;而value的值这个阶段为0),真正的赋值是在初始化阶段中。

4、解析

解析阶段中JVM将常量池内的符号引用替换为直接引用的过程。在此之前,在Class文件中的类、接口、方法、字段都是以CONSTANT_Class_info、CONSTANT_Methodref_info、CONSTANT_Fieldref_info等类型的常量形式出现的,这些称为符号引用。而经过这个阶段之后,这些符号引用会替换成直接引用,即常量池内的引用能够直接指向目标,使用指针、或者句柄。

有类或接口的解析、字段解析、类方法解析、接口方法解析。

5、初始化

在这个阶段,各个实例变量才真正有值,通过调用构造器<clinit>()方法进行对变量的赋值。

 

几个阶段如下图所示:

Java加载jar包路径 jvm加载jar包_Java加载jar包路径

 

二、类加载器

1、双亲委派模型

三种类加载器:启动类加载器、扩展类加载器、应用程序类加载器。这些,构成了一个“双亲委派模型”。下层的类加载请求都委派给上层的加载器进行,除非父类无法加载时,子类才会自己加载。借助这种模型,使得java类也具备了一种优先级的层次关系。因为他使得那些放在rt.jar中的类(用启动类加载器加载),无论哪个类加载器都要加载这些类,都必须最终委派给启动类加载器加载(除非加载路径中没有该类),这样就保证了那些java自带类的唯一性。


                                                        

Java加载jar包路径 jvm加载jar包_Java加载jar包路径_02

    尽管类加载器遵循的是这种委托关系,但这也使得类加载器变得不够灵活。当有一些第三方的应用类需要进行加载,且需要使用他自己提供的类加载器进行类加载,这个委托体系就变得无法满足需求了。例如,JNDI服务的加载,就是使用启动类加载器进行加载,就但这些类的加载,需要让JNDI接口提供者去进行,而启动类加载器并不认识这些代码,怎么办?

    Java设计团队通过引入线程上下文类加载器来解决。他可以通过Thread.setContextClassLoader()方法进行设置,这样就可以实现启动类加载器将请求委派给子类了。

2、判断两个类是否“相等”

因此,判定两个类是否相等,需要满足两个条件:(1)相同的Class 文件。(2)相同的类加载器加载。否则,都不是相同的类。