类加载器ClassLoader-2

类装载器

大纲:

n  class装载验证流程java

n  什么是类装载器ClassLoadernode

n  JDK中ClassLoader默认设计模式编程

n  打破常规模式bootstrap

n  热替换设计模式

class装载验证流程:

n  加载数组

n  连接安全

–      验证数据结构

–      准备app

–      解析框架

n  初始化

加载

n  装载类的第一个阶段

取得类的二进制流[z1] 

转为方法区数据结构

Java堆中生成对应的java.lang.Class对象

连接

验证

n  连接 -> 验证

–      目的:保证Class流的格式是正确的

文件格式的验证

是否以0xCAFEBABE开头

版本号是否合理

元数据验证

是否有父类

继承了final类?

非抽象类实现了全部的抽象方法

1,  什么是元数据:

举例:任何文件系统中的数据分为数据和元数据。数据是指普通文件中的实际数据,而元数据指用来描述一个文件的特征的系统数据诸如访问权限、文件拥有者以及文件数据块的分布信息(inode...)等等。在集群文件系统中,分布信息包括文件在磁盘上的位置以及磁盘在集群中的位置。用户须要操做一个文件必须首先获得它的元数据才能定位到文件的位置而且获得文件的内容或相关属性。

2,元数据最大的好处是它使信息的描述和分类能够实现格式化从而为机器处理创造了可能。

3,  元数据概念:元数据是关于数据的数据。在编程语言上下文中,元数据是添加到程序元素如方法、字段、类和包上的额外信息。对数据进行说明描述的数据。

4,元数据的做用: 通常来讲,元数据能够用于建立文档(根据程序元素上的注释建立文档),跟踪代码中的依赖性可声明方法是重载,依赖父类的方法),执行编译时检查可声明是否编译期检测),代码分析。以下:

1)编写文档经过代码里标识的元数据生成文档

2)代码分析经过代码里标识的元数据对代码进行分析

3).编译检查经过代码里标识的元数据让编译器能实现基本的编译检查

五、java平台元数据

     注解Annotation就是java平台的元数据,是 J2SE5.0新增长的功能,该机制容许在Java 代码中添加自定义注释,并容许经过反射(reflection),以编程方式访问元数据注释。经过提供为程序元素(类、方法等)附加额外数据的标准方法元数据功能具备简化和改进许多应用程序开发领域的潜在能力,其中包括配置管理、框架实现和代码生成。

Annotation不直接影响程序的语义。然而,开发和部署工具能够读取这些注释,并以某种形式处理这些注释,可能生成其余 Java源程序、XML配置文件或者要与包含注释的程序一块儿使用的其余组件,从而影响运行状态的程序的语义。注释能够从源代码中读取,从编译后的.class文件中读取,也能够经过反射机制在运行时读取。

一、Annotation具备如下的一些特色: 
元数据以标签的形式存在于Java代码中。  
元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的。  
元数据须要编译器以外的工具额外的处理用来生成其它的程序部件。  
 元数据能够只存在于Java源代码级别,也能够存在于编译以后的Class文件内部。

二、JDK5.0前没出现语言级的元数据机制Annotation,java元数据的解决方案:

在注解诞生以前,程序的元数据存在的形式仅限于xml 部署描述文件java注释或javadoc,但注解能够提供更多功能,它不只包含元数据,还能做用于运行期,注解解析器可以使用注解决定处理流程

字节码验证 (很复杂)

n  运行检查

栈数据类型和操做码数据参数吻合

跳转指令指定到合理的位置

符号引用验证

常量池中描述类是否存在

访问的方法或字段是否存在且有足够的权限

符号引用是基于字符串的。

准备

n  连接 -> 准备

–      分配内存,并为类设置初始值 (方法区中)

•      public static int v=1;

•       在准备阶段中,v会被设置为0

•       在初始化的<clinit>中才会被设置为1

•       对于static final类型,在准备阶段就会被赋上正确的值

•      public static final  int v=1;

解析

n  连接 -> 解析

–     将符号引用替换为直接引用

–     符号引用是一个字符串,被引用对象不必定被加载

–     直接引用是指向一个对象的指针或者对象的地址偏移量,被引用的对象必定在内存

符号引用和直接引用:

简单来说符号引用就是一个字符串常量。好比:好比说我这个类的超类是java.lang.object.符号引用就是说,我在我这个class的常量池里面,我有一个字符串,字符串的内容就是java.lang.object.这就是符号引用。符号引用并不能直接的引用到要引用的内容,他只是一种表示的方式。要真正可以直接使用这个引用,那么咱们须要直接引用,直接引用就是指针指向一个内存地址或者地址偏移量(偏移量必定是有一个基地址,而后相对这个基地址偏移多少获得的那个地址),直接指向到要引用的内容。引用对象必定在内存,而符号引用,引用对象不必定被加载。

1.        符号引用(Symbolic References):

符号引用以一组符号来描述所引用的目标,符号能够是任何形式的字面量[z2] ,只要使用时可以无歧义的定位到目标便可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不必定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,所以只能使用符号引用来代替。好比org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,所以只能使用符号org.simple.Language(假设是这个,固然实际中是由相似于CONSTANT_Class_info[z3] 的常量来表示的)来表示Language类的地址。各类虚拟机实现的内存布局可能有所不一样,可是它们能接受的符号引用都是一致的,由于符号引用的字面量形式明肯定义在Java虚拟机规范的Class文件格式中。

 

2.        直接引用:直接引用能够是

(1)直接指向目标的指针(好比,指向“类型”【Class对象】、类变量、类方法的直接引用多是指向方法区的指针

(2)相对偏移量(好比,指向实例变量、实例方法的直接引用都是偏移量),偏移量就是内存地址。

(3)一个能间接定位到目标的句柄

直接引用是和虚拟机的布局相关的,同一个符号引用在不一样的虚拟机实例上翻译出来的直接引用通常不会相同。若是有了直接引用,那引用的目标一定已经被加载入内存中了。

初始化

n  执行 类构造器<clinit>

–      static变量的 赋值语句

–      static{}语句

子类的<clinit>调用前保证父类的<clinit>被调用

<clinit>是线程安全的

问题:

n  Java.lang.NoSuchFieldError错误可能在什么阶段抛出?

域不存在错误。当应用试图访问或者修改某类的某个域,而该类的定义中没有该域的定义时抛出该错误。

很显然是在连接的验证阶段的符号引用验证时会抛出这个异常,或者NoSuchMethodError等异常。

装载器ClassLoader

什么是类装载器ClassLoader?

n  ClassLoader是一个抽象类

ClassLoader的实例将读入Java字节码将类装载到JVM

n  ClassLoader能够定制,知足不一样的字节码流获取方式

n  ClassLoader负责类装载过程当中的加载阶段

JDK中ClassLoader默认设计模式

ClassLoader的重要方法

–     public Class<?> loadClass(String name) throws ClassNotFoundException

•       载入并返回一个Class(猜想它内部调用了defineClass方法)

–     protected final Class<?> defineClass(byte[] b, int off, int len)

•       定义一个类,不公开调用

•       真正完成类的加载工做是经过调用 defineClass来实现的;将字节数组转为方法区数据结构,在Java堆中生成对应的java.lang.Class对象等

–     protected Class<?> findClass(String name) throws ClassNotFoundException

•       loadClass会调用该方法,自定义ClassLoader的推荐作法,这个方法在jdk中是空实现,在jdk自带的加载器都没法加载该类时,会调用这个方法。

–     protected final Class<?> findLoadedClass(String name)

•       寻找已经加载的类

protected Class<?> findClass(String name) throws ClassNotFoundException {

        throw new ClassNotFoundException(name);

    }

protected Class<?> loadClass(String name, boolean resolve)

        throws ClassNotFoundException

    {

        synchronized (getClassLoadingLock(name)) {

            // First, check if the class has already been loaded

            Class c = findLoadedClass(name);

            if (c == null) {

                long t0 = System.nanoTime();

                try {

                    if (parent != null) {

                        c = parent.loadClass(name, false);

                    } else {

                        c = findBootstrapClassOrNull(name);

                    }

                } catch (ClassNotFoundException e) {

                    // ClassNotFoundException thrown if class not found

                    // from the non-null parent class loader

                }

 

                if (c == null) {

                    // If still not found, then invoke findClass in order

                    // to find the class.

                    long t1 = System.nanoTime();

                    c = findClass(name);

 

                    // this is the defining class loader; record the stats

                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

                    sun.misc.PerfCounter.getFindClasses().increment();

                }

            }

            if (resolve) {

                resolveClass(c);

            }

            return c;

        }

    }

加载有三步:

1,  找到或获得.class字节码文件(或字节码内容)。

2,  经过流,将字节码内容读取到内存中,并转化为字节数组。(这一步已经完成了加载到内存的操做)

3, 将该字节数组做为参数传入defineClass方法,在堆中定义该类的Class对象。(真正完成类的加载工做是经过调用 defineClass来实现的;将字节数组转为方法区数据结构,在Java堆中生成对应的java.lang.Class对象等

类加载器的分类

BootStrap ClassLoader (启动ClassLoader

Extension ClassLoader (扩展ClassLoader

App ClassLoader (应用ClassLoader/系统ClassLoader

Custom ClassLoader(用户自定义ClassLoader)

每一个ClassLoader都有一个Parent做为父亲(除了根加载器BootStrap ClassLoader,它是最顶级的)

类加载器的协同工做(父委托机制/双亲模式)

双亲模式存在的问题(父ClassLoader没法访问子ClassLoader加载的类的问题)

用Thread. setContextClassLoader()解决父ClassLoader没法访问子ClassLoader加载的类的问题

n  Thread. setContextClassLoader(ClassLoader cl)

–      上下文加载器

–      是一个角色

–      用以解决顶层ClassLoader没法访问底层ClassLoader加载的类的问题

–     基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

–     将类加载器对象在线程中传递,当咱们要加载一个类时,咱们能够从线程中获取这个类加载器来加载,这样能够保证当咱们想要用同一个加载器加载那些在不一样类加载器做用区域的类。

示例:

代码来自于

javax.xml.parsers.FactoryFinder

展现如何在启动类加载器加载AppLoader的类上下文ClassLoader能够突破双亲模式的局限性

static private Class getProviderClass(String className, ClassLoader cl,

            boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException

    {

        try {

            if (cl == null) {

                if (useBSClsLoader) {

                    return Class.forName(className, true, FactoryFinder.class.getClassLoader());

                } else {

                    cl = ss.getContextClassLoader();

                    if (cl == null) {

                        throw new ClassNotFoundException();

                    }

                    else {

                        return cl.loadClass(className); //使用上下文ClassLoader

                    }

                }

            }

            else {

                return cl.loadClass(className);

            }

        }

        catch (ClassNotFoundException e1) {

            if (doFallback) {

                // Use current class loader - should always be bootstrap CL

                return Class.forName(className, true, FactoryFinder.class.getClassLoader());

            }

            else {

                throw e1;

            }

        }

    }

 

ClassLoader getContextClassLoader() throws SecurityException{

        return (ClassLoader)

                AccessController.doPrivileged(new PrivilegedAction() {

            public Object run() {

                ClassLoader cl = null;

                //try {

                cl = Thread.currentThread().getContextClassLoader();

                //} catch (SecurityException ex) { }

 

                if (cl == null)

                    cl = ClassLoader.getSystemClassLoader();

 

                return cl;

            }

        });

    }

 

双亲模式的破坏

n  双亲模式的破坏

–      双亲模式是默认的模式,但不是必须这么作

–      Tomcat的WebappClassLoader 就会先加载本身的Class,找不到再委托parent

–      OSGi的ClassLoader造成网状结构,根据须要自由加载Class

示例:破坏双亲模式-  先从底层ClassLoader加载

自定义OrderClassLoader的部分实现:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    // First, check if the class has already been loaded

    Class re=findClass(name);//先尝试本身加载

    if(re==null){

        System.out.println(“没法载入类:”+name+“ 须要请求父加载器");

        return super.loadClass(name,resolve);//加载不了再委托父加载器加载

    }

    return re;

}

protected Class<?> findClass(String className) throws ClassNotFoundException {

Class clazz = this.findLoadedClass(className);

if (null == clazz) {

    try {

        String classFile = getClassFile(className);

        FileInputStream fis = new FileInputStream(classFile);

        FileChannel fileC = fis.getChannel();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        WritableByteChannel outC = Channels.newChannel(baos);

        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

         省略部分代码

        fis.close();

        byte[] bytes = baos.toByteArray();

        clazz = defineClass(className, bytes, 0, bytes.length);

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }

}

return clazz;

}

使用OrderClassLoader

OrderClassLoader myLoader=new OrderClassLoader("D:/tmp/clz/");

Class clz=myLoader.loadClass("geym.jvm.ch6.classloader.DemoA");

System.out.println(clz.getClassLoader());

System.out.println("==== Class Loader Tree ====");

ClassLoader cl=myLoader;

while(cl!=null){

    System.out.println(cl);

    cl=cl.getParent();

}

输出结果:

java.io.FileNotFoundException: D:\tmp\clz\java\lang\Object.class (系统找不到指定的路径。[z4] )

       at java.io.FileInputStream.open(Native Method)

       .....

       at geym.jvm.ch6.classloader.ClassLoaderTest.main(ClassLoaderTest.java:7)

没法载入类:java.lang.Object须要请求父加载器[z5] 

geym.jvm.ch6.classloader.OrderClassLoader@18f5824

==== Class Loader Tree ====

geym.jvm.ch6.classloader.OrderClassLoader@18f5824

sun.misc.Launcher$AppClassLoader@f4f44a

sun.misc.Launcher$ExtClassLoader@1d256fa

若是OrderClassLoader不重载loadClass(),只重载findClass,那么程序输出为

sun.misc.Launcher$AppClassLoader@b23210[z6] 

==== Class Loader Tree ====

geym.jvm.ch6.classloader.OrderClassLoader@290fbc

sun.misc.Launcher$AppClassLoader@b23210

sun.misc.Launcher$ExtClassLoader@f4f44a

不重载loadClass()方法,会使用双亲的模式,就会先让AppLoader加载

热替换

含义:

n  含义:

–      当一个class被替换后,系统无需重启,替换的类当即生效

–      例子:

•       geym.jvm.ch6.hot.CVersionA

public class CVersionA {

       public void sayHello() {

              System.out.println("hello world! (version A)");

       }

}

n  DoopRun 不停调用CVersionA . sayHello()方法,所以有输出:

–      hello world! (version A)

DoopRun 的运行过程当中,替换CVersionA 为:

public class CVersionA {

       public void sayHello() {

              System.out.println("hello world! (version B)");

       }

}

n  替换后, DoopRun 的输出变为

–      hello world! (version B)

 

 

 

 

 [z1]1,class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的。

2,字节是电脑里的数据量单位。字节码是一种中间码,它比机器码更抽象。它常常被看做是包含一个执行程序的二进制文件,更像一个对象模型。字节码被这样叫是由于一般每一个 opcode 是一字节长(java中自个字节占8个比特位)。

3,机器码就是说计算机能读懂的代码,简单点说就是给计算机执行的二进制代码.字节码,是JAVA语言专有的,它是JVM来执行的二进制代码。虽然都是二进制代码,可是因为执行它的哥们不同,因此它们存在一些指令集上的区别。

4,机器码彻底依附硬件而存在,而且不一样硬件因为内嵌指令集不一样,即便相同的0 1代码 意思也多是不一样的,换句话说,根本不存在跨平台性,好比:不一样型号的CPU,你给他个指令10001101,他们可能会解析为不一样的结果。

5,咱们知道JAVA是跨平台的,为何呢?由于他有一个jvm,不论那种硬件,只要你装有jvm,那么他就认识这个JAVA字节码,至于底层的机器码,咱不用管,有jvm搞定,他会把字节码再翻译成所在机器认识的机器码。

6,机器码就是0101这样的二进制代码,而二进制码要想操做机器就须要相应的指令系统而指令系统就是对二进制代码所表示意思的规定,好比0表示某个开关关,1表示某个开关开这样的.关于这一部分属于硬件部分了。

 [z2]

Java字面量(Java直接量)

int i = 1;把整数1赋值给int型变量i,整数1就是Java字面量

一样,String s = "abc";中的abc也是字面量。

 [z3]

1,常量池中的CONSTANT_Class_info能够看作是一个CONSTANT_Class数据类型的一个实例。 他是对类或者接口的符号引用。 它描述的能够是当前类型的信息, 也能够描述对当前类的引用, 还能够描述对其余类的引用。 也就是说, 若是访问了一个类字段, 或者调用了一个类的方法, 对这些字段或方法的符号引用, 必须包含它们所在的类型的信息,CONSTANT_Class_info就是对字段或方法符号引用中类型信息的描述。

2,CONSTANT_Class_info的第一个字节是tag值为7也就是说, 当虚拟机访问到一个常量池中的数据项, 若是发现它的tag值为7就能够判断这一个CONSTANT_Class_infotag下面的两个字节是一个叫作name_index的索引值它指向一个CONSTANT_Utf8_info这个CONSTANT_Utf8_info中存储了CONSTANT_Class_info要描述的类型的全限定名。

 [z4]由于先从OrderClassLoader加载,找不到父类Object,以后使用appLoader加载Object

 [z5]DemoA在ClassPath中,但由OrderClassLoader加载,由于方法中是先由本身加载,加载不了再父加载器加载。

 [z6]DemoA由AppClassLoader加载

相关文章
相关标签/搜索