类的加载阶段将类的.class文件中的二进制数据读入到内存中,将其常量池、属性表、方法表、异常表放在运行时数据区的方法区内,然后在创建一个对应的java.lang.Class对象,用来封装类在方法区内的数据结构
JVM主要在程序第一次主动使用类的时候,才会去加载该类,而且只加载一次
jvm支持两种类型的加载器,分别是引导类加载器(启动类加载器)和自定义加载器,引导类加载器是由c/c++实现的,自定义加载器是由java实现的。自定义加载器派生于ClassLoader,它包括扩展类加载器(Extension Class Loader),系统类加载器(System Class Loader),自定义加载器(User-Defined ClassLoader)
引导类加载器(启动类加载器)使用c/c++实现,嵌套再jvm内部
它用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、 resource.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类。
并不继承自java.lang.ClassLoader,没有父加载器
java语言编写,由sun.misc.Launcher$ExtClassLoader实现 (ExtClassLoader是ClassLoader的子类)
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的JAR 放在此目录下,也会自动由扩展类加载器加载。
父类加载器为启动类加载器
java语言编写,由 sun.misc.Lanucher$AppClassLoader 实现(AppClassLoader 是ClassLoader的子类)
该类加载是程序中默认的类加载器,一般来说Java应用的类都是由它来完成加载的,它负责加载环境变量classpath或系统属性java.class.path 指定路径下的类库
父类加载器为扩展类加载器
通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。
在日常的Java开发中,类加载几乎是由三种加载器配合执行的,在必要时我们还可以自定义类加载器,来定制类的加载方式。
自定义加载器的作用:
1.隔离加载类
把类加载到不同的应用系统中。比如tomcat这类web应用服务器,内部自定义了好几中类加载器,用于隔离web应用服务器上的不同应用程序。
2.修改类加载方式
除了Bootstrap加载器外,其他的加载器并非一定要引入。根据实际情况在某个时间点按需进行动态加载。
3.扩展加载源
比如还可以从数据库、网络、或其他终端上加载
4.防止源码泄漏
java代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。
public class GetClassLoader {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);
//获取自定义类加载器
ClassLoader classLoader = GetClassLoader.class.getClassLoader();
System.out.println(classLoader);
//同样的方式获取不到启动类加载器
ClassLoader bootStrapClassLoader = extClassLoader.getParent();
System.out.println(bootStrapClassLoader);
}
}
双亲委派
如果一个类加载器收到类加载的请求,它自己先不会去加载这个类,它会先把这个加载的请求委派给父类加载器加载。如果父类加载器在自己的范围内加载不到(ClassNotFoundException),自己才回尝试去加载。
自定义了一个java.lang.String并启动
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("123");
}
}
父类会先加载java自己的java.lang.String,然后这里使用String的类方法的时候,使用的就是父类的加载的java.lang.String
在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。
双亲委派的实现原理
进入java.lang.ClassLoader
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//检查是否加载过这个class
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//先让父加载器加载
c = parent.loadClass(name, false);
} else {
//如果父加载器为null则用引导类加载器加载
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();
//尝试自己加载,这个方法需要classloader的子类自己实现
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;
}
}
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
自定义类加载器的实现
1继承ClassLoader类
2重写loadClass方法或者重写findClass方法
loadClass是实现双亲委派机制逻辑的地方,修改他需要重写双亲委派机制的逻辑,推荐重写findClass方法
创建TestClass
public class TestClass {
}
在文件路径下编译文件
javac TestClass.java
public class MyClassLoader extends ClassLoader {
private String codePath;
public MyClassLoader(String codePath) {
this.codePath = codePath;
}
@Override
protected Class> findClass(String name) {
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;
try {
//1.字节码路径
String fileName = codePath + name + ".class";
//2.获取输入流
bis = new BufferedInputStream(new FileInputStream(fileName));
//3.获取输出流
baos = new ByteArrayOutputStream();
//4.io读写
int len;
byte[] data = new byte[1024];
while ((len = bis.read(data)) != -1) {
baos.write(data, 0, len);
}
//5.获取内存中字节数组
byte[] byteCode = baos.toByteArray();
//6.调用defineClass 将字节数组转成Class对象
Class> defineClass = defineClass(null, byteCode, 0, byteCode.length);
return defineClass;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public static void main(String[] args) {
//这里写入自己的TestClass.class路径
MyClassLoader classLoader = new MyClassLoader("TestClass.class的路径");
try {
Class> clazz = classLoader.loadClass("TestClass");
System.out.println("TestClass.class的类加载器是" + clazz.getClassLoader().getClass().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}



