JavaEE学习之类加载器
类装载子系统
在JAVA虚拟机中,负责查找并装载类型的那部分被称为类装载子系统。
JAVA虚拟机有两种类装载器:启动类装载器和用户自定义类装载器。前者是JAVA虚拟机实现的一部分,后者则是Java程序的一部分。由不同的类装载器装载的类将被放在虚拟机内部的不同命名空间中。
类装载器子系统涉及Java虚拟机的其他几个组成部分,以及几个来自java.lang库的类。比如,用户自定义的类装载器是普通的Java对象,它的类必须派生自java.lang.ClassLoader类。ClassLoader中定义的方法为程序提供了访问类装载器机制的接口。此外,对于每一个被装载的类型,JAVA虚拟机都会为它创建一个java.lang.Class类的实例来代表该类型。和所有其他对象一样,用户自定义的类装载器以及Class类的实例都放在内存中的堆区,而装载的类型信息则都位于方法区。
类装载器子系统除了要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。这些动作必须严格按以下顺序进行:
(1)装载——查找并装载类型的二进制数据。
(2)连接——指向验证、准备、以及解析(可选)。
● 验证 确保被导入类型的正确性。
● 准备 为类变量分配内存,并将其初始化为默认值。
● 解析 把类型中的符号引用转换为直接引用。
(3)初始化——把类变量初始化为正确初始值。
每个JAVA虚拟机实现都必须有一个启动类装载器,它知道怎么装载受信任的类,每个类装载器都有自己的命名空间,其中维护着由它装载的类型,所以一个Java程序可以多次装载具有同一个全限定名的多个类型,这样一个类型的全限定名就不足以确定在一个Java虚拟机中的唯一性。因此,当多个类装载器都装载了同名的类型时,为了惟一地标识该类型,还要在类型名称前加上装载该类型(指出它所位于的命名空间)的类装载器标识。位于不同命名空间的相同类无法相互转换,下面程序演示了这一点:
1 import java.io.ByteArrayInputStream; 2 import java.io.ByteArrayOutputStream; 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.io.InputStream; 8 9 public class MyClass extends ClassLoader{ 10 11 private String name;//类加载器的名称 12 13 private String path;//加载类的路径 14 15 private final String extendName = ".class";//文件扩展名 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public String getPath() { 26 return path; 27 } 28 29 public void setPath(String path) { 30 this.path = path; 31 } 32 33 public String getExtendName() { 34 return extendName; 35 } 36 37 public MyClass(String name){ 38 super(); 39 this.name = this.name; 40 } 41 42 public MyClass(ClassLoader parent,String name){ 43 super(parent); 44 this.name=name; 45 } 46 47 @Override 48 public String toString() { 49 return this.name; 50 } 51 52 @Override 53 protected Class<?> findClass(String name) throws ClassNotFoundException { 54 byte[] bytes = this.loadDate(name); 55 return this.defineClass(name, bytes, 0, bytes.length); 56 } 57 58 public byte[] loadDate(String name){ 59 String filename = name.replace(".", "\\"); 60 String filepath = this.path+filename+this.extendName; 61 File file = new File(filepath); 62 InputStream in = null; 63 ByteArrayOutputStream out = null; 64 byte[] bytes = null; 65 try { 66 in = new FileInputStream(file); 67 int len = 0; 68 out = new ByteArrayOutputStream(); 69 while((len=in.read())!=-1){ 70 out.write(len); 71 } 72 bytes=out.toByteArray(); 73 return bytes; 74 } catch (FileNotFoundException e) { 75 e.printStackTrace(); 76 } catch (IOException e) { 77 e.printStackTrace(); 78 }finally{ 79 try { 80 in.close(); 81 out.close(); 82 } catch (IOException e) { 83 e.printStackTrace(); 84 } 85 } 86 return null; 87 } 88 89 public static void main(String[] args) { 90 MyClass loader1 = new MyClass("loader1"); 91 loader1.setPath("E:\\test\\loader1"); 92 MyClass loader2 = new MyClass(loader1,"loader2"); 93 loader2.setPath("E:\\test\\loader2"); 94 95 96 MyClass loader3 = new MyClass(null,"loader3"); 97 loader3.setPath("E:\\test\\loader3\\"); 98 test(loader1); 99 test(loader2); 100 test(loader3); 101 102 try{ 103 Class classzz = loader3.loadClass("Sample"); 104 Object object = classzz.newInstance(); 105 Sample sample = (Sample)object; 106 System.out.println(sample.toString()); 107 }catch(Exception e){ 108 e.printStackTrace(); 109 } 110 } 111 112 public static void test(ClassLoader loader){ 113 try{ 114 Class classzz = loader.loadClass("Sample"); 115 Object object = classzz.newInstance(); 116 }catch(Exception e){ 117 e.printStackTrace(); 118 } 119 } 120 }
1 public class Sample { 2 public Sample(){ 3 System.out.println("i am Sample......,加载我的类加载器的名称是:"+this.getClass().getClassLoader().toString()); 4 new Person(); 5 } 6 }
1 public class Person { 2 public Person(){ 3 System.out.println("i am person......,加载我的类加载器的名称是:"+this.getClass().getClassLoader().toString()); 4 } 5 }
上面程序通过继承ClassLoader,实现自定义类加载器,在主方法中,创建三个自定义类加载器,其中loader2的父类加载器为loader1,loader3的父类加载器为根类加载器,在E盘下创建三条路径,分别为E:\\test\\loader1,E:\\test\\loader2,E:\\test\\loader3,将三段程序的.class文件分别放置在三个文件夹内,通过DOS命令切换到该目录下,首先运行命令:java MyClass,结果如下:
1 E:\test\loader1>java MyClass 2 i am sample......,加载我的类加载器的名称是:sun.misc.Launcher$AppClassLoader@39443f 3 i am person......,加载我的类加载器的名称是:sun.misc.Launcher$AppClassLoader@39443f 4 i am sample......,加载我的类加载器的名称是:sun.misc.Launcher$AppClassLoader@39443f 5 i am person......,加载我的类加载器的名称是:sun.misc.Launcher$AppClassLoader@39443f 6 i am sample......,加载我的类加载器的名称是:loader3 7 i am person......,加载我的类加载器的名称是:loader3
从结果可以看出,test1,test2均为系统类加载加载所需要类,对于test3,加载所需类的类加载器为自定义类加载器MyClass,可以肯出虚拟机在加载类的过程中使用父类委托机制,loader3的父类为根类加载器,在JDK中找不到自定义类Sample,所以只能靠自定义类加载器加载该类,对于该自定义类加载器加载的类则位于自己命名空间下,其他明明空间下无法调用,可以通过修改上述程序进行验证,
1 //package com.swust.自定义类加载器; 2 3 import java.io.ByteArrayInputStream; 4 import java.io.ByteArrayOutputStream; 5 import java.io.File; 6 import java.io.FileInputStream; 7 import java.io.FileNotFoundException; 8 import java.io.IOException; 9 import java.io.InputStream; 10 11 public class MyClass extends ClassLoader{ 12 13 private String name;//类加载器的名称 14 15 private String path;//加载类的路径 16 17 private final String extendName = ".class";//文件扩展名 18 19 public String getName() { 20 return name; 21 } 22 23 public void setName(String name) { 24 this.name = name; 25 } 26 27 public String getPath() { 28 return path; 29 } 30 31 public void setPath(String path) { 32 this.path = path; 33 } 34 35 public String getExtendName() { 36 return extendName; 37 } 38 39 public MyClass(String name){ 40 super(); 41 this.name = this.name; 42 } 43 44 public MyClass(ClassLoader parent,String name){ 45 super(parent); 46 this.name=name; 47 } 48 49 @Override 50 public String toString() { 51 return this.name; 52 } 53 54 @Override 55 protected Class<?> findClass(String name) throws ClassNotFoundException { 56 byte[] bytes = this.loadDate(name); 57 return this.defineClass(name, bytes, 0, bytes.length); 58 } 59 60 public byte[] loadDate(String name){ 61 String filename = name.replace(".", "\\"); 62 String filepath = this.path+filename+this.extendName; 63 File file = new File(filepath); 64 InputStream in = null; 65 ByteArrayOutputStream out = null; 66 byte[] bytes = null; 67 try { 68 in = new FileInputStream(file); 69 int len = 0; 70 out = new ByteArrayOutputStream(); 71 while((len=in.read())!=-1){ 72 out.write(len); 73 } 74 bytes=out.toByteArray(); 75 return bytes; 76 } catch (FileNotFoundException e) { 77 e.printStackTrace(); 78 } catch (IOException e) { 79 e.printStackTrace(); 80 }finally{ 81 try { 82 in.close(); 83 out.close(); 84 } catch (IOException e) { 85 e.printStackTrace(); 86 } 87 } 88 return null; 89 } 90 91 public static void main(String[] args) { 92 MyClass loader1 = new MyClass("loader1"); 93 loader1.setPath("E:\\test\\loader1\\"); 94 MyClass loader2 = new MyClass(loader1,"loader2"); 95 loader2.setPath("E:\\test\\loader2\\"); 96 97 98 MyClass loader3 = new MyClass(null,"loader3"); 99 loader3.setPath("E:\\test\\loader3\\"); 100 // test(loader1); 101 // test(loader2); 102 // test(loader3); 103 104 try{ 105 Class classzz = loader3.loadClass("Sample"); 106 Class classess = loader1.loadClass("Sample"); 107 Object object = classzz.newInstance(); 108 Object object1 = classess.newInstance(); 109 System.out.println("由不同类加载器加载的类类型是否可以转换:"+(object==object1)); 110 // Sample sample = (Sample)object; 111 // System.out.println(sample.toString()); 112 }catch(Exception e){ 113 e.printStackTrace(); 114 } 115 } 116 117 public static void test(ClassLoader loader){ 118 try{ 119 Class classzz = loader.loadClass("Sample"); 120 Object object = classzz.newInstance(); 121 }catch(Exception e){ 122 e.printStackTrace(); 123 } 124 } 125 }
执行结果如下:
1 E:\test\loader1>java -cp .;E:\test\loader3 MyClass 2 i am sample......,加载我的类加载器的名称是:loader3 3 i am person......,加载我的类加载器的名称是:loader3 4 i am sample......,加载我的类加载器的名称是:sun.misc.Launcher$AppClassLoader@39443f 5 i am person......,加载我的类加载器的名称是:sun.misc.Launcher$AppClassLoader@39443f 6 由不同类加载器加载的类类型是否可以转换:false
从结果可以看出,由不同类加载器加载的同一类无法相互引用,虽然都是相同的类Sample,但由于他们位于不同的命名空间中,但新建实例却不是同一对象,这样做也保证了类的唯一性