一、类加载过程
多个java文件经过编译打包生成可运行jar包,最终由java命令运行某个主类的main启动程序,这里需要先通过类加载器把主类加载到JVM
主类在运行过程中如果使用到其他类,会逐步加载这些类。
注意:jar包里的类不是一次性全部加载的,是使用到时才加载,不过类似于java.lang.Object这种支持JVM运行的类会在启动时便被加载。
类加载过程
加载>>验证>>准备>>解析>>初始化>>使用>>卸载
- 加载:在硬盘上查找并通过IO将字节码文件读入到内存中,使用到类时才会被加载,例如调用类的main()方法,new对象等
- 验证:验证字节码文件的正确性
- 准备:为类的静态变量分配内存空间,并给静态变量赋予初始值
- 解析:将符号引用替换为这直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链 接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块
二、类加载器和双亲委派机制
类的加载主要通过类加载器来实现,java中的类加载器如下:
- 启动类加载器(BootstrapClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
- 扩展类加载器(ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
- 应用程序加载器(AppClassLoader):负责加载ClassPath路径下的类包,主要是java开发人员自己写的类生成的字节码文件
- 自定义加载器:开发人员可以通过继承ClassLoader这个类来自定义自己的类加载器,只需要实现findClass()这个方法即可,如果要打破双亲委派机制则需要额外自己实现loadClass()这个方法修改代码使其不使用双亲委派的方式。
类加载器示例:
package jvm;
public class TestJDKClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
}
结果:
null//启动类加载器是使用C++语言实现,所以无法打印
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader
双亲委派机制的逻辑大致如下:
1.首先加载指定名称的类是否已被加载过,如果加载过就不需要重复加载,直接返回。
2.如果此类没有被加载,那么判断是否有父类加载器,如果有,则委派给父加载器加载,如果没有则直接委派给启动类加载器加载。
3.如果父加载器及bootstrapClassLoader均没有找到目标类则有当前类加载器的findClass完成加载。
总结:加载器加载时将加载动作逐级向上委托直到最高级的启动类加载器,再从最高级向下逐级进行目标类加载,如果在某一级加载到了目标类则不再向下继续。
设计双亲委派机制的目的:
- 沙盒安全机制:java开发人员自己写的java.lang.class不会被加载,防止核心API库被篡改。
- 避免类的重复加载:当父亲类加载器已经加载到目标类时,字加载器便不会在进行加载,保证的被加载类的唯一性。
在自定义类加载器示例:
自定义类加载器主要是重写findclass()方法:
package jvm;
import java.io.FileInputStream;
import java.io.IOException;
//自定义类加载器
public class MyClassloaderTest extends ClassLoader{
private String classPath;
//初始化时指定字节码目录所在目录
public MyClassloaderTest(String classPath) {
this.classPath = classPath;
}
//将字节码文件加载到内存中
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\.","/");
FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
int len = fis.available();
byte[] bt = new byte[len];
fis.read(bt);
fis.close();
return bt;
}
//通过该类返回Class对象
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name,data,0,data.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
打破双亲委派机制
如果要打破双亲委派机制只需要重写loadClass()这个方法
package jvm;
import java.io.FileInputStream;
import java.io.IOException;
//自定义类加载器
public class MyClassloaderTest extends ClassLoader{
private String classPath;
public MyClassloaderTest(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\.","/");
FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
int len = fis.available();
byte[] bt = new byte[len];
fis.read(bt);
fis.close();
return bt;
}
@Override
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) {
// 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;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name,data,0,data.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
测试类:
package jvm;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
MyClassloaderTest classloader = new MyClassloaderTest("F:\test");
Class clazz = classloader.loadClass("java.lang.String");
Object o = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(o,null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}