# JVM类加载机制
一、JVM启动流程
- 调用Java -jar,启动本Java项目,会先调用java.exe函数;
- java.exe调用底层jvm.dll(C++)函数库,创建java虚拟机(C++)实现;
- jvm.dll创建一个引导类加载器实例(C++实现);
- 引导类加载器加载资源,(C++调用)创建一个JVM启动器sun.misc.Launcher,并创建其他类加载器;
- JVM启动器通过调用getLauncher()方法获取运行类自己的加载器
- app类加载器的loadClass方法加载对应的main方法入口类,整个Java项目启动;
二、类加载器
- 引导类加载器:加载支撑JVM运行的卫浴JRE的lib目录下的核心类库,如rt.jar、charsets.jar等;
- 扩展类加载器:加载支撑JVM运行的卫浴JRE的lib目录下的ext扩展目录中的JAR类包;
- 应用类加载器:加载ClassPath路径下的类包,主要加载自己写的类;
- 自定义类加载器:加载用户自定义路径下的类包;
类加载器初始化过程:启动过程中创建引导类加载器,引导类加载器创建sun.misc.Launcher即为创建JVM启动类,Launcher创建扩展类加载器(其父类加载器为null,因为引用类加载器是C++创建的),Launcher创建应用类加载器,并设置父类加载器为扩展类加载器;
三、类加载过程(对应JVM启动当中的loadClass()方法)
加载-验证-准备-解析-初始化-使用-卸载
加载:查找字节码文件并通过IO读入到内存,生成一个java.lang.Class对象,作为方法区这个类的各种数据的访问入口,在运行过程中并不是一次性全部加载,是使用到才会加载,逐步加载这些类;
验证:校验字节码文件的正确性;
准备:给类的静态变量分配内存,并赋予默认值;
解析:将符号引用替换为直接引用;
- 静态连接:把静态方法替换为指向数据所存内存的指针活句柄等(直接饮用)
- 动态连接:在程序运行期间完成将符号殷红替换为直接引用
初始化:对类的静态变量初始化为指定的值,执行静态代码块;
备注:
类被加载到方法区中主要内容:
运行时常量池、类型信息、字段信息、方法信息、类加载器引用、对应class实例的引用等
对应class实例的引用:类加载器在加载类信息放到方法区中后,回创建一个对应的class类型的对象实例放到堆中,作为开发人员访问方法区中类定义的入口和切入点,并非是直接引用方法区中的信息;
四、双亲委派机制
- 先通过当前类加载器查找是否已经加载过,如果有则返回,没有则调用父类加载器;
- 父类加载器查看当前类路径下是否已经加载过,没有则接着调用父类,直到没有父类加载器,则查找引导类加载器,并查看引导类加载器是否已经加载过,如果没有则查找此类;
- 先从引导类加载器的类路径下查找是否有此类的class文件,如果有则加载,如果没有则向下委托,委托扩展类加载器,有则返回,没有则继续向下委托,直到查找到,并加载返回;
- 设计双亲委派机制的原因:
- 沙箱安全机制:用户自己定义的和系统类路径相同的class不会被加载,这样可以防止核心Api库被随意篡改;
- 避免类的重复加载:当父加载器已经加载了该类是,子加载器就无需加载,保证被加载类的唯一性;
- 全盘负责委托机制:指当一个ClassLoader装载一个类时,除非显式的使用另外一个ClassLoder,否则该类所依赖及引用的类也由这个ClassLoder载入;
五、自定义类加载器
- 继承java.lang.Classloader类,该类有两个核心方法,一个是loadClass(string,boolean),实现了双亲委派机制,另一个是findClass,默认实现是空方法,自定义类加载器,主要是重写findClass方法;
六、打破双亲委派机制
- 重写loadClass方法,按自己的逻辑去实现则可以打破;
七、Tomcat打破双亲委派机制
- 结果导向:
- 不同应用依赖不同的类库,或者依赖同一个第三方类库的不同版本,要保证相互隔离;
- 部署在同一个web容器中相同的类库相同的版本可以共享;否则每个对应版本都需要加载到虚拟机;
- web容器有自己的类库,不能与应用程序类库混淆,且为了保持安全性,应该相互隔离;
- web容器要支持jsp的修改,jsp文件被编译成class文件运行在虚拟机中,后续修改web容器需要支持jsp修改后不用重启;
- 原因:
- 如果使用默认的类加载器机制,无法加载两个相同类库的不同版本,默认的类加载器通过全限定类名加载,并且只有一份;
- 默认的类加载器可以实现,他的职责就是保持唯一性;
- 和第一个问题一样;
- 如何实现jsp文件热加载,jsp文件就是class文件,如果修改了,但类名还是一样,类加载器回直接去方法区中已经存在的,修改后的jsp不会被加载;解决方法是直接卸载掉这个jsp问价的类加载器,所以每个jsp文件对应一个唯一的类加载器,当jsp文件被修改,就卸载掉对应的类加载器,重新创建类加载器,并加载jsp文件;
- tomcat的几个主要类加载器;
- commonClassLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
- catalinadClassLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见,实现对app隔离;
- sharedClassLoader:各个Webapp共享的类加载器,加载路径中的class热与所有Webapp可见,但是对于Tomcat容器不可见,实现app共享,但是tomcat隔离;
- WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,例如war包里相关的类,实现相互隔离;
- tomcat的类加载机制打破了java的双亲委派机制,实现了隔离性,webappClassLoader加载自己目录下的class文件不会传递给父类加载器打破双亲委派机制;
- 备注:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,多一看两个类对象是否是同一个,除了类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才认为他们是同一个;