引言
目前工作上大部分都是springboot项目,启动的时候都只需要简单地运行main方法就可以进行启动了。不过当我们用java -jar命令运行启动类的main方法进行启动时,对应的类加载机制以及运行过程是怎么样的呢? (●ˇ∀ˇ●) 容我娓娓道来
java类的加载运行过程
当我们用java命令运行main方法时,类加载器会将主类优先加载至JVM中找不到或无法加载主类,在主类的运行过程中,类加载器会根据主类的需要,逐步的将使用到的类加载至JVM中。
注意:java中的类并不是在jar包启动时全部加载,而是在主类的运行过程中,使用到才会进行加载,很重要
java类的加载到执行过程大概分为以下几步:
加载:通过io流的方式读取硬盘上的class文件,并且将mian方法以及main方法中使用到的类进行加载,并验证class文件中的字节码是否正确准备:给类的静态变量分配内存,并赋予默认值(看到这个就知道静态变量的加载优先级了O(∩_∩)O),例如int类型默认值为0找不到或无法加载主类,对象的默认值为null等解析:该阶段会将静态方法名替换为指向数据存储的指针,即静态链接过程初始化:对类的静态变量初始化为指定的值,并执行静态代码块
类的整体加载流程如下:
加载并验证--->准备---> 解析 --->初始化---> 使用----> 卸载
java类加载器以及其双亲委派机制启动类加载器(使用的是C语言写的,java中不可见):负责加载jvm运行时需要的核心包,比如javaws.jar、rt.jar,java.lang.String、Long这些java类都是此加载器进行加载扩展类加载器(ExtClassLoader):负责加载加载jvm运行的扩展包,主要是我们jre/ext文件夹下面的包,这些包应该是java的开发人员当年不敢改动核心包(开发扩展包和核心包的开发人员估计是两个部门的吧吧哈哈哈),又必须加扩展功能时,扩展出来的包应用类加载器(AppClassLoader):这个就主要是负责加载我们写的那些业务代码啦,加载的是项目classpath目录下的class文件自定义加载器:当以上的三种加载器满足不了我们的要求的时候(比如加载的class文件不在classspath目录下),就需要自定义类加载器来加载了
//加载器示例
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
双亲委派机制效果图
java类加载是存在一种亲子结构的,专业属于双亲委派机制,即类加载器需要加载某个类时,会不断的委托给自己的父类进行类加载工作,直到找到此工作委托给最顶级的类(不存在父类),此时才会开始正式加载类,当顶级类加载不了对应的类后,会不断的委托给自己的子类进行加载,直到加载到目标类为止。语言比较干涩,通过AppClassLoader的源码(省略了和机制无关的代码)来解释一下就清楚了:
//ClassLoader的loadClass方法,里面实现了双亲委派机制
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查当前类加载器是否已经加载了该类
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //如果当前加载器父加载器不为空则委托父加载器加载该类
c = parent.loadClass(name, false);
} else { //如果当前加载器父加载器为空则委托引导类加载器加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {}
if (c == null) {
//这里可以看到,如果根加载器和父加载器都无法加载目标类时,就会在当前classpath下寻找目标类
c = findClass(name);
}
}
if (resolve) { //不会执行
resolveClass(c);
}
return c;
}
}
保证沙箱安全:java的核心类库不会被修改,保证了核心api功能的正常避免类重复加载:父加载器加载过的内容,子加载器无需加载,保证了类的唯一性自定义类加载器
如果需要自定义类加载器需要继承Classloader类,可以重写findClass(可以自定义从指定的路径读取类文件)、loadClass(可以定义类加载方式,Classloader中的loadclass方法遵循的是双亲委派机制,如果需要打破此机制,需要重写这个方法)
下图中我自定义了loadclass方法,成功加载了非本地classpath下的class文件
自定义classloader
使用自定义classloader加载class
现在我们开始打破双亲委派机制,即使当前项目的classpath存在此class,还是加载指定目录下的class文件,如下图:
自定义loadclass方法
当前classpath下User.class内容
打破双亲委派机制效果图
版权声明
本文仅代表作者观点。
本文系作者授权发表,未经许可,不得转载。
发表评论