上篇中我们讲解了Class文件,这篇我们说说虚拟机是如何加载这些Class文件的?Class文件中的信息进入到虚拟机后会发生什么变化?这就涉及到了类加载机制。 类加载机制是把类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。这一系列的过程都是在程序运行期间完成的。 类加载器 类加载器就是下图中红框的部分,它通过一个类的全限定名来获取描述此类的二进制字节流,从而将java类动态地加载进JVM的内存空间中。 适用情景 对于一个非数组类的加载阶段,可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器去完成。 双亲委派机制 双亲委派机制是类加载所采取的一种方式。如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。每一层的类加载器均是如此。只有当父加载器反馈自己无法完成这个请求时,子加载器才会尝试自己去加载。 类比到现实:小明想买一个玩具挖土机,可他又不好意思直接张口说。所以,发生了下面的对话。 小明去问他爸爸:爸爸你有挖土机吗? 分类 启动类加载器是使用C++实现的,是虚拟机自身的一部分。 好处 以String类为例。就算是用户自己写了一个String类的实现,那对此类进行加载时,也只会委派给启动类加载器来对JDK中原本的String类进行加载,而自定义的String类永远不会被调用。这样保证了系统的安全。 什么时候进行类加载? 有且只有以下5种方式必须立即对类进行加载 (1)使用new实例化对象的时候;读取或配置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候;调用一个类的静态方法的时候。 (2)使用java.lang.reflect包的方法对类进行反射调用的时候。如果类没有进行过初始化,则需要先触发其初始化。 (3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 (4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个主类 类加载过程详述 类加载过程分为5步。大部分都是由虚拟机主导和控制的,除了以下两种情形: 在加载阶段 开发人员可以通过自定义类加载器参与 在初始化阶段 会执行开发人员的代码去初始化类变量和其它资源 1、加载 虚拟机需要完成的事情:
2、验证 验证的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全。 3、准备 此阶段是正式为类变量分配内存并设置类变量初始值的阶段。其是在方法区中进行分配的。有两个注意点: (1)此时只是对类变量(被static修饰的变量)进行内存分配,而不是对象变量。给对象分配内存是在对象实例化时,随着对象一起分配到java堆中。 (2)如果一个类变量没有被final修饰,则其初始值是数据类型的零值。比如int类型的是0,boolean类型的是false。举个例子来说明:
在准备阶段过后的初始值为0而不是123,因为这个时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器< clinit >()方法之中。所以把value赋值为123的动作将在初始化阶段才会执行。
此时因为有final,所以在准备阶段value就已经被赋值为123了。 4、解析 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。可对类或接口、字段、类方法、接口方法等进行解析。 符号引用是什么: 符号引用就是包含类的信息,方法名,方法参数等信息的字符串,它供实际使用时在该类的方法表中找到对应的方法。 直接引用是什么: 直接引用就是偏移量,通过偏移量可以直接在该类的内存区域中找到方法字节码的起始位置。 5、初始化 此阶段用于初始化类变量和其它资源,是执行类构造器< clinit >()方法的过程,此时才是真正开始执行类中定义的java程序代码。 以上是对JAVA虚拟机类加载机制的详细讲解,更多相关问题请访问PHP中文网:JAVA视频教程 (责任编辑:admin) |