**加载 >> 验证 >> 准备 >> 解析 >> 初始化** >> 使用 >> 卸载
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如
main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过
程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化:对类的静态变量初始化为指定的值,执行静态代码块
静态链接:main方法中调用hello静态方法#7就属于符号引用,在常量池中可以找到,类加载 时,该符号引用会被换成jmm内存中的地址
动态链接:就是普通方法的符号引用在程序运行期间换成内存地址
Constant pool:
#1 = Methodref #8.#24 // java/lang/Object."":()V
#2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #27 // test
#4 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #30 // com/demo/Test
#6 = Methodref #5.#24 // com/demo/Test."":()V
#7 = Methodref #5.#31 // com/demo/Test.hello:()V
#8 = Class #32 // java/lang/Object
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class com/demo/Test
3: dup
4: invokespecial #6 // Method "":()V
7: astore_1
8: aload_1
9: pop
10: invokestatic #7 // Method hello:()V
引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如
rt.jar、charsets.jar等
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR
类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那
些类
自定义加载器:负责加载用户自定义路径下的类包
继承关系:扩展、应用类加载器都继承URLClassLoader
而这三个类加载其并不是真正的父子关系,持有关系,可以看下顶层ClassLoader中有parent属性
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//首先创建扩展类加载器,它的父类加载器即他的parent属性因是引导类加载器是c++实现的,此处 没有传参
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//创建应用类加载器,并把扩展类加载器当参数,即应用类加载器parent属性是扩展类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//剩下代码此处省略
}
加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类![在这里插入图片描述](https://img-blog.csdnimg.cn/20210410144335961.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTkwNjU0,size_16,color_FFFFFF,t_70#pic_center)
双亲委派机制优点:
沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心
API库被随意篡改
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一
次,保证被加载类的唯一性
加载过程:Launcher类中,loader.loaderClass(“xx.xx”)
public Class<?> loadClass(String var1) throws ClassNotFoundException {
return this.loadClass(var1, false);
}
protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(var1)) {
//先看有没有被加载过,有直接返回加载过的
Class var4 = this.findLoadedClass(var1);
if (var4 == null) {
long var5 = System.nanoTime();
try {
//先从父类中获取,这是个递归
if (this.parent != null) {
var4 = this.parent.loadClass(var1, false);
} else {
var4 = this.findBootstrapClassOrNull(var1);
}
} catch (ClassNotFoundException var10) {
}
if (var4 == null) {
long var7 = System.nanoTime();
//当向下委托,最终是应用类加载器自己去加载自己类
var4 = this.findClass(var1);
PerfCounter.getParentDelegationTime().addTime(var7 - var5);
PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
PerfCounter.getFindClasses().increment();
}
}
if (var2) {
this.resolveClass(var4);
}
return var4;
}
}
看下this.findClass(var1),其实ClassLoader是个空实现,最终调用URLClassLoader中的findClass(var1)
protected Class<?> findClass(final String var1) throws ClassNotFoundException {
Class var2;
try {
var2 = (Class)AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
//拼接类全限定名
String var1x = var1.replace('.', '/').concat(".class");
//根据路径读取该类的字节
Resource var2 = URLClassLoader.this.ucp.getResource(var1x, false);
if (var2 != null) {
try {
//该方法就是加载类,不继续跟进去
return URLClassLoader.this.defineClass(var1, var2);
} catch (IOException var4) {
throw new ClassNotFoundException(var1, var4);
}
} else {
return null;
}
}
}, this.acc);
} catch (PrivilegedActionException var4) {
throw (ClassNotFoundException)var4.getException();
}
if (var2 == null) {
throw new ClassNotFoundException(var1);
} else {
return var2;
}
}
其实从上面可以看出,双亲委派在loadClass方法中实现,我们自定义类加载器只需重写该方法即可
package com.demo;
import sun.misc.PerfCounter;
import java.io.FileInputStream;
import java.lang.reflect.*;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name)throws Exception{
name=name.replaceAll("\\.","/");
FileInputStream fis=new FileInputStream(classPath+"/"+name+".class");
int length=fis.available();
byte[] data= new byte[length];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data=loadByte(name);
return defineClass(name, data, 0, data.length);
}catch (Exception e){
e.printStackTrace();
}
return super.findClass(name);
}
@Override
protected Class<?> loadClass(String name, boolean b) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(name)) {
Class var4 = this.findLoadedClass(name);
if (var4 == null) {
//该类有当前类加载器加载,其余还是交给父类加载器,负责会报错因为加载Object不允许被私有类加载器加载
if(name.equals("com.demo.User")){
var4 = this.findClass(name);
}else{
var4= this.getParent().loadClass(name);
}
}
return var4;
}
}
public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader=new MyClassLoader("/home/kangcc/Desktop");
Class<?> clazz=myClassLoader.loadClass("com.demo.User");
Object obj=clazz.newInstance();
System.out.println(clazz.getClassLoader());
}
}
看源码时候存在的疑惑:
自定义类加载器parent是不是AppClassLoader?
首先自定义类加载器继承ClassLoader,自定义类加载器被加载时,ClassLoader也会被加载初始化,看下父类中parent属性是不是应用类加载器
子类被加载为啥也会加载父类(虽然这个问题知道,但是还是想跟着源码看下):
demo如下
首先Animal是父类
People是子类
首先我加载people类跟进去看看,此时已经完成people类的加载
继续看763以后代码:可以看底层自动加载父类
是那个类调用去加载父类的继续看下,可以看出是本地地方,即底层应该找出父类并加载