Java类加载机制详解

类的加载过程

在使用java命令运行主类(main)的时候,首先要经过类加载器将类加载到JVM内存中去。主类在运行过程当中若是用到其余的类就会逐步加载这些类。jar包里的类并非一次性加载的,是使用的时候才加载的。java

类加载过程分为如下几步:web

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

一、加载:在硬盘上经过IO读入字节码文件,使用到类的时候才会加载,例如调用main()方法,new对象等等。数组

二、验证:校验字节码文件的正确性tomcat

三、准备:给类的静态变量分配内存,而且赋予默认值安全

四、解析:将符号引用替换为直接引用,该阶段会把一些静态方法替换为指向数据所存内存的地址指针或句柄,即静态连接过程(类加载期间完成)。动态连接是在程序运行期间完成的,将符号引用替换为直接引用。服务器

五、初始化:对类的静态变量初始化为指定值,执行静态代码块。app

 双亲委派机制

双亲委派机制就是在加载某个类的时候会先委托父类加载器(爸爸)加载,父类加载器就会继续委托其父类加载器(爷爷辈)加载,若是全部父类加载器都没有找到目标类,则在本身的加载类路径中查找目标类进行加载。简言之就是有事情作的时候,先交给爸爸作,爸爸作不了就交给爷爷作,爷爷也作不了就只能本身作了。也就是拼爹机制。jvm

  假设你本身写了一个类Test.当在加载Test的时候,应用程序类加载器会委托扩展类加载器加载,扩展类加载器会委托启动类加载器加载,启动类加载器在加载路径中没法找到Test类,就退回给扩展类加载器,扩展类加载器收到回复,就在本身的加载类路径中查找该类,找不到就退回给应用程序加载器加载。应用程序类在加载类路径中查找要加载的类进行加载。jsp

为何要设置双亲委派机制呢:

一、避免重复加载

二、沙箱安全机制,避免核心API库被篡改。(好比你本身写了一个String类,是不会加载的)

 自定义加载器

要实现自定义加载器只须要继承ClassLoader类,该类有两个核心方法,

  • loadClass(String, boolean),实现了双亲委派机制,大致逻辑
  1. 首先,检查一下指定名称的类是否已经加载过,若是加载过了,就不须要再加载,直接返回。
  2. 若是此类没有加载过,那么,再判断一下是否有父加载器;若是有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类(启动类)加载器来加载。
  3.  若是父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器
    的findClass方法来完成类加载。还有一个方法是findClass,默认实现是抛出异常,因此咱们自定义类加载器主要是重写
  • findClass方法。
 public class MyClassLoaderTest {
   static 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 len = fis.available();
     byte[] data = new byte[len];
     fis.read(data);
     fis.close();
     return data;
 }

 protected Class<?> findClass(String name) throws ClassNotFoundException{
   try {
     byte[] data = loadByte(name);
     //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终
    的字节数组。
     return defineClass(name, data, 0, data.length);
     } catch (Exception e) {
       e.printStackTrace();
     throw new ClassNotFoundException();
     }
    }

 }

 public static void main(String args[]) throws Exception {
     MyClassLoader classLoader = new MyClassLoader("D:/test");
     Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
     Object obj = clazz.newInstance();
     Method method= clazz.getDeclaredMethod("sout", null);38 method.invoke(obj, null);
     System.out.println(clazz.getClassLoader().getClass().getName());
   }
 }

 运行结果:
 =======本身的加载器加载类调用方法=======
 com.tuling.jvm.MyClassLoaderTest$MyClassLoader

 

 打破双亲委派机制

在如下几种状况下须要打破双亲委派机制:

一、同一个容器里面部署多个应用,这几个应用都依赖于同一个第三方类库的不一样版本

二、多个应用共享同一个版本的类库。

三、容器依赖的类库与应用程序的类库分开

Tomcat自定义加载器

Tomcat是个web容器, 那么它要解决什么问题: 
1. 一个web容器可能须要部署两个应用程序,不一样的应用程序可能会依赖同一个第三方类库的不一样版本,不能要求同一个类库在同一个服务器只有一份,所以要保证每一个应用程序的类库都是独立的,保证相互隔离。 
2. 部署在同一个web容器中相同的类库相同的版本能够共享。不然,若是服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,这是扯淡的。 
3. web容器也有本身依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。 
4. web容器要支持jsp的修改,咱们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已是司空见惯的事情,不然要你何用? 因此,web容器须要支持 jsp 修改后不用重启。

Tomcat 若是使用默认的类加载机制行不行? 
答案是不行的。为何?

一、若是使用默认的类加载器机制,那么是没法加载两个相同类库的不一样版本的,默认的累加器是无论你是什么版本的,只在意你的全限定类名,而且只有一份。

二、默认的类加载器是可以实现的,由于他的职责就是保证惟一性。

三、同1。

四、咱们想咱们要怎么实现jsp文件的热修改,jsp 文件其实也就是class文件,那么若是修改了,但类名仍是同样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会从新加载的。那么怎么办呢?咱们能够直接卸载掉这jsp文件的类加载器,因此你应该想到了,每一个jsp文件对应一个惟一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。从新建立类加载器,从新加载jsp文件。

 

 

 

CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat本身定义的类加载器,它们分别加载/common/*/server/*/shared/*(在tomcat 6以后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器一般会存在多个实例,每个Web应用程序对应一个WebApp类加载器,每个JSP文件对应一个Jsp类加载器。

  • commonLoader:Tomcat最基本的类加载器,加载路径中的class能够被Tomcat容器自己以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于全部Webapp可见,可是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

从图中的委派关系中能够看出:

  • CommonClassLoader能加载的类均可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和Shared ClassLoader本身能加载的类则与对方相互隔离。
  • WebAppClassLoader可使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。
  • JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并经过再创建一个新的Jsp类加载器来实现JSP文件的HotSwap功能。
相关文章
相关标签/搜索