java代码执行流程:
现有一个Math.java类:
public class Math {
public int compute() {
int a = 1;
int b = 2;
return a+b;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
1、首先java.exe会调用jvm.dll文件(相当于jar包) 创建JVM虚拟机(由c++实现),虚拟机创建引导类加载器实例(C++实现)。
2、引导类加载器会去加载一个sum.misc.Launcher类实例,JVM会通过Launcher类去创建其他类加载器,Math类是由AppClassLoader加载,所以,通过Launcher.getClassLoader()拿到AppClassLoader实例去加载Math(appClassLoader.loadClass(“Math.java”))
3、加载完成后JVM会调用Math.java类的main方法入口,然后java程序结束,JVM销毁。
loadClass的类加载过程:
加载:在硬盘上查找并通过IO读入字节码文件,在内存中生成一个代表这个类的Class对象
jar包或war包里的类使用时才会加载,例如调用类的main()方法,new对象等
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解析:把一些静态方法替换为指向数据所存内存的指针等(直接引用),是静态链接(动态链接是在程序运行期间完成的将符号引用替换为直接引用)。
初始化:对类的静态变量初始化为指定的值,执行静态代码块
类加载器种类
引导类加载器:加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如charsets.jar
扩展类加载器:加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR包
应用程序类加载器:加载ClassPath路径下的类包,就是加载自己写的那些类
自定义加载器:加载自定义路径下的类包
类加载器初始化过程
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//构造扩展类加载器,在构造的过程中将其父加载器设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
//Launcher的loader初始化属性值是AppClassLoader,
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
}
引导类加载器bootstrapLoader是C++实现,不用关注
初始化Launcher类的时候,Launcher构造方法内部,会构造两个类加载器,分别是ExtClassLoader(扩展类加载器)和AppClassLoader(应用程序类加载器)。AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器为null
双亲委派机制
概念:加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
通俗来讲就是,先找父类加载,不行再由自己加载
比如刚才的Math类,源码中Launcher中loader初始化值是AppClassLoader,所以会最先找AppClassLoader加载,AppClassLoader会调用loadClass方法,loadClass中首先有一个C++的findLoadedClass方法,在已经加载过的类中找Math,没有的话委托父加载器ExtClassLoader加载,然后ExtClassLoader找已加载的Math,(此时ExtClassLoader的父加载器为null),如过没找到,再委托引导类加载器,引导类加载器在自己的类加载路径里没找到Math类,则向下退回加载Math类的请求,ExtClassLoader收到回复就自己加载,在自己的类加载路径里没找到Math类,又向下退回Math类的加载请求给AppClassLoader,AppClassLoader在自己的类加载路径里找到了Math类,于是通过父类URLClassLoader的findClass方法找到对应的类路径文件,IO到JVM内存中。
为什么有双亲委派机制
避免类的重复加载:当父加载器已经加载了该类时,就没有必要子加载器重复加载,保证被加载类的唯一性
沙箱安全机制:当父加载器已经加载了该类时,就直接返回该类,不会被下级类加载器重复加载,防止核心API库被篡改
全盘负责委托机制:当一个ClassLoder装载一个类时,除非指定其他ClassLoder,该类所依赖及引用的类也由当前ClassLoder载入。
如何实现自定义类加载器
自定义类加载器只需要继承 ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以主要是重写findClass方法就可以实现自定义类加载器。
如何打破双亲委派机制
最简单的实现方法就是在自定义类加载器中,重写loadClass方法,删除其中双亲委派机制的代码的代码(就是判断由父类加载的代码)。
如果自定义类加载器中依赖了JDK的核心类库,会报SecurityException,此错误是沙箱安全机制导致,当加载JDK核心类库时指定用父加载器去加载就能解决该问题
Tomcat打破双亲委派机制
Tomcat为何要打破双亲委派机制?
1、一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,所以同一类库在同一容器中不能只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
2、部署在同一个web容器中相同的类库相同的版本要可共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
3、web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,容器的类库和程序的类库应该隔离开。
4、web容器需要支持 jsp 修改后不用重启。
Tomcat如何打破双亲委派机制?
tomcat的几个主要类加载器:
commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个WebApp访问;
catainaLoader:Tomcat容器私有的类加载器,加载路径中的class对于WebApp不可见;
sharedLoader:各个WebApp共享的类加载器,加载路径中的class对于所有WebApp可见,但是对于Tomcat容器不可见;
WebappClassLoader:每个war包应用都有自己的WebappClassLoader,加载路径中的class只对当前WebApp可见==
上图的委派关系可以看出:
1、CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,实现了公有类库的共用。
2、CatalinaClassLoader和SharedClassLoader加载各自的类,实现了Tomcat和WebApp的相互隔离。
3、WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个应用之间相互隔离,实现了同一类库不同版本的隔离。
4、JasperLoader只加这个JSP文件所编译出来的一个Class文件,当Tomcat检测到JSP文件被修改时,会丢弃当前的JasperLoader的实例,并重新建立一个新的Jsp类加载器,实现JSP文件的热加载功能。