类加载机制的奥妙。
10年积累的成都做网站、成都网站制作、成都外贸网站建设经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先做网站设计后付款的网站建设流程,更有水磨沟免费网站建设让你可以放心的选择与我们合作。
1、什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
类加载器并不需要等到某个类被“***主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序***主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
加载.class文件的方式
从本地系统中直接加载
通过网络下载.class文件
从zip,jar等归档文件中加载.class文件
从专有数据库中提取.class文件
将Java源文件动态编译为.class文件
2、类的生命周期
其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。
另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
加载
查找并加载类的二进制数据加载时类加载过程的***个阶段,在加载阶段,虚拟机需要完成以下三件事情:
相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性***的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。
连接
验证:确保被加载的类的正确性
验证是连接阶段的***步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备:为类的静态变量分配内存,并将其初始化为默认值
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
假设一个类变量的定义为:public static int value = 3;
那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的public static指令是在程序编译后,存放于类构造器
这里还需要注意如下几点:
3、如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。
假设上面的类变量value被定义为: public static final int value = 3;
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。我们可以理解为static final常量在编译期就将其结果放入了调用它的类的常量池中
解析:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
JVM初始化步骤
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
结束生命周期
在如下几种情况下,Java虚拟机将结束生命周期
3、类加载器
寻找类加载器,先来一个小例子
- package com.neo.classloader;
- public class ClassLoaderTest {
- public static void main(String[] args) {
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- System.out.println(loader);
- System.out.println(loader.getParent());
- System.out.println(loader.getParent().getParent());
- }
- }
运行后,输出结果:
- sun.misc.Launcher$AppClassLoader@64fef26a
- sun.misc.Launcher$ExtClassLoader@1ddd40f3
- null
从上面的结果可以看出,并没有获取到ExtClassLoader的父Loader,原因是Bootstrap Loader(引导类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null。
这几种类加载器的层次关系如下图所示:
注意:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的。
站在Java虚拟机的角度来讲,只存在两种不同的类加载器:启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分;所有其它的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。
站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:
启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。
应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
JVM类加载机制
4、类的加载
类加载有三种方式:
例子:
- package com.neo.classloader;
- public class loaderTest {
- public static void main(String[] args) throws ClassNotFoundException {
- ClassLoader loader = HelloWorld.class.getClassLoader();
- System.out.println(loader);
- //使用ClassLoader.loadClass()来加载类,不会执行初始化块
- loader.loadClass("Test2");
- //使用Class.forName()来加载类,默认会执行初始化块
- //Class.forName("Test2");
- //使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块
- //Class.forName("Test2", false, loader);
- }
- }
demo类
- public class Test2 {
- static {
- System.out.println("静态初始化块执行了!");
- }
- }
分别切换加载方式,会有不同的输出结果。
5、双亲委派模型
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
双亲委派机制:
ClassLoader源码分析:
- public Class> loadClass(String name)throws ClassNotFoundException {
- return loadClass(name, false);
- }
- protected synchronized Class> loadClass(String name, boolean resolve)throws ClassNotFoundException {
- // 首先判断该类型是否已经被加载
- Class c = findLoadedClass(name);
- if (c == null) {
- //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
- try {
- if (parent != null) {
- //如果存在父类加载器,就委派给父类加载器加载
- c = parent.loadClass(name, false);
- } else {
- //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
- c = findBootstrapClass0(name);
- }
- } catch (ClassNotFoundException e) {
- // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
- c = findClass(name);
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
双亲委派模型意义:
6、自定义类加载器
通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。比如应用是通过网络来传输 Java类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。
自定义类加载器一般都是继承自ClassLoader类,从上面对loadClass方法来分析来看,我们只需要重写 findClass 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:
- package com.neo.classloader;
- import java.io.*;
- public class MyClassLoader extends ClassLoader {
- private String root;
- protected Class> findClass(String name) throws ClassNotFoundException {
- byte[] classData = loadClassData(name);
- if (classData == null) {
- throw new ClassNotFoundException();
- } else {
- return defineClass(name, classData, 0, classData.length);
- }
- }
- private byte[] loadClassData(String className) {
- String fileName = root + File.separatorChar
- + className.replace('.', File.separatorChar) + ".class";
- try {
- InputStream ins = new FileInputStream(fileName);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- int bufferSize = 1024;
- byte[] buffer = new byte[bufferSize];
- int length = 0;
- while ((length = ins.read(buffer)) != -1) {
- baos.write(buffer, 0, length);
- }
- return baos.toByteArray();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- public String getRoot() {
- return root;
- }
- public void setRoot(String root) {
- this.root = root;
- }
- public static void main(String[] args) {
- MyClassLoader classLoader = new MyClassLoader();
- classLoader.setRoot("E:\\temp");
- Class> testClass = null;
- try {
- testClass = classLoader.loadClass("com.neo.classloader.Test2");
- Object object = testClass.newInstance();
- System.out.println(object.getClass().getClassLoader());
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:
【本文为专栏作者“纯洁的微笑”的原创稿件,转载请通过微信公众号联系作者获取授权】
分享标题:jvm系列(一):java类的加载机制
标题链接:http://www.csdahua.cn/qtweb/news39/229989.html
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网