首先知道我们写的.Java源码文件,经过javac.exe编译后成为了.class文件,.class文件中描述了类的各种信息,最终都需要加载到虚拟机之后才能运行和使用。而虚拟机如何加载这些.class文件?.class文件的信息进入到虚拟机后会发生什么变化?下面我们就来了解一下java类加载的过程:
我们写的类,首先会编译成字节码文件,然后通过Java 中的类加载器负责将我们需要使用的类,加载进内存。而类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。
当然jvm启动的时候,并不是一次性加载所有的类,而是根据需要动态去加载类,主要分为隐式加载和显示加载。
隐式加载:程序代码中不通过调用ClassLoader来加载需要的类,而是通过JVM类自动加载需要的类到内存中。例如,当我们在类中继承或者引用某个类的时候,JVM在解析当前这个类的时,发现引用的类不在内存中,那么就会自动将这些类加载到内存中。
显示加载:代码中通过Class.forName(),this.getClass.getClassLoader.LoadClass(),自定义类加载器中的findClass()方法等。
-
引导类加载器(bootstrap class loader):
它用来加载 Java 的核心库(jre/lib/rt.jar),是用原生C/C++代码来实现的,并不继承自java.lang.ClassLoader。加载扩展类和应用程序类加载器,并指定他们的父类加载器,因为不是java编写,所以在java中并无法展现,也获取不到。 但是我们可以通过简单代码来查看它所负责加载类的路径:
package test;
public class Test01{
public static voidmain(String[]args){
System.out.println(System.getProperty(“sun.boot.class.path”));
}
}
打印结果如下:
D:\ develop\Java\jdk1.8.0_20\jre\lib\resources.jar;D:\Program
Files\Java\jdk1.8.0_20\jre\lib\rt.jar;D:\Program
Files\Java\jdk1.8.0_20\jre\lib\sunrsasign.jar;D:\Program
Files\Java\jdk1.8.0_20\jre\lib\jsse.jar;D:\Program
Files\Java\jdk1.8.0_20\jre\lib\jce.jar;D:\Program
Files\Java\jdk1.8.0_20\jre\lib\charsets.jar;D:\Program
Files\Java\jdk1.8.0_20\jre\lib\jfr.jar;D:\Program
Files\Java\jdk1.8.0_20\jre\classes
- 扩展类加载器(extensions class loader):
它用来加载 Java 的扩展库(jre/ext/*.jar)。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 也可以通过System.out.println(System.getProperty(“java.ext.dirs”))查看加载类文件的路径。
打印结果如下:
D:\develop\Java\jdk1.8.0_20\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
-
系统类加载器(system class loader):
它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它,sun.misc.Launcher$AppClassLoader@58644d46 -
自定义类加载器(custom class loader):
除了系统提供的类加载器以外,我们开发人员自己可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。 -
类加载器的继承关系
ExtClassLoader,AppClassLoder继承URLClassLoader,而URLClassLoader继承ClassLoader,BoopStrapClassLoder不在上图中,因为它是由C/C++编写的,它本身是虚拟机的一部分,并不是一个java类。
jvm加载的顺序:BoopStrap ClassLoder-〉ExtClassLoader->AppClassLoder
-
类加载机制-双亲委托机制
咱先得弄清楚双亲委托机制到底是什么东西,它是是通过类加载器将class文件加载到虚拟机中以备调用,但是我们可以自己也写一个java.lang.String,来冒充jdk中的String, 假设这两个String都还没有加载,那么首先自身的加载器 AppClassLoader会去内存中找,找不到,直到bootstrap也在内存中找不到,那这时bootstrap会加载rt.jar中的String,至此加载结束,你自己的高仿是加载不到的。假设内存中原有的String已经加载,那么更简单,你兴致冲冲的写完了高仿准备装逼的时候,bootstrap 或者AppClassLoader 已经在内存中找到了(至于哪个加载器寻找哪一个区域的内存空间我不是很清楚 所以也不敢妄下定论)那你的高仿又失去了表现机会,这样的加载模式,保证了java自身类的安全。
public abstract class ClassLoader {protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//findLoadedClass() 是去内存中查找是否加载过这个名字为 name的类,上面的注释是源码中的注释,也是在说 检查这个类是否已经被加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//parent为classload类中的属性,表示它的父加载器,如果父加载器不为空则调用父类的loadClass方法
//可以想到如果当前类加载器没有在内存中找到该类,那么它的父类加载器就会去内存中寻找是否加载了该类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父加载器为空,那么调用BootstrapClassLoader去寻找该类,这也证明了BootstrapClassLoader在java中是
//以null的形式存在的
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}if (c == null) { //如果还是没有找到,那么调用findClass去查找 也就是从上面提到的路径中找 // 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©;
}
return c;
}
}} -
自定义类加载器步骤
1.继承ClassLoader
2.重写findClass()
3.调用defineClass()
package com.itcast.test;
public class Test {
public void say(){
System.out.println(“Hello MyClassLoader”);
}
}
package com.itcast.test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoad extends ClassLoader {
private String classpath;
public MyClassLoad(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] classDate = getDate(name);
if (classDate == null) {
} else {
//defineClass方法将字节码转化为类
return defineClass(name, classDate, 0, classDate.length);
}
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
//返回类的字节码
private byte[] getDate(String className) throws IOException {
InputStream in = null;
ByteArrayOutputStream out = null;
String path = classpath + File.separatorChar +
className.replace('.', File.separatorChar) + ".class";
try {
in = new FileInputStream(path);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
in.close();
out.close();
}
return null;
}
}package com.itcast.test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestMyClassLoad {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
//自定义类加载器的加载路径
MyClassLoader myClassLoader = new MyClassLoader(“E:\lib”);
//包名+类名
Class c = myClassLoader.loadClass(“com.itcast.test.Test”);
if (c != null) {
Object obj = c.newInstance();
Method method = c.getMethod(“say”, null);
method.invoke(obj, null);
System.out.println(c.getClassLoader().toString());
}
}
}
首先:需要在E盘下创建一个lib文件夹,再把idea项目中src的com文件夹直接复制 E盘lib文件夹即可。
打印结果如下:
备注:以上代码均在JDK1.8版本下进行,JDK1.9类加载器略有不同。