java类加载过程
类加载是指将类的字节码文件加载到JVM内存,首先将的类信息存放在方法区,同时在堆中创建一个对应的的java.lang.class对象,作为对方法区类信息访问的入口。方法区类信息包括:
(1)类接口信息例如类名,权限修饰符。
(2)类的静态变量,存储在方法区的静态区。
(3)类的静态方法,存储在方法区的静态区。
(4)类的非静态方法,存储在方法区的非静态区。
(5)代码块,存储在方法区的静态区。
(6)类构造函数,存储在方法区的静态区。
静态属性和静态方法属于类信息,在类加载时就会在内存分类空间和初始化;类的非静态属性属于具体对象,因此只有在创建具体对象时才会为非静态属性在堆上分配空间和初始化,这也解释了为什么静态方法不能访问非静态属性。
类加载器分类和作用
类加载器分为四大类,他们协作起来对类进行加载,下图是使用类加载器默认加载java类的过程。
四种类加载器负责加载不同类:
启动类加载器: 加载<JAVA_HOME>\lib目录的下jar包中类,这些类是java的核心类,属于JVM层面,开发人员无法访问,一个JVM只有一个。
扩展类加载器: 加载<JAVA_HOME>\lib\ext目录下jar包中的扩展类,属于应用层面,开发人员可以访问,一个JVM一个。
应用类加载器: 加载classpath目录下类,项目工程中开发人员编写的类,应用层面,开发人员可以访问,一个jVM只有1个。
自定义类加载器: 用户自定类加载器,可以定义多个。
默认的类加载器遵循双亲委派机制进行加载,自底向下检查类是否加载,自顶向下尝试对类进行加载,优点是保护核心类不会被篡改。
为何要打破双亲委派机制
对同一个类,在java核心存在,但同时希望使用自定义的同一个类时,需要打破双亲委派机制,类的加载是通过类加载器中findClass方法实现,双亲委派机制是通过loadClass方法实现,同时重写ClassLoader中的findClass和LoadClass方法可以打破默认的双亲委派机制。
public class MyClassLoader extends SecureClassLoader {
private String classPath;
public MyClassLoader(String classPath){
this.classPath = classPath;
}
@Override
public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
System.out.println(name +" ->开始加载");
Class<?> res;
if("TV".equals(name)){//过滤需要打破双亲委派加载的类
res = this.findClass(name);
}
res = super.loadClass(name,resolve);
System.out.println(name+"加载完成");
return res;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// if ()
//去读要加载类的字节码文件,并转化为byte[]数组
String filePath = this.classPath+name.replace(".","\\")+".class";
ByteArrayBuffer buffer = new ByteArrayBuffer();
byte [] b = null;
FileInputStream fis;
int code = 0;
try {
fis = new FileInputStream(new File(filePath));
while((code = fis.read()) != -1){
buffer.write(code);
}
b = buffer.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, b, 0, b.length);
}
}
打破双亲委派的例子
例如JDBC中的Driver,不同的数据库厂商提供得了连接自己数据库的不同Driver实现,JDBC需要打破双亲委派机制实现对不同数据库厂商Driver实现类加载。tomcat加载webapps中的不同应用,不同应用之间可能存在类重名或者版本不一致的情况,需要打破双亲委派实现不同应用加载类之间的隔离,使用不同加载器加载的类是不相等的,不相等值的是equals不相等,isitanceof不相等。
思考和疑问
在使用自定义类加载器加载类时,对执行的类加载顺序比较疑惑,自己的原有理解是,当要加载一个类,首先需要加载它的父类,然后还需要加载类中使用到的类,最后才完成当前类的加载。下面是执行的自定义类加载器类加载演示和实际运行结果。
//测试类
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\GuCheng\\IdeaProjects\\MyClassLoader\\");
watchTV(myClassLoader);
}
public static void watchTV(ClassLoader classLoader) throws Exception {
Class<?> clazz= classLoader.loadClass("TV");
Object object = clazz.newInstance();
clazz.getMethod("play",null).invoke(object,null);
}
}
//待加载类
public class TV {
public void play(){
System.out.println("猫了老鼠");
}
}
对输出结果的中黄色框内容有疑惑,期待知道的大佬解释下。