Java类加载器的学习---底层类加载器思想以及自己写的类加载器,java---
Java类加载器的学习---底层类加载器思想以及自己写的类加载器,java---
Java 类加载机制
ava 虚拟机一般使用 Java 类的流程为:首先将开发者编写的 Java 源代码(.java 文件)编译成 Java 字节码(.class 文件),然后类加载器会读取这个 .class 文件,并转换成 java.lang.Class 的实例。有了该 Class 实例后,Java 虚拟机可以利用 newInstance 之类的方法创建其真正对象了。
ClassLoader 是 Java 提供的类加载器,绝大多数的类加载器都继承自 ClassLoader,它们被用来加载不同来源的 Class 文件。
Class 文件来源
- Java自己的核心类 如 java.lang、java.math、java.io 等 package 内部的类,位于 $JAVA_HOME/jre/lib/ 目录下,如 java.lang.String 类就是定义在 $JAVA_HOME/jre/lib/rt.jar 文件里;
- Java的核心扩展类,位于 $JAVA_HOME/jre/lib/ext 目录下。开发者也可以把自己编写的类打包成 jar 文件放入该目录下;
- 开发者自己写的类,这些类位于项目目录下;
- 动态加载远程的 .class 文件
Java的类加载器
针对上面四种来源的类,分别有不同的加载器负责加载。
双亲委托 加载机制
源码查看双亲委托加载机制过程如下
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否曾加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 优先让 parent 加载器去加载
c = parent.loadClass(name, false);
} else {
// 如无 parent,表示当前是 BootstrapClassLoader,调用 native 方法去 JVM 加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果 parent 均没有加载到目标 class,调用自身的 findClass() 方法去搜索
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;
}
}
// BootstrapClassLoader 会调用 native 方法去 JVM 加载
private native Class<?> findBootstrapClass(String name);
系统默认三个主要类加载器
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap, ExtClassLoader, AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
(c语言写的)
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
关系如下图
类加载器的父类委托机制: 父类里面有.class 就先使用父类里的
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的aa.jar包中后,运行结果为ExtClassLoader的原因。
<不同的类加强器,对于同一个类,是不能转换的>
比如Person类, 正常 p = new Person(){这个是通过java类加载器加载的) 那么我们再调用自己的加载器 类反射得到个 Object obj ;我们知道这个obj一定是Person 但是不能转换为Java类加载器 写的Person
package cn.hncu.classLoader;
import java.lang.reflect.Method;
import org.junit.Test;
public class ClassLoaderDemo1 {
//1演示:类加载器中的父类委托机制---父类加载器能加载的,子类就不加载
@Test
public void demo1(){
//如果“\jre\lib\ext”目录中的某个jar包中存在Person.class则会
//屏蔽掉当前classpath下的Person类 ---父类委托机制的原因。
//AppClassLoader加载classpath下的类, 父类加载器ExtClassLoader加载的是“\jre\lib\ext”目录中的所有jar包,爷爷类加载器BootStrap加载的是rt.jar
Person p = new Person();
p.aa();
}
@Test//看看默认的3个类加载器
public void demo2(){
ClassLoader loader = Person.class.getClassLoader();
//如果“\jre\lib\ext”目录中的某个jar包中存在Person.class,则此处输出:ExtClassLoader。否则输出:AppClassLoader
System.out.println("1:"+loader);
loader = loader.getParent();
System.out.println("2:"+loader);
loader = loader.getParent();
System.out.println("3:"+loader); //BootStrap是C开发的,因此这里输出null
}
@Test//用系统的3个类加载器是无法加载它们默认加载地方(rt.jar,ext目录,classpath)以外的类的----如果想要加载这些类(包括本地磁盘指定目录、来自网络甚至无中生有的类,得自己做类加载器
public void demo3() throws Exception{
//MyA a = new MyA(); a.aa();
//用类反射来加载d盘a文件夹下的 “aa.MyA类”
Class c = Class.forName("d:/a/aa.MyA");
Object obj = c.newInstance();
Method m = c.getDeclaredMethod("aa", nu````` l ;
m.invoke(obj, null);
}
}
自制类加载器
我们自己写的类加载器
自制类加载类的核心步骤:1.继承ClassLoader 2.调用父类的defineClass(对应class字节码文件的数据—byte[]),该方法会帮我们返回一个Class对象
因为ClassLoader类中的方法是protected权限的,因此我们想要访问,必须定义成它的子类
package cn.hncu.classLoader.myClassLoader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
//自制类加载类的核心步骤:1.继承ClassLoader 2.调用父类的defineClass(对应class字节码文件的数据---byte[]),该方法会帮我们返回一个Class对象
//因为ClassLoader类中的方法是protected权限的,因此我们想要访问,必须定义成它的子类
public class MyClassLoader extends ClassLoader{
public Class findClass(String fileName) {
Class c=null;
try {
//把外面的字节码文件(.class文件)读取到内存流baout中
InputStream in = new FileInputStream(fileName);
ByteArrayOutputStream baout = new ByteArrayOutputStream();
int len=0;
byte bs[] = new byte[512];
while((len=in.read(bs))!=-1){
baout.write(bs, 0, len);
}
baout.close();
//把内存流baout中的数据定义成一个Class对象---利用父类ClassLoader中的一个方法
byte b[] = baout.toByteArray();
c = defineClass(null, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassFormatError e) {
e.printStackTrace();
}
return c;
}
}
package cn.hncu.classLoader.myClassLoader;
import java.lang.reflect.Method;
import org.junit.Test;
public class TestMyClassLoader {
public static void main(String[] args) {
try {
Class c = Class.forName("cn.hncu.classLoader.myClassLoader.TestMyClassLoader");
Object obj = c.newInstance();
System.out.println(obj);
System.out.println(c.getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testMyClassLoader() throws Exception{
MyClassLoader loader = new MyClassLoader();
Class c = loader.findClass("D:/a/aa/MyA.class");
Object obj = c.newInstance();
System.out.println(obj);
System.out.println(c.getClassLoader());
Method m = c.getDeclaredMethod("aa", null);
m.invoke(obj, null);
}
}
部分内容引用地址
相关文章
- 暂无相关文章
用户点评