一、类加载器父亲委托机制
Java类加载器采用父亲委托机制,先尝试用父加载器加载,父加载器无法加载,则此加载器加载,
注意,此父加载器与子加载器并非继承关系,而是指定的关系。
从ClassLoader类loadClass()方法的源码,可以看出父亲委托机制的思想的实现。
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)根(Bootstrap)类加载器:根类加载器的实现依赖于底层操作系统,并没有继承java.lang.ClassLoader类(加载虚拟机的核心类库,如java.lang.Object,根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库)
import java.util.Properties;
public class TestCP{
//命令行下运行
public static void main(String[] args){
Properties pros = System.getProperties();
String p1= pros.getProperty("sun.boot.class.path");
for (String s : p1.split(";")){
System.out.println(s);
}
}
}
结果:
(2)扩展(Extension)类加载器:它的父加载器为根类加载器,纯Java类,是java.lang.ClassLoader的子类(加载系统属性java.ext.dirs所指定的目录下的类库,或从JDK安装目录下的jre/lib/ext子目录下加载类库)
import java.util.Properties;
public class TestCP{
//命令行下运行
public static void main(String[] args){
Properties pros = System.getProperties();
String p1= pros.getProperty("java.ext.dirs");
for (String s : p1.split(";")){
System.out.println(s);
}
}
}
结果 :
(3)系统(System)类加载器:它的父加载器为扩展类加载器,纯Java类,是java.lang.ClassLoader的子类(加载系统属性java.class.path所指定的目录下的类库,或从环境变量classpath所指定的目录下的类库)
import java.util.Properties;
public class TestCP{
//命令行下运行
public static void main(String[] args){
Properties pros = System.getProperties();
String p1= pros.getProperty("java.class.path");
for (String s : p1.split(";")){
System.out.println(s);
}
}
}
结果:
在集成编辑代码工具,引入第三方包,实际上就是把第三方包的路径加到java.class.path中,使用时用系统类加载器加载
查看加载Object、JarFileSystemProvider、TestClassLoader的加载器:
import com.sun.nio.zipfs.JarFileSystemProvider;
public class TestClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader objClassLoader = Object.class.getClassLoader();
System.out.println(objClassLoader);
ClassLoader proClassLoader = JarFileSystemProvider.class.getClassLoader();
System.out.println(proClassLoader);
ClassLoader testClassLoaderClassLoader = TestClassLoader.class.getClassLoader();
System.out.println(testClassLoaderClassLoader);
}
}
查看加载器结果:
Object类由根加载器加载;com.sun.nio.zipfs.JarFileSystemProvider在ext的目录下,由扩展加载器加载;TestClassLoader在java.class.path值中,由系统加载器加载
null
sun.misc.Launcher$ExtClassLoader@677327b6
sun.misc.Launcher$AppClassLoader@18b4aac2
命令空间:每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。
运行时包:由同一个类加载器加载的属于相同包的类组成了运行时包。只有属于同一个运行时包的类才能相互访问包可见(即默认访问级别)的类和类成员,这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。
不同类加载器的命名空间存在以下关系:
同一个命名空间的类是可见的;子加载器的命名空间包含父加载器的命名空间,因此由子加载器加载的类可以看见父加载器加载的类;父加载器的类看不见子加载器的类;如果两个加载器之间没有直接或间接的父子关系,那么它们格各自加载的类是相互看不见的(可采用Java反射机制来访问对方实例的属性和方法)。
三、自定义加载器
MyClassLoader类:
每个类加载器:loadClass->findClass->defineClass
自定义加载器需要,继承ClassLoader;重写findClass(String name)方法;调用defineClass()方法
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoader extends ClassLoader {
private String name;
private String path;
private final String fileType = ".class";
public MyClassLoader(String name){
super();
this.name = name;
}
public MyClassLoader(ClassLoader parent, String name){
super(parent);
this.name = name;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
}
/*把类的二进制数据读到内存中*/
private byte[] loadClassData(String name) throws ClassNotFoundException {
FileInputStream fis = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
name = name.replace(".", "\");
fis = new FileInputStream(new File(path + name + fileType));
baos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = fis.read()) != -1){
baos.write(ch);
}
data = baos.toByteArray();
}catch (IOException e){
throw new ClassNotFoundException("Class is not found:" + name, e);
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return data;
}
public void setPath(String path) {
this.path = path;
}
public String getPath() {
return path;
}
@Override
public String toString() {
return name;
}
}
被自定义加载的Dog类:
打印出加载Dog类的加载器名称
package classloader;
public class Dog {
public Dog(){
System.out.println("Dog is loaded by " + this.getClass().getClassLoader());
}
}
Test类:
设置loader1加载的路径为F:libserverlib,loader2加载的路径为F:libclientlib,loader3加载的路径为F:libotherlib,
把Dog类的.class文件复制到三个加载器的加载路径下(Dog在classloader包下,需要在加载路径下建立classloader文件夹,再把Dog类的.class文件复制到classloader目录下)
Test类和MyClassLoader的.class文件下,复制到F:libsyslib下
loader1加载器的父加载器是系统类加载器;loader2加载器的父加载器是loaer1;loader3加载器的父加载器是根类加载器
public class Test {
public static void main(String[] args) throws Exception {
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("F:\lib\serverlib\");
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("F:\lib\clientlib\");
MyClassLoader loader3 = new MyClassLoader(null, "loader3");
loader3.setPath("F:\lib\otherlib\");
test(loader2);
test(loader3);
}
public static void test(ClassLoader loader) throws Exception {
Class objClass = loader.loadClass("classloader.Dog");
Object obj = objClass.newInstance();
}
}
加载器结构图:
执行java -classpath F:libsyslib classloader.Test
指定classpath为F:libsyslib,则此目录下由系统加载器加载