`

5、java类加载器

    博客分类:
  • java
 
阅读更多
讲解之前先引入几个问题:
1.jvm如何识别.class文件?
2.jvm如何加载.class文件里面的字节码?
3.jvm如何创建类、对象、方法、属性?

jvm是通过.class文件的二进制流转换成16进制,得到前8位字符串cafebabe认为这是一个.class文件;


由上图可知类加载器是JVM的一部分,主要作用是将字节码加载进入执行引擎,以供执行。当调用java.exe执行一个.class文件时,从而根据%JAVA_HOME%\jre\lib\i386\jvm.cfg配置来选择激活jvm,初始化工作完成之后便启动Bootstrap Loader(引导类)加载器,它由C++编写。JVM中另外两个内置类加载器是ExtClassLoader和AppClassLoader,它们定义在sun.misc.Launcher.class中,为内部类,且由Bootstrp Loader加载进入虚拟机。
启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
标准扩展(Extension)类加载器:扩展类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
系统(System)类加载器:系统类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。
除了以上列举的三种类加载器,还有一种比较特殊的类型就是线程上下文类加载;

类加载器也是Java类,本身也要被类加载器加载,第一个类加载器不是java类,而是BootStrap。Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。

1、类加载器的委派模型:假设AppClassLoader需要加载一个类,它会首先委托其父加载器ExtClassLoader来加载此类,ExtClassLoader也会递归性的委托其父加载器Bootstrap Loader来加载此类,如果Bootstrap Loader在sun.boot.class.path下找到被加载类时即加载,如果无法找到时再依次由子类加载器去加载。委派模型是针对Java安全而设计的,这也印证了Java语言的设计初衷:面向网络的编程语言。
2、由同一个类加载器所加载的类只能引用该加载器和其父加载器所加载的其他类。 

两种方法可以在运行时动态加载.class文件:1) Class clazz = Class.forName("类名称"); 2) 自定义类加载器,然后调用loadClass(“类名称”)方法;
一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 .class 文件,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 new Instance()方法就可以创建出该类的一个对象。

基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

&:类是Class的实例,类加载器是抽象类ClassLoader的实例(除了启动类加载器BootStrap);

java.lang.ClassLoader类的基本功能就是根据一个指定的类名称,找到或者生成其对应的字节码(.class文件),然后从这些字节代码中创建一个 java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。ClassLoader 中与加载类相关的方法:

//加载指定名称(包括包名)的二进制类型,供用户调用的接口    
public Class<?> loadClass(String name) throws ClassNotFoundException{ … }   
   
//加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是这里的resolve参数不一定真正能达到解析的效果),供继承用    
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … }   
  
//findClass方法一般被loadClass方法调用去加载指定名称类,供继承用    
protected Class<?> findClass(String name) throws ClassNotFoundException { … }   
 
//定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承    
//(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了)    
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }  
不同的类加载器为相同名称的类创建了不同的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间

类加载器   加载类的时候,首先会让父类加载器来加载这个类。这就意味着:完成类的加载工作的类加载器和启动这个加载过程的类加载器(&:加载一个类是分为两个步骤完成的),可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,是哪个类加载器启动了的加载过程并不重要,重要的是最终是那个类加载器定义了这个类。
方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。
Java代码  收藏代码
public static void main(String[] args) {   
        try {   
            System.out.println(ClassLoader.getSystemClassLoader());   
            System.out.println(ClassLoader.getSystemClassLoader().getParent());   
           System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());   
        } catch (Exception e) {   
            e.printStackTrace();   
        }   
}   
sun.misc.Launcher$AppClassLoader@6d06d69c   
sun.misc.Launcher$ExtClassLoader@70dea4e   
null   
通过以上的代码输出,我们可以判定系统类加载器的父加载器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时确得到了null,由于jvm对于父类加载器是引导类加载器的情况,getParent()方法返回 null。
我们首先看一下java.lang.ClassLoader抽象类中默认实现的两个构造函数(大楷意思,与源码不太一致):标准扩展类加载器和系统类加载器及其父类(java.net.URLClassLoader和java.security.SecureClassLoader)都没有覆写java.lang.ClassLoader中默认的加载委派规则---loadClass(…)方法。
Java代码  收藏代码
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 findBootstrapClass0(String name)   
                c = findBootstrapClass0(name);   
            }   
        } catch (ClassNotFoundException e) {   
            // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能   
            c = findClass(name);   
        }   
    }   
    if (resolve) {   
        resolveClass(c);   
    }   
    return c;   
}  
类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。

动态加载类:
1.调用class.forName(String);
public static Class<?> forName(String className) 
               throws ClassNotFoundException { 
       return forName0(className, true, ClassLoader.getCallerClassLoader());     //ClassLoader 
   } 
设置为true表示强制加载同时完成初始化。例如典型的就是利用DriverManager进行JDBC驱动程序类注册的问题。因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用。这就要求驱动程序类必须被初始化,而不单单被加载。Class.forName的一个很常见的用法就是在加载数据库驱动的时候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。


2.自定义类加载器
通过前面的分析,除了启动类加载器之外,标准扩展类加载器和系统类加载器都可以当做自定义类加载器,唯一区别是是否被虚拟机默认使用。自定义的类加载器必须符合以下要求:
     继承ClassLoader类;
     覆写findClass方法

Java代码  收藏代码
package org.Smart; 
 
import java.io.ByteArrayOutputStream; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.IOException; 
import java.io.InputStream; 
 
public class FileSystemClassLoader extends ClassLoader {  
    private String rootDir;  
    public FileSystemClassLoader(String rootDir) {  
        this.rootDir = rootDir;  
    }  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        byte[] classData = getClassData(name);  
        if (classData == null) {  
            throw new ClassNotFoundException();  
        }  
        else {  
            return defineClass(name, classData, 0, classData.length);   //ClassLoader里的方法:创建对象 
        }  
    }  
    private byte[] getClassData(String className) {  
        String path = classNameToPath(className);  
        try {  
            InputStream ins = new FileInputStream(path);  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            int bufferSize = 4096;  
            byte[] buffer = new byte[bufferSize];  
            int bytesNumRead = 0;  
            while ((bytesNumRead = ins.read(buffer)) != -1) {  
                baos.write(buffer, 0, bytesNumRead);  
            }  
            return baos.toByteArray();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
    String classNameToPath(String className) { 
        StringBuffer s=new  StringBuffer(rootDir+"/"+className.replace('.', '/')+".class"); 
        return s.toString(); 
    }  

测试: 
public static void main(String...s) throws ClassNotFoundException { 
        @SuppressWarnings("rawtypes") 
        Class app=new FileSystemClassLoader("E:/side1/Smart/target/classes").findClass("org.Smart.App"); 
        System.out.println(app.getClassLoader());          //org.Smart.FileSystemClassLoader@3020ad 
        System.out.println(App.class.getClassLoader());    //sun.misc.Launcher$AppClassLoader@15253d5 
    } 
自定义的类加载器测试成功!!

   

线程上下文类加载器(context class loader)
是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。
     Java默认的线程上下文类加载器是系统类加载器(AppClassLoader)。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。
后续:

http://blog.csdn.net/zhoudaxia/article/details/35897057
  • 大小: 15.9 KB
分享到:
评论

相关推荐

    自定义Java类加载器

    看完一个Java加载原理教程后,写了这个自己的类加载器,作个笔记,以便以且使用

    java类加载器实例

    类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java ...

    java 类加载器 加密

    java 类加密 使用类加载器解密加载类 反射执行main

    java类加载器

    ClassLoader 三种类加载方式 Boostrap Extenxsion 以及Application ClassLoad分别适用的场景

    java类加载器学习三、类加载器的委托模式

    java类加载器学习三、类加载器的委托模式

    Java类加载器.pdf

    Java类加载器.pdf

    java 类加载器 class loader

    java 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loaderjava 类加载器 class loader

    java类装载器学习一、类加载器的基本概念

    类装载器学习一、类加载器的基本概念 类装载器学习一、类加载器的基本概念 类装载器学习一、类加载器的基本概念

    自定义Java类加载器demo

    自定义Java类加载器demo,自定义了一个classLoader,重写了loadClass 和findClass,注意 loadClass打破了双亲委派机制,所有的类都要在自定义的class文件中找到,而findClass遵循了双亲委派机制

    深入探讨 Java 类加载器.wps

    深入探讨 Java 类加载器.wps深入探讨 Java 类加载器.wps深入探讨 Java 类加载器.wps

    Java类加载器原理

    自己根据一些文章总结的,不知道有没有漏洞,希望大家知道,谢谢

    java类加载器和反射学习要点ppt

    本学习讲义是关于java类加载和反射机制需要注意的要点学习,内容详细

    类加载器(java)

    当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构,理解类加载器:J2EE 环境下的 log4j.files

    掌握Java类加载器

    类加载器是Java最强大的特征之一。但是开发者常常忘记类加载组件。类加载器是在运行时负责寻找和加载类文件的类。Java允许使用不同的类加载器,甚至自定义的类加载器。类加载器从源文件(通常是.class 或 .jar文件)...

    ModRunJava类加载器可以直接从Maven存储库加载并运行类

    Java类加载器可以直接从Maven存储库加载并运行类,能在运行时解决依赖关系

    深入探讨 Java 类加载器

    类加载器(class ...本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式、加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。

    java类加载器学习二、自定义类加载器

    java类加载器学习二、自定义类加载器

    java类加载机制.xmind

    该文件是JVM中关于类加载机制的知识整理的思维导图,包括类加载机制概述、类加载的生命周期、加载时机、加载过程、类加载、类的初始化和实例化等几个大方面进行了讲解,其中类加载中还对JVM三种预定义类加载器进行了...

    java应用程序类加载器,ClassLoader for java Application

    java应用程序类加载器(ClassLoader for java Application),类似exe4j, 方便启动java程序, 配置灵活,支持多平台选择性配置

Global site tag (gtag.js) - Google Analytics