• Java类加载器


    1.类加载器介绍

    类加载器负责将class文件加载到内存中,并为之生成对应的java.lang.Class对象。对于任意一个类,都需要加载它的类加载器和这个类本身来确定该类在JVM中唯一性,也就是说,同一个class文件用两个不同的类加载器加载并创建两个java.lang.Class对象,即使两个对象来源自同一个class文件,它们也是不相等的,这里“相等”包括Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法,也包括使用instanceof关键字做对象所属关系判定情况。

    验证代码如下:

    [java] view plain copy
     
    1. public class ClassLoaderTest {  
    2.     public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {  
    3.         // 自定义类加载器  
    4.         ClassLoader myLoader = new ClassLoader() {  
    5.             @Override  
    6.             public Class<?> loadClass(String name) throws ClassNotFoundException {  
    7.                 String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";  
    8.                 InputStream is = getClass().getResourceAsStream(fileName);  
    9.                 if (is == null) {  
    10.                     return super.loadClass(name);  
    11.                 }  
    12.                 try {  
    13.                     byte[] buff = new byte[is.available()];  
    14.                     is.read(buff);  
    15.                     return defineClass(name, buff, 0, buff.length);  
    16.                 } catch (IOException e) {  
    17.                     throw new ClassNotFoundException(name);  
    18.                 }  
    19.             }  
    20.         };  
    21.         // 使用自定义的类加载加载classLoaderTest.ClassLoaderTest类  
    22.         Class clazz = myLoader.loadClass("classLoaderTest.ClassLoaderTest");  
    23.         Object obj = clazz.newInstance();  
    24.         /* 
    25.          创建obj对象的类实例是由自定义类加载器加载的, 
    26.          classLoaderTest.ClassLoaderTest使用的类加载器是系统类加载器,所属关系并不一致 
    27.          */  
    28.         System.out.println(obj instanceof classLoaderTest.ClassLoaderTest);  
    29.         /* 
    30.          clazz类实例是由自定义类加载器加载的, 
    31.          classLoaderTest.ClassLoaderTest.class使用的类加载器是系统类加载器 
    32.          */  
    33.         System.out.println(clazz.getClassLoader());  
    34.         System.out.println(classLoaderTest.ClassLoaderTest.class.getClassLoader());  
    35.         System.out.println(clazz.equals(classLoaderTest.ClassLoaderTest.class));  
    36.     }  
    37. }  

    输出结果:

    [java] view plain copy
     
    1. false  
    2. classLoaderTest.ClassLoaderTest$1@537e7f88  
    3. sun.misc.Launcher$AppClassLoader@7d05e560  
    4. false  

    2.类加载器分类

    从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器是虚拟机的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机,并且全部继承自java.lang.ClassLoader。细分来看,类加载器还可以分为如下几类:

    • Bootstrap ClassLoader:根类加载器
    • Extension ClassLoader:扩展类加载器
    • System ClassLoader:应用程序类加载器
    • 用户自定义类加载器

    2.1 Bootstrap ClassLoader

    Bootstrap ClassLoader被称为根类加载器,它负责加载Java的核心类。根类加载器并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。在Sun的JVM中,当执行java.exe命令时,使用-Xbootclasspath选择或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。

    通过如下程序查看根类加载器加载的类的路径:

    [java] view plain copy
     
    1. public class LoaderTest {  
    2.     public static void main(String[] args) throws IOException {  
    3.         // 获取根类加载器所加载的全部URL数组  
    4.         URL[] urls = Launcher.getBootstrapClassPath().getURLs();  
    5.         // 遍历、输出根类加载器加载的全部URL  
    6.         System.out.println("*********根类加载器加载的全部URL*************");  
    7.         for (URL url : urls) {  
    8.             System.out.println(url.toExternalForm());  
    9.         }  
    10.     }  
    11. }  


    输出结果:

    [java] view plain copy
     
    1. *********根类加载器加载的全部URL*************  
    2. file:/D:/Java/jdk1.7.0_80/jre/lib/resources.jar  
    3. file:/D:/Java/jdk1.7.0_80/jre/lib/rt.jar  
    4. file:/D:/Java/jdk1.7.0_80/jre/lib/sunrsasign.jar  
    5. file:/D:/Java/jdk1.7.0_80/jre/lib/jsse.jar  
    6. file:/D:/Java/jdk1.7.0_80/jre/lib/jce.jar  
    7. file:/D:/Java/jdk1.7.0_80/jre/lib/charsets.jar  
    8. file:/D:/Java/jdk1.7.0_80/jre/lib/jfr.jar  
    9. file:/D:/Java/jdk1.7.0_80/jre/classes  

    2.2 Extension ClassLoader

    Extension ClassLoader是扩展类加载器,它负责加载JRE的扩展目录中JAR包的类,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

    2.3 Application ClassLoader

    Application ClassLoader是应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,可以通过ClassLoader.getSystemClassLoader()方法获取,因此也被称为系统类加载器。它负责加载用户类路径(ClassPath)上指定的类库,开发者可以直接使用这个类加载器,如果应用程序没有自定义类加载器,一般情况下这个就是程序中默认的类加载器。

    2.4 自定义类加载器器

    为了实现类加载的个性化定制,我们可以通过扩展java.lang.ClassLoader类来实现自定义类加载器,详细的实现随后描述。

    我们的应用程序一般都是由这几种类加载器相互配合进行加载的,类加载器之间的关系如图:

    类加载器之间的关系并非是类继承性质的父子关系,而是一种组合关系。

    3.类加载器加载类的步骤

    类加载器在加载一个类时会先把加载的请求委托给父加载器加载,如果父类加载器仍有父加载器(只有根加载器没有父加载器),则再次委托给其父加载器,最后所有的加载请求都会传送给根类加载器,只有当父类加载器中无法完成对委托的类的加载时,该类才会被子加载器加载。这样的加载方式显著的好处就是:在一个运行的JVM中,所有的类都不会被不同的类加载器,某个类在各个加载器环境中看到的都是同一个Class对象。
    另外,当一个类加载器负责加载某个Class时,该Class所引用的其他Class也会被该类加载器载入,除非显式使用另外一个类加载器加载。
    JVM的类加载有一种缓存机制,如果一个类已经被加载过,那么在程序中再次用到这个类时,会直接从缓存中获取该类,只有在缓存中不存在该类时,类加载器才会读取该类的二进制数据并创建该类的Class对象。这也很好地解释了为什么修改了Java代码之后需要重启JVM修改才能生效。不过,现在已经有技术能够做到修改Java代码后不重启JVM仍可生效。

    4.创建自定义的类加载器

    在日常的程序开发中,出于某种需要,我们可能要自定义一些类加载器。通常推荐扩展ClassLoader类并重写findClass方法来实现自定义类加载器。
    接下来我们自定义实现一个可以直接加载源代码的加载类,实现的原理很简单,把对源码的编译放在类加载器中执行:

    [java] view plain copy
     
    1. import java.io.File;  
    2. import java.io.FileInputStream;  
    3. import java.io.IOException;  
    4. import java.lang.reflect.Method;  
    5.   
    6. /** 
    7.  * author:xszhaobo 
    8.  * <p/> 
    9.  * date:2016/5/7 
    10.  * <p/> 
    11.  * package_name:JVMTest.myclassloader 
    12.  * <p/> 
    13.  * project: _darksiderg 
    14.  */  
    15. public class CompileClassLoader extends ClassLoader {  
    16.   
    17.     public static void main(String[] args) throws Exception {  
    18.         if (args.length < 1) {  
    19.             System.out.println("缺少目标类:");  
    20.             System.out.println("java CompileClassLoader ClassName");  
    21.         }  
    22.         // 第一个参数指定需要运行的类名  
    23.         String progClass = args[0];  
    24.         // 其他的一些参数  
    25.         String[] progArgs = new String[args.length - 1];  
    26.         System.arraycopy(args,1,progArgs,0,progArgs.length);  
    27.         // 指定类加载器  
    28.         CompileClassLoader loader = new CompileClassLoader();  
    29.         Class clazz = loader.loadClass(progClass);  
    30.         // main指方法名称   
    31.         // new String[0]).getClass()表示main的形参类型对应Class  
    32.         Method main = clazz.getMethod("main",(new String[0]).getClass());  
    33.         Object[] argsArray = {progArgs};  
    34.         // 第一个参数表示需要调用的方法所属的对象,如果调用静态方法,那么可以设置为null  
    35.         // 第二个以及以后的参数表示该方法传入的参数  
    36.         main.invoke(null,argsArray);  
    37.      }  
    38.   
    39.     @Override  
    40.     protected Class<?> findClass(String name) throws ClassNotFoundException {  
    41.         System.out.println("加载类:" + name + ".java");  
    42.         Class clazz = null;  
    43.         String fileStub = name.replaceAll("\.", "/");  
    44.         String javaFileName = fileStub + ".java";  
    45.         String classFileName = fileStub + ".class";  
    46.         File javaFile = new File(javaFileName);  
    47.         File classFile = new File(classFileName);  
    48.         // java源文件存在而class文件不存在,或者java源文件修改时间晚于class文件  
    49.         if (javaFile.exists() && (!classFile.exists()  
    50.                 || javaFile.lastModified() > classFile.lastModified())) {  
    51.             try {  
    52.                 if (!compile(javaFileName) || !classFile.exists()) {  
    53.                     throw new ClassNotFoundException("ClassNotFoundException:" + javaFileName);  
    54.                 }  
    55.             } catch (IOException e) {  
    56.                 e.printStackTrace();  
    57.             }  
    58.   
    59.             if (classFile.exists()) {  
    60.                 try {  
    61.                     byte[] raw = getBytes(classFileName);  
    62.                     clazz = this.defineClass(name,raw,0,raw.length);  
    63.                 } catch (IOException e) {  
    64.                     e.printStackTrace();  
    65.                 }  
    66.             }  
    67.         }  
    68.         if (clazz == null) {  
    69.             throw new ClassNotFoundException(name);  
    70.         }  
    71.         return clazz;  
    72.     }  
    73.   
    74.     private boolean compile(String javaFile) throws IOException {  
    75.         System.out.println("CompileClassLoader编译" + javaFile + "...");  
    76.         Process p = Runtime.getRuntime().exec("javac -encoding utf-8 " + javaFile);  
    77.         try {  
    78.             p.waitFor();  
    79.         } catch (InterruptedException e) {  
    80.             e.printStackTrace();  
    81.         }  
    82.         int ret = p.exitValue();  
    83.         return ret == 0;  
    84.     }  
    85.   
    86.     private byte[] getBytes(String fileName) throws IOException {  
    87.         File file = new File(fileName);  
    88.         long length = file.length();  
    89.         byte[] bytes = new byte[(int) length];  
    90.         // 使用Java 7的特性:具有资源管理的try语句  
    91.         try (FileInputStream fis = new FileInputStream(file)) {  
    92.             int readLen = fis.read(bytes);  
    93.             if (readLen != length) {  
    94.                 throw new IOException("无法读文件的全部内容:" + fileName);  
    95.             }  
    96.             return bytes;  
    97.         }  
    98.   
    99.         // Java 7之前的版本使用这种资源管理方式  
    100.         /*try { 
    101.             int readLen = fis.read(bytes); 
    102.             if (readLen != length) { 
    103.                 throw new IOException("无法读文件的全部内容:" + fileName); 
    104.             } 
    105.             return bytes; 
    106.         } finally { 
    107.             fis.close(); 
    108.         }*/  
    109.     }  
    110. }  


    编译该类:
    javac -encoding utf-8 -Xlint:unchecked CompileClassLoader.java

    指令的含义
    javac是编译命令;
    -encoding utf-8指定编码是utf-8;
    -Xlint:unchecked指由于CompileClassLoader.java使用了未经检查或不安全的操作,需要指定-Xlint:unchecked编译;
    CompileClassLoader.java指需要编译的文件。

    在相同的文件夹下面随便写一个测试主类:

    [java] view plain copy
     
    1. /** 
    2.  * author:xszhaobo 
    3.  * <p/> 
    4.  * date:2016/5/9 
    5.  * <p/> 
    6.  * package_name:JVMTest.myclassloader 
    7.  * <p/> 
    8.  * project: _darksiderg 
    9.  */  
    10. public class HelloTest {  
    11.     public static void main(String[] args) {  
    12.             System.out.println("参数 :" + args[0]);  
    13.     }  
    14. }  


    此时在命令行中使用CompileClassLoader作为类加载器直接使用java命令运行源代码:
    java CompileClassLoader HelloTest 这是一个参数
    首次加载时输出结果:

    [java] view plain copy
     
    1. CompileClassLoader编译HelloTest.java...  
    2. 参数:这是一个参数  

    可以看出这是此时先编译了HelloTest.java文件,然后运行该类中的main方法。
    但是这里有一个问题,如果此时修改HelloTest.java源代码,重新执行如下命令:
    java CompileClassLoader HelloTest 这是一个参数
    输出的结果:

    [java] view plain copy
     
    1. 参数:这是一个参数  

    此时的输出已经不包含“CompileClassLoader编译HelloTest.java...”这一句,说明这一次并没有重新编译源代码,该问题的原因尚未找到,如果你知道答案,不妨告诉我。
    还需要注意的地方:
    1.执行命令时所处的目录应当是java源文件所在的目录;
    2.源代码中类的定义不要指定包,如果你指定了包,在编译源代码和指定加载器时需要做进一步处理。

  • 相关阅读:
    spark的环境安装
    (7)zabbix资产清单inventory管理
    (6)zabbix主机与组配置
    (5)zabbix配置详解
    (4)zabbix监控第一台服务器
    (3)zabbix用户管理
    (2)zabbix硬件需求
    (1) zabbix进程构成
    centos7系统root无法通过su切换到某个普通用户
    01基础复习
  • 原文地址:https://www.cnblogs.com/vilionzhan/p/8552338.html
Copyright © 2020-2023  润新知