- 0. java相关环境变量:
- JAVA_HOME
- PATH
- CLASSPATH
- 1. 什么是classloader
- 2. 一个类怎么被加载到jvm中的
- 1. 三个加载器
- 2. 加载顺序/委托加载机制
- 3. 代码实现类的加载
- 3.1 几个重要的函数
- 3.1.1 loadClass
- 3.1.2 defineClass
- 3.2 利用URLClassLoader加载远程class文件
- 3.3 使用ClassLoader类的defineClass函数直接加载字节码
- 参考文章
指的是你JDK安装的位置,一般默认安装在C盘,如
PATHC:Program FilesJavajdk1.8.0_91
path路径中的可执行文件,在命令行输入名字后可直接执行,不需要加上绝对路径,例如javac、java等命令。
CLASSPATHPATH=%JAVA_HOME%bin;%JAVA_HOME%jrebin;%PATH%;
CLASSPATH=.;%JAVA_HOME%lib;%JAVA_HOME%libtools.jar
jar包路径。需要注意的是前面的.;,.代表当前目录。
1. 什么是classloader就是在程序的运行中将其他的java class文件加载到jvm虚拟机中的一种技术。classloader本质是一个类,这个类中有实现此种技术的函数。
2. 一个类怎么被加载到jvm中的类是被类加载器加载到jvm中的
1. 三个加载器理论上所有的加载器都可以将字节码加载到jvm中。
我们可以自己定义一个classloader加载器,这个加载器的父类加载器是应用类加载器(appClassLoader),应用类加载器(appClassLoader)的父类加载器是平台类加载器(PlatformClassLoader),平台类加载器(PlatformClassLoader)但父类加载器是根类加载器,这里的父类与类中继承概念中的父类要区分,可以仅仅将这个父类理解成一个名字,而不是真正的继承关系。他们三个其实都是继承了URLClassLoader类。
-
根类加载器(Bootstrap ClassLoader)
根装载器不是ClassLoader的子类,由C++编写,因此在java中看不到他,负责装载JRE的核心类库,如JRE目录下的rt.jar,charsets.jar等。
根据代码逻辑,平台类加载器的父加载器为null,接着看代码我们知道,如果父加载器为null的时候则自动会调用Bootstrap ClassLoader这个加载器作为此加载器的父加载器。因此从逻辑上Bootstrap ClassLoader加载器是平台类加载器的父加载器。 -
平台类加载器(PlatformClassLoader)(jdk1.8之后的版本,之前的称为扩展类加载器 ExtClassLoader)
虽说能拿到,但是我们在实践中很少用到它,它主要加载扩展目录%JAVA_HOME%libext下的jar包,。还可以加载-D java.ext.dirs=path选项指定的目录。 -
应用类加载器(appClassLoader)
它主要加载我们应用程序中的类,它会加载当前应用的classpath的所有类。
这时候我们假设要加载一个test.class类,并写好了加载的代码:
- 我们写的classloader123加载器会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则询问它的父加载器应用类加载器(appClassLoader)。
- 应用类加载器(appClassLoader)会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则询问它的父加载器平台类加载器(PlatformClassLoader)。
- 平台类加载器(PlatformClassLoader)接收到这个命令后会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则询问它的父加载器根类加载器。
- 根类加载器接收到这个命令后会先查看缓存中是否有test.class这个类,如果有的话就直接返回,如果没有则,在它自己事先设定好的寻找路径中找是否有test.class文件,如果有则加载,如果没有则会让它的子加载器平台类加载器(PlatformClassLoader)去尝试加载。
- 平台类加载器(PlatformClassLoader)在它自己事先设定好的寻找路径中找是否有test.class文件,如果有则加载,如果没有则会让它的子加载器应用类加载器(appClassLoader)。
- 应用类加载器(appClassLoader)会在自己的设定的路径下查看是否有test.class文件,如果有就直接加载到jvm中,如果没有则会问它的子加载器,也就是我们自己写的那个加载器classloader123。
- 加载器classloader123会在自己的设定的路径下查看是否有test.class文件,如果有就将这个数据传递给defineclass函数,defineclass函数会将这个类直接加载到jvm中。如果没有找到test.class则会加载失败。
protected Class> loadClass(String name,boolean resolve)
这个loadclass函数逻辑大概如下:
- 调用findLoadedClass(String name)去检测这个class是不是已经加载过了。
- 执行父加载器的loadClass方法。如果父加载器为null,则用jvm内置的加载器Bootstrap ClassLoader去替代。这也解释了平台加载器的父加载器为null,但仍然说Bootstrap ClassLoader是它的父加载器。等自行到Bootstrap ClassLoader的时候,就不会再寻找父加载器了,因为已经没有了。
- 如果找不到父类加载器等时候,则通过findClass(String name)在各自加载器设定好的路径中查找是不是有这个类,如果没有,则在子加载器中事先设定好的路径中查找。如果找到了就传送给defineclass函数进行加载,defineclass函数会将类直接加载到jvm中。
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检测是否已经加载
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则调用Bootstrap Classloader
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();
//父加载器没有找到,则调用findclass
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()加载寻找到的类
resolveClass(c);
}
return c;
}
}
3.1.2 defineClass
defineClass方法是protected属性的,且它是来自ClassLoader类的。我们无法直接从外部进行调用,所以我们这里需要借助反射来调用这个方法。
这个函数的功能是,加载二进制的java字节码到jvm中。
3.2 利用URLClassLoader加载远程class文件加载的流程如下:
1.
classloader代码:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class LoadHttpRemote {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//注意url结尾需要有斜杠
URL[] urls = {new URL("http://121.111.111.111:8000/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class c= loader.loadClass("Test");
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("say",null);
//通过反射调用Test类的say方法
method.invoke(obj, null);
}
}
test.java代码:
public class Test {
public void say(){
System.out.println("Say Hello1");
}
}
3.3 使用ClassLoader类的defineClass函数直接加载字节码
因为defineClass是protected属性的,所以只能通过反射来使用:
import sun.misc.base64Decoder;
import java.lang.reflect.Method;
public class DefineClassTest {
public static void main(String[] args) throws Exception {
//利用反射来调用。
Class clas = Class.forName("java.lang.ClassLoader");
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);
String base64_code = "yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEAn" +
"Bjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsn" +
"bG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhn" +
"L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ryn" +
"ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nn" +
"OylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAn" +
"AAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM";
base64Decoder base64Decoder = new base64Decoder();
byte[] code = base64Decoder.decodeBuffer(base64_code);
//调用AppClassLoader加载器中的defineclass函数来实现类的加载
Class Hello = (Class)defineclass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
//调用ExtClassLoader加载器中的defineclass函数来实现类的加载
//Class Hello = (Class)defineclass.invoke(ClassLoader.getSystemClassLoader().getParent(), "Hello", code, 0, code.length);
Hello.newInstance();
}
}
参考文章
一篇文章带你深入理解 Java 中的Class.getClassLoader
一看你就懂,超详细java中的ClassLoader详解
Java安全-Java动态加载字节码方法



