热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

jvm(1)类加载(一)(加载过程,双亲加载)

JVM类加载器机制与类加载过程jvm虚拟机的种类:Hotspot(Oracle)(基本上都是在说这个)J9,JikesRVM(IBM)Zulu,Zing(Azul)L

 JVM类加载器机制与类加载过程

jvm虚拟机的种类:

Hotspot(Oracle)(基本上都是在说这个)
J9, JikesRVM(IBM)
Zulu, Zing (Azul)

Launcher是一直用于启动JVM进程的启动器,有两种:

一种windows平台下运行时会保留在控制台 
一种用于执行Java的GUI程序,不会显示任何程序的输出信息

Launcher只是一个封装了虚拟机的执行外壳,由它负责装载JRE环境和windows平台下的jvm.dll动态链接库

 

补充:

HotSpot虚拟机对象的创建、内存布局、访问定位

PerformanceCounter 表示 Windows NT 性能计数器组件 

一:跨平台:

Java语言之所以说它是跨平台的、因为Java语言的运行环境是在Java虚拟机中。

 Java虚拟机对各个平台而言,实质上是各个平台上的一个可执行程序。java虚拟机对于windows而言,就是一个java.exe进程而已。

二:Java虚拟机启动、加载类过程分析

package org.luanlouis.jvm.load;  
import sun.security.pkcs11.P11Util;  
public class Main{  
    public static void main(String[] args) {  
        System.out.println("Hello,World!");  
        ClassLoader loader = P11Util.class.getClassLoader();    
        System.out.println(loader);  
    }  
}  

执行java    org.luanlouis.jvm.load.Main

windows开始运行{JRE_HOME}/bin/java.exe程序,java.exe 程序将完成以下步骤:

1. 给jvm分配内存空间: 根据JVM内存配置要求,为JVM申请特定大小的内存空间;
2. 类加载器创建和加载系统类到方法区:创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;
3. 启动器创建和获取类加载器:创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader;
4. 类加载器加载自定义类:使用上述获取的ClassLoader实例加载我们定义的 org.luanlouis.jvm.load.Main类;
5. jvm执行main方法:加载完成时候JVM会执行Main类的main方法入口,执行Main类的main方法;
6. 结束,java程序运行结束,JVM销毁。

1,根据JVM内存配置要求,为JVM申请特定大小的内存空间

2,创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;

JVM申请好内存空间后,JVM会创建一个引导类加载器(Bootstrap Classloader)实例,加载系统类:

引导类加载器是使用C++语言实现的,负责加载JVM虚拟机运行时所需的基本系统级别的类,如java.lang.String, java.lang.Object等等。

引导类加载器(Bootstrap Classloader)会读取 {JRE_HOME}/lib 下的jar包和配置,然后将这些系统类加载到方法区内。

也可以使用参数 -Xbootclasspath 或 系统变量sun.boot.class.path来指定的目录来加载类。

一般而言,{JRE_HOME}/lib下存放着JVM正常工作所需要的系统类,如下表所示:

文件名                 描述
rt.jar            运行环境包,rt即runtime,J2SE 的类定义都在这个包内
charsets.jar      字符集支持包
jce.jar           是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现
jsse.jar          安全套接字拓展包Java(TM) Secure Socket Extension
classlist         该文件内表示是引导类加载器应该加载的类的清单
net.properties    JVM 网络配置信息 

引导类加载器(Bootstrap ClassLoader) 加载系统类后,JVM内存会呈现如下格局:

1,引导类加载器将类信息加载到方法区中,以特定方式组织,对于某一个特定的类而言,
在方法区中它应该有 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用,对应class实例的引用等信息。 2,类加载器的引用,由于这些类是由引导类加载器(Bootstrap Classloader)进行加载的,而引导类加载器是有C
++语言实现的,所以是无法访问的,故而该引用为NULL 3,对应class实例的引用, 类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

补充:

1,也就是说程序员使用的类的数据都是(通过类加载器创建的每个类的class)Class实例来获取的。(方法区和堆的交互是加载类完成的)

2,当我们在代码中尝试获取系统类如java.lang.Object的类加载器时,你会始终得到NULL:

System.out.println(String.class.getClassLoader());//null  
System.out.println(Object.class.getClassLoader());//null  
System.out.println(Math.class.getClassLoader());//null  
System.out.println(System.class.getClassLoader());//null  

3,创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader

上述步骤完成,JVM基本运行环境就准备就绪了。要让JVM工作起来:运行我们定义的程序 org.luanlouis,jvm.load.Main。

需要JVM虚拟机调用已经加载在方法区的类sun.misc.Launcher 的静态方法getLauncher(),  获取sun.misc.Launcher 实例:

sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher(); //获取Java启动器  
ClassLoader classLoader = launcher.getClassLoader();          //获取类加载器ClassLoader用来加载class到内存来  

启动器做了什么:

创建扩展类加载器和应用类加载器,并将应用加载器指定给当前线程作为线程上下文加载器。
 public Launcher() {  
      Launcher.ExtClassLoader var1;  
      try {  
          var1 = Launcher.ExtClassLoader.getExtClassLoader();  
      } catch (IOException var10) {  
          throw new InternalError("Could not create extension class loader", var10);  
      }  
  
      try {  
          this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);  
      } catch (IOException var9) {  
          throw new InternalError("Could not create application class loader", var9);  
      }  
//将AppClassLoader设置成当前线程的上下文加载器  
      Thread.currentThread().setContextClassLoader(this.loader);  
      //.......  
}  

sun.misc.Launcher 使用了单例模式设计,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。

在Launcher的内部,其定义了两个类加载器(ClassLoader),分别是sun.misc.Launcher.ExtClassLoader和sun.misc.Launcher.AppClassLoader,

这两个类加载器分别被称为拓展类加载器(Extension ClassLoader) 和 应用类加载器(Application ClassLoader).如下图所示:

 图上的指向引导类加载器的虚线表示:

除了引导类加载器(Bootstrap Class Loader )外的所有类加载器,都有一个能力,就是判断某一个类是否被引导类加载器加载过,
如果加载过,可以直接返回对应的Class instance,如果没有,则返回null. 

 

从Launcher源码中可以看到

public class Launcher {  
    private static URLStreamHandlerFactory factory = new Factory();  
    private static Launcher launcher = new Launcher();  
  
    public static Launcher getLauncher() {  
        return launcher;  
    }  
  
    private ClassLoader loader;  
      
    //ClassLoader.getSystemClassLoader会调用此方法  
    public ClassLoader getClassLoader() {  
        return loader;  
    }  
  
    public Launcher() {  
        // 1. 创建ExtClassLoader   
        ClassLoader extcl;  
        try {  
            extcl = ExtClassLoader.getExtClassLoader();  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create extension class loader");  
        }  
  
        // 2. 用ExtClassLoader作为parent去创建AppClassLoader   
        try {  
            loader = AppClassLoader.getAppClassLoader(extcl);  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create application class loader");  
        }  
  
        // 3. 设置AppClassLoader为ContextClassLoader  
        Thread.currentThread().setContextClassLoader(loader);  
        //...  
    }  
  
    static class ExtClassLoader extends URLClassLoader {  
        private File[] dirs;  
  
        public static ExtClassLoader getExtClassLoader() throws IOException  
        {  
            final File[] dirs = getExtDirs();  
            return new ExtClassLoader(dirs);  
        }  
  
        public ExtClassLoader(File[] dirs) throws IOException {  
            super(getExtURLs(dirs), null, factory);  
            this.dirs = dirs;  
        }  
  
        private static File[] getExtDirs() {  
            String s = System.getProperty("java.ext.dirs");  
            File[] dirs;  
            //...  
            return dirs;  
        }  
    }  
  
    /** 
     * The class loader used for loading from java.class.path. 
     * runs in a restricted security context. 
     */  
    static class AppClassLoader extends URLClassLoader {  
  
        public static ClassLoader getAppClassLoader(final ClassLoader extcl)  
            throws IOException  
        {  
            final String s = System.getProperty("java.class.path");  
            final File[] path = (s == null) ? new File[0] : getClassPath(s);  
  
            URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);  
            return new AppClassLoader(urls, extcl);  
        }  
  
        AppClassLoader(URL[] urls, ClassLoader parent) {  
            super(urls, parent, factory);  
        }  
          
        /** 
         * Override loadClass so we can checkPackageAccess. 
         * 这个方法似乎没什么必要,因为super.loadClass(name, resolve)时也会checkPackageAccess 
         */  
        public synchronized Class loadClass(String name, boolean resolve)  
            throws ClassNotFoundException  
        {  
            int i = name.lastIndexOf('.');  
            if (i != -1) {  
                SecurityManager sm = System.getSecurityManager();  
                if (sm != null) {  
                    //  
                    sm.checkPackageAccess(name.substring(0, i));  
                }  
            }  
            return (super.loadClass(name, resolve));  
        }  
  
    }  
}  
View Code

launcher.getClassLoader() 方法将会返回 AppClassLoader 实例(AppClassLoader将ExtClassLoader作为自己的父加载器)。
当AppClassLoader加载类时:

1,判断自己是否已经加载,
2,会首先尝试让父加载器ExtClassLoader进行加载,如果父加载器ExtClassLoader加载成功,则AppClassLoader直接返回父加载器ExtClassLoader加载的结果;
3,如果父加载器ExtClassLoader加载失败,AppClassLoader则会判断该类是否是引导的系统类(即是否是通过Bootstrap类加载器加载,这会调用Native方法进行查找);
4,若要加载的类不是系统引导类,那么ClassLoader将会尝试自己加载,加载失败将会抛出“ClassNotFoundException”。

再这里就说点一个重要的概念:双亲委派模型(parent-delegation model)

对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:

1. 委托父类加载器帮忙加载;
2. 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
3. 如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
4. 若加载成功,返回 对应的Class 对象;若失败,抛出异常“ClassNotFoundException”。

请注意:

双亲委派模型中的"双亲"并不是指它有两个父类加载器的意思,一个类加载器只应该有一个父加载器。

上面的步骤中,有两个角色:

1. 父类加载器(parent classloader):它可以替子加载器尝试加载类
2. 引导类加载器(bootstrap classloader): 子类加载器只能判断某个类是否被引导类加载器加载过,而不能委托它加载某个类;

4. 使用类加载器ClassLoader加载Main类

通过 launcher.getClassLoader()方法返回AppClassLoader实例,接着就是AppClassLoader加载 org.luanlouis.jvm.load.Main类

ClassLoader classloader = launcher.getClassLoader();//取得AppClassLoader类  
classLoader.loadClass("org.luanlouis.jvm.load.Main");//加载自定义类  

上述定义的org.luanlouis.jvm.load.Main类被编译成org.luanlouis.jvm.load.Main class二进制文件,

这个class文件中有一个叫常量池(Constant Pool)的结构体来存储该class的常亮信息。

常量池中有CONSTANT_CLASS_INFO类型的常量,表示该class中声明了要用到那些类:

当AppClassLoader要加载 org.luanlouis.jvm.load.Main类时,会去查看该类的定义,
发现它内部声明使用了其它的类:
sun.security.pkcs11.P11Util、
java.lang.Object、
java.lang.System、
java.io.PrintStream、
java.lang.Class;
org.luanlouis.jvm.load.Main类要想正常工作,首先要能够保证这些其内部声明的类加载成功。
所以AppClassLoader要先将这些类加载到内存中。
(注:为了理解方便,这里没有考虑懒加载的情况,事实上的JVM加载类过程比这复杂的多)

加载顺序:

1. 加载java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class
   AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;
而ExtClassLoader发现不是其加载范围,其返回null;
AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过,
结果表明这些类已经被BootstrapClassLoader加载过,则无需重复加载,直接返回对应的Class
实例; 2. 加载sun.security.pkcs11.P11Util 此在{JRE_HOME}/lib/ext/sunpkcs11.jar包内,属于ExtClassLoader负责加载的范畴。
AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;
而ExtClassLoader发现其正好属于加载范围,故ExtClassLoader负责将其加载到内存中。
ExtClassLoader在加载sun.security.pkcs11.P11Util时也分析这个类内都使用了哪些类,
并将这些类先加载内存后,才开始加载sun.security.pkcs11.P11Util,
加载成功后直接返回对应的Class实例; 3. 加载org.luanlouis.jvm.load.Main AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;
AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过。
而结果表明BootstrapClassLoader 没有加载过它,这时候AppClassLoader只能自己动手负责将其加载到内存中,
然后返回对应的Class
实例引用;

 以上三步骤都成功,才表示classLoader.loadClass("org.luanlouis.jvm.load.Main")完成,上述操作完成后,JVM内存方法区的格局会如下所示:

JVM方法区的类信息区是按照类加载器进行划分的,每个类加载器会维护自己加载类信息;
某个类加载器在加载相应的类时,会相应地在JVM内存堆(Heap)中创建一个对应的Class,用来表示访问该类信息的入口

5. 使用Main类的main方法作为程序入口运行程序

6. 方法执行完毕,JVM销毁,释放内存

三:类加载器有哪些?其组织结构是怎样的?

 JVM自身定义了三个类加载器:

引导类加载器(Bootstrap Class Loader)、拓展类加载器(Extension Class Loader )、应用加载器(Application Class Loader又叫系统类加载器)。

当然,我们有时候也会自己定义一些类加载器来满足自身的需要。

1,引导类加载器(Bootstrap Class Loader): 该类加载器使JVM使用C/C++底层代码实现的加载器,用以加载JVM运行时所需要的系统类,
这些系统类在{JRE_HOME}/lib目录下(-Xbootclasspath选项指定)的jar包加载到内存中。
由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用。
但是,我们可以查询某个类是否被引导类加载器加载过。

我们经常使用的系统类如:java.lang.String,java.lang.Object,java.lang*.......
这些都被放在 {JRE_HOME}/lib/rt.jar包内,
当JVM系统启动的时候,引导类加载器会将其加载到 JVM内存的方法区中。
2,拓展类加载器(Extension Class Loader): 该加载器是用于加载 java 的拓展类 ,
拓展类一般会放在 {JRE_HOME}
/lib/ext/ 目录下(-Djava.ext.dir指定位置)的类库加载到内存中,用来提供除了系统类之外的额外功能。
拓展类加载器是是整个JVM加载器的Java代码可以访问到的类加载器的最顶端(即是超级父加载器,拓展类加载器是没有父类加载器的)。
3,应用类加载器(Applocatoin Class Loader): 该类加载器是用于加载用户代码,是用户代码的入口。
负责将系统类路径java -classpath,即环境变量classpath(或-Djava.class.path指定的目录)的类库加载到内存中

我经常执行指令 java xxx.x.x.XClass , 实际上,JVM就是使用的AppClassLoader加载 xxx.x.x.XClass 类的。

应用类加载器将拓展类加载器当成自己的父类加载器,当其尝试加载类的时候,首先尝试让其父加载器(
拓展类加载器加载);
如果拓展类加载器加载成功,则直接返回加载结果Class instance,
加载失败,则会询问是否引导类加载器已经加载了该类;
只有没有加载的时候,应用类加载器才会尝试自己加载。

4,用户自定义类加载器(Customized Class Loader):用户可以自己定义类加载器来加载类。所有的类加载器都要继承java.lang.ClassLoader类。

由于Class是整个用户代码的入口,在Java虚拟机规范中,称其为 初始类(Initial Class).

 

 

 

四:双亲加载模型的逻辑和底层代码实现是怎样的?

通过JDK源码看java.lang.ClassLoader的核心方法 loadClass()的实现:

//提供class类的二进制名称表示,加载对应class,加载成功,则返回表示该类对应的Class instance 实例  
public Class loadClass(String name) throws ClassNotFoundException {  
    return loadClass(name, false);  
}  
  
  
protected Class loadClass(String name, boolean resolve)  
    throws ClassNotFoundException  
{  
    synchronized (getClassLoadingLock(name)) {  
        // 首先,检查是否已经被当前的类加载器记载过了,如果已经被加载,直接返回对应的Class实例  
        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;  
    }  
}  

完全的ClassLoader源码:

public abstract class ClassLoader {  
  
    // 父ClassLoader  
    private ClassLoader parent;  
  
    // 被此classLoader加载过的Class对象  
    private Vector classes = new Vector();  
      
    // The packages defined in this class loader.  Each package name is mapped  
    // to its corresponding Package object.  
    private HashMap packages = new HashMap();  
  
    // 由虚拟机调用  
    void addClass(Class c) {  
        classes.addElement(c);  
    }  
  
    // The packages defined in this class loader. Each package name is mapped  
    // to its corresponding Package object.  
    private final HashMap packages = new HashMap();  
  
    // 指明parent  
    protected ClassLoader(ClassLoader parent) {  
        this(checkCreateClassLoader(), parent);  
    }  
  
    // 不指名parent时使用SystemClassLoader  
    protected ClassLoader() {  
        this(checkCreateClassLoader(), getSystemClassLoader());  
    }  
  
    // 默认resolve=false  
    public Class loadClass(String name) throws ClassNotFoundException {  
        return loadClass(name, false);  
    }  
  
    protected synchronized Class loadClass(String name, boolean resolve)  
            throws ClassNotFoundException {  
        // 1 检查此class是否被此classloader加载过,  
        // 最终是有native方法返回,native方法会使用到classes集合  
        Class c = findLoadedClass(name);  
        // 1.1 未被加载  
        if (c == null) {  
            try {  
                // 1.1.1 此classloader有parent,委托parent去load  
                if (parent != null) {  
                    c = parent.loadClass(name, false);  
                } else {// 1.1.2 此classloader无parent = 启动类装载器去加载  
                    c = findBootstrapClassOrNull(name);  
                }  
            } catch (ClassNotFoundException e) {  
            }  
            // 如果没有找到class,自己去加载试试  
            if (c == null) {  
                c = findClass(name);  
            }  
        }  
        // 找到的Class对象是否需要连接操作  
        if (resolve) {  
            resolveClass(c);  
        }  
        // 1.2 被加载过,直接返回  
        return c;  
    }  
  
    protected final Class findLoadedClass(String name) {  
        if (!checkName(name))  
            return null;  
        return findLoadedClass0(name);  
    }  
  
    // 如果name里包含/,或者虚拟机不支持class数组你使用了数组的时候返回false,其它情况返回true,包括空的name  
    // eg com.jyz.component.core.collection.Tuple return true  
    private boolean checkName(String name) {  
        if ((name == null) || (name.length() == 0))  
            return true;  
        if ((name.indexOf('/') != -1)  
                || (!VM.allowArraySyntax() && (name.charAt(0) == '[')))  
            return false;  
        return true;  
    }  
  
    // 检查package是否可访问  
    private void checkPackageAccess(Class cls, ProtectionDomain pd) {  
        final SecurityManager sm = System.getSecurityManager();  
        if (sm != null) {  
            // ...  
        }  
        domains.add(pd);  
    }  
  
    // 自定义classloader时重写此方法  
    protected Class findClass(String name) throws ClassNotFoundException {  
        throw new ClassNotFoundException(name);  
    }  
  
    // 将byte数组转换成一个Class对象,最终有native方法实现  
    // 同一个byte数组,被不同的classloader加载,产生两个不同的Class对象  
    protected final Class defineClass(String name, byte[] b, int off, int len)  
            throws ClassFormatError {  
        return defineClass(name, b, off, len, null);  
    }  
  
    /* 
     * Determine protection domain, and check that: - not define java.* class, - 
     * signer of this class matches signers for the rest of the classes in 
     * package. 
     */  
    //native的defineClass时会调用此方法检查name是否合法  
    //首先checkName,然后还需要!name.startsWith("java.")  
    //所以我们定义了java.mypackage包,都将异常  
    //java.lang.SecurityException: Prohibited package name: java.mypackage  
    private ProtectionDomain preDefineClass(String name,  
            ProtectionDomain protectionDomain) {  
        if (!checkName(name))  
            throw new NoClassDefFoundError("IllegalName: " + name);  
        if ((name != null) && name.startsWith("java.")) {  
            throw new SecurityException("Prohibited package name: "  
                    + name.substring(0, name.lastIndexOf('.')));  
        }  
        //...  
    }  
  
    // protected的resolveClass方法,可以在自定义的classloader调用  
    protected final void resolveClass(Class c) {  
        resolveClass0(c);  
    }  
      
    //获得appClassLoader,实际调用Launcher完成  
    public static ClassLoader getSystemClassLoader() {  
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();  
        return l.getClassLoader();  
    }  
  
} 
View Code

五:类加载器与Class  实例的关系

六:线程上下文加载器

我们平时并没有使用加载器,是因为jvm帮我调用了。(会将加载器关联到线程上)

 Java 任何一段代码的执行,都有对应的线程上下文。如果我们在代码中,想看当前是哪一个线程在执行当前代码,我们经常是使用如下方法:

Thread  thread = Thread.currentThread();//返回对当当前运行线程的引用  

相应地,我们可以为当前的线程指定类加载器。

在上述的例子中, 当执行   java    org.luanlouis.jvm.load.Main  的时候,JVM会创建一个Main线程,而创建应用类加载器AppClassLoader的时候,

会将AppClassLoader  设置成Main线程的上下文类加载器:

 public Launcher() {  
      Launcher.ExtClassLoader var1;  
      try {  
          var1 = Launcher.ExtClassLoader.getExtClassLoader();  
      } catch (IOException var10) {  
          throw new InternalError("Could not create extension class loader", var10);  
      }  
  
      try {  
          this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);  
      } catch (IOException var9) {  
          throw new InternalError("Could not create application class loader", var9);  
      }  
//将AppClassLoader设置成当前线程的上下文加载器  
      Thread.currentThread().setContextClassLoader(this.loader);  
      //.......  
  }  

线程上下文类加载器是从线程的角度来看待类的加载,为每一个线程绑定一个类加载器,可以将类的加载从单纯的 双亲加载模型解放出来,进而实现特定的加载需求。

线程上下文加载器的意义何在???

 

七:扩展加载器和应用加载器的继承结构:

特别注意的是:虽然都是URLClassLoader的子类,但是都是Launcher类的静态内部类,jdk的api是没有的。

都是sun/misc/Launcher.class的静态成员内部类,声明如下:
static class AppClassLoader extends URLClassLoader
static class ExtClassLoader extends URLClassLoader

 

八:类加载器加载路径的测试 

Launcher类的部分源码(比较老的一个版本),主要是引导类加载器加载的路径

private static Launcher launcher = new Launcher();  
private static String bootClassPath =  System.getProperty("sun.boot.class.path");  
  
    public static Launcher getLauncher() {  
        return launcher;  
    }  
  
    private ClassLoader loader;  
  
    public Launcher() {  
        // Create the extension class loader  
        ClassLoader extcl;  
        try {  
            extcl = ExtClassLoader.getExtClassLoader();  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create extension class loader");  
        }  
  
        // Now create the class loader to use to launch the application  
        try {  
            loader = AppClassLoader.getAppClassLoader(extcl);  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create application class loader");  
        }  
  
        // Also set the context class loader for the primordial thread.  
        Thread.currentThread().setContextClassLoader(loader);  
  
        // Finally, install a security manager if requested  
        String s = System.getProperty("java.security.manager");  
        ......  
    }  
  
    /* 
     * Returns the class loader used to launch the main application. 
     */  
    public ClassLoader getClassLoader() {  
        return loader;  
    }  
View Code

测试:

public static void main(String[] args) throws Exception {  
          
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();  
        ClassLoader extClassloader = appClassLoader.getParent();  
        ClassLoader bootstrapLoader  = extClassloader.getParent();  
        System.out.println("the bootstrapLoader : " + bootstrapLoader);  
        System.out.println("the extClassloader : " + extClassloader);  
        System.out.println("the appClassLoader : " + appClassLoader);  
          
        System.out.println();  
        System.out.println("bootstrapLoader加载以下文件:");  
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();  
        for (int i = 0; i ) {  
            System.out.println(urls[i]);  
        }  
          
        System.out.println();  
        System.out.println("extClassloader加载以下文件:");  
        System.out.println(System.getProperty("java.ext.dirs"));  
          
        System.out.println();  
        System.out.println("appClassLoader加载以下文件:");  
        System.out.println(System.getProperty("java.class.path"));  
          
    }  
}  

 eclipse下测试:

dos下测试:

总结:

可以发现应用类加载器加载就是环境变量classpath设置的。
(至于eclipse中不同,是因为eclipse临时修改了自己的classpath。)

九:测试加载器加载类的路径是被限制的:

默认引导类加载器只会加载{jre.lib}下的,并且还做了限制只会加载系统类。
默认扩展类加载器只会加载{jre.ext.lib}下的。
默认系统类加载器(应用类加载器)只会加载classpath下的。

1,在同一目录中建立2个类:

需要加载的类:
public class TestBean {        
    public TestBean() { }  
}  
测试类:
public class ClassLoaderTest { public static void main(String[] args) { try { //调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean Class typeLoaded = Class.forName("TestBean"); //查看被加载的TestBean类型是被那个类加载器加载的 System.out.println(typeLoaded.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }

输出:(因为eclipse默认classpath是当前工程下)补充:eclipse的classpath(build path)和classpaht几种设置的方式

sun.misc.Launcher$AppClassLoader@73d16e93  

2,将TestBean.class复制打包进test.jar剪贴到/lib/ext目录下(现在工程目录下和JRE扩展目录下都有待加载类型的class文件)。

sun.misc.Launcher$ExtClassLoader@15db9742  

对比测试一和测试二,可以验证前面说的双亲委派机制:

系统类加载器在接到加载TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器),标准扩展类加载器抢先完成了加载请求。

 

3,将test.jar拷贝一份到/lib下,运行测试代码:

sun.misc.Launcher$ExtClassLoader@15db9742  

放置到/lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双亲委派机制并不矛盾。

虚拟机出于安全等因素考虑,不会加载/lib存在的陌生类,不会加载非JDK自身的类。

如果将工程和jre/lib/ext下的TestBean移除,只保留jre/lib下的,会发现findBootstrapClass0()会抛出异常,找不到类。

 

 


推荐阅读
author-avatar
遗留下的痛cc-x_393
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有