# JVM类加载机制

一、JVM启动流程

  1. 调用Java -jar,启动本Java项目,会先调用java.exe函数;
  2. java.exe调用底层jvm.dll(C++)函数库,创建java虚拟机(C++)实现;
  3. jvm.dll创建一个引导类加载器实例(C++实现);
  4. 引导类加载器加载资源,(C++调用)创建一个JVM启动器sun.misc.Launcher,并创建其他类加载器;
  5. JVM启动器通过调用getLauncher()方法获取运行类自己的加载器
  6. app类加载器的loadClass方法加载对应的main方法入口类,整个Java项目启动;

二、类加载器

  1. 引导类加载器:加载支撑JVM运行的卫浴JRE的lib目录下的核心类库,如rt.jar、charsets.jar等;
  2. 扩展类加载器:加载支撑JVM运行的卫浴JRE的lib目录下的ext扩展目录中的JAR类包;
  3. 应用类加载器:加载ClassPath路径下的类包,主要加载自己写的类;
  4. 自定义类加载器:加载用户自定义路径下的类包;

类加载器初始化过程:启动过程中创建引导类加载器,引导类加载器创建sun.misc.Launcher即为创建JVM启动类,Launcher创建扩展类加载器(其父类加载器为null,因为引用类加载器是C++创建的),Launcher创建应用类加载器,并设置父类加载器为扩展类加载器;

三、类加载过程(对应JVM启动当中的loadClass()方法)

加载-验证-准备-解析-初始化-使用-卸载

  1. 加载:查找字节码文件并通过IO读入到内存,生成一个java.lang.Class对象,作为方法区这个类的各种数据的访问入口,在运行过程中并不是一次性全部加载,是使用到才会加载,逐步加载这些类;

  2. 验证:校验字节码文件的正确性;

  3. 准备:给类的静态变量分配内存,并赋予默认值;

  4. 解析:将符号引用替换为直接引用;

    1. 静态连接:把静态方法替换为指向数据所存内存的指针活句柄等(直接饮用)
    2. 动态连接:在程序运行期间完成将符号殷红替换为直接引用
  5. 初始化:对类的静态变量初始化为指定的值,执行静态代码块;

  6. 备注:

    1. 类被加载到方法区中主要内容:

      运行时常量池、类型信息、字段信息、方法信息、类加载器引用、对应class实例的引用等

    2. 对应class实例的引用:类加载器在加载类信息放到方法区中后,回创建一个对应的class类型的对象实例放到堆中,作为开发人员访问方法区中类定义的入口和切入点,并非是直接引用方法区中的信息;

四、双亲委派机制

  1. 先通过当前类加载器查找是否已经加载过,如果有则返回,没有则调用父类加载器;
  2. 父类加载器查看当前类路径下是否已经加载过,没有则接着调用父类,直到没有父类加载器,则查找引导类加载器,并查看引导类加载器是否已经加载过,如果没有则查找此类;
  3. 先从引导类加载器的类路径下查找是否有此类的class文件,如果有则加载,如果没有则向下委托,委托扩展类加载器,有则返回,没有则继续向下委托,直到查找到,并加载返回;
  4. 设计双亲委派机制的原因:
    1. 沙箱安全机制:用户自己定义的和系统类路径相同的class不会被加载,这样可以防止核心Api库被随意篡改;
    2. 避免类的重复加载:当父加载器已经加载了该类是,子加载器就无需加载,保证被加载类的唯一性;
  5. 全盘负责委托机制:指当一个ClassLoader装载一个类时,除非显式的使用另外一个ClassLoder,否则该类所依赖及引用的类也由这个ClassLoder载入;

五、自定义类加载器

  1. 继承java.lang.Classloader类,该类有两个核心方法,一个是loadClass(string,boolean),实现了双亲委派机制,另一个是findClass,默认实现是空方法,自定义类加载器,主要是重写findClass方法;

六、打破双亲委派机制

  1. 重写loadClass方法,按自己的逻辑去实现则可以打破;

七、Tomcat打破双亲委派机制

  1. 结果导向:
    1. 不同应用依赖不同的类库,或者依赖同一个第三方类库的不同版本,要保证相互隔离;
    2. 部署在同一个web容器中相同的类库相同的版本可以共享;否则每个对应版本都需要加载到虚拟机;
    3. web容器有自己的类库,不能与应用程序类库混淆,且为了保持安全性,应该相互隔离;
    4. web容器要支持jsp的修改,jsp文件被编译成class文件运行在虚拟机中,后续修改web容器需要支持jsp修改后不用重启;
  2. 原因:
    1. 如果使用默认的类加载器机制,无法加载两个相同类库的不同版本,默认的类加载器通过全限定类名加载,并且只有一份;
    2. 默认的类加载器可以实现,他的职责就是保持唯一性;
    3. 和第一个问题一样;
    4. 如何实现jsp文件热加载,jsp文件就是class文件,如果修改了,但类名还是一样,类加载器回直接去方法区中已经存在的,修改后的jsp不会被加载;解决方法是直接卸载掉这个jsp问价的类加载器,所以每个jsp文件对应一个唯一的类加载器,当jsp文件被修改,就卸载掉对应的类加载器,重新创建类加载器,并加载jsp文件;
  3. tomcat的几个主要类加载器;
    1. commonClassLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
    2. catalinadClassLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见,实现对app隔离;
    3. sharedClassLoader:各个Webapp共享的类加载器,加载路径中的class热与所有Webapp可见,但是对于Tomcat容器不可见,实现app共享,但是tomcat隔离;
    4. WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,例如war包里相关的类,实现相互隔离;
  4. tomcat的类加载机制打破了java的双亲委派机制,实现了隔离性,webappClassLoader加载自己目录下的class文件不会传递给父类加载器打破双亲委派机制;
  5. 备注:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,多一看两个类对象是否是同一个,除了类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才认为他们是同一个;