jadestoner

导航

 

类加载机制:

主要参考文章:https://www.cnblogs.com/aspirant/p/7200523.html

主要知识点:

1.类加载器的作用,类型及其各自特点

  作用:加载类;唯一确定类。

       JVM类加载机制:

                  1.关联加载;2.双亲委派;3.优先缓存

  类型:

                 1. 启动类加载器;2.拓展类加载器;3.应用类加载器;4.自定义加载器

       各加载器特点:

1.启动类加载器,Bootstrap ClassLoader,加载JACA_HOME\lib(dt.jar,tools.jar),或者被-Xbootclasspath参数限定的类

2.扩展类加载器,Extension ClassLoader,加载\lib\ext,或者被java.ext.dirs系统变量指定的类
3.应用程序类加载器,Application ClassLoader,加载ClassPath中的类库

  补充:我们正常配置【.:JAVA_HOME/jre/lib/rt.jar:JAVA_HOME/lib/dt.jar:JAVA_HOME/lib/tools.jar】,

    3.1. 开头的【.】代表了当前路径,

    3.2. rt.jar其实大名鼎鼎,runtime的缩写,我们寻常使用的io,collection,thread都在这个里面,其实不需要配在ClassPath中,因为默认就在Root Classloader的加载路径里面的

    3.3. dt.jar是关于运行环境的类库, 一般用不到,swing相关的需要用到

    3.4 tools.jar 是系统用来编译一个类的时候用到的,即执行javac的时候用到

      javac XXX.java
      实际上就是运行
      java -Calsspath=%JAVA_HOME%\lib\tools.jar xx.xxx.Main XXX.java
      javac就是对上面命令的封装 所以tools.jar 也不用加到classpath里面

    3.5. 在Classpath设置这几个变量,是为了方便在程序中 import;Web系统都用到tool.jar。

4.自定义类加载器,通过继承ClassLoader实现,一般是加载我们的自定义类

2. 双亲委派的概念,意义

  概念:所谓双亲委派是指每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的搜索范围中没有找到对应的类),子类尝试自己加载。

  意义:1.避免同一个类被多次加载; 2.每个加载器只能加载自己范围内的类

      方法:defineClass,loadClass,findClass的区别

    loadClass实现了双亲委派的一套体系,会一直向上找合适的类加载器来加载,重写这个方法就可以打破双亲委派,

     findClass:子类重写这个方法,这个方法的意义就是在自己职责范围内找到相关的类路径,然后调用defineClass方法即可完成加载过程,

    defineClass:是native方法,职责是接收class文件的二进制流,完成类加载

ClassLoader定义了该流程的算法骨架:

大概如下:

protected Class loadClass(String className){

Class c = findFromCache(className);   // 先取加载缓存(一般热加载机制会再这一步动手,每次都重新加载,不走缓存,(破坏双亲委派机制))

if(c == null){             // 先委托父类加载

c=parent.loadClass(className);

if(c == null){          // 如果父类不加载,则自己加载

c = findClass(className);

}

}

}

 

 

补充:tomcat的类加载流程

Tomcat中的类加载器

在Tomcat目录结构中,有三组目录(“/common/*”,“/server/*”和“shared/*”)可以存放公用Java类库,此外还有第四组Web应用程序自身的目录“/WEB-INF/*”,把java类库放置在这些目录中的含义分别是:

放置在common目录中:类库可被Tomcat和所有的Web应用程序共同使用。 放置在server目录中:类库可被Tomcat使用,岁所有的Web应用程序都不可见。 放置在shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。 放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,如下图所示

 

灰色背景的3个类加载器是JDK默认提供的类加载器,这3个加载器的作用前面已经介绍过了。而 CommonClassLoader、CatalinaClassLoader、SharedClassLoader 和 WebAppClassLoader 则是 Tomcat 自己定义的类加载器,它们分别加载 /common/*、/server/*、/shared/* 和 /WebApp/WEB-INF/* 中的 Java 类库。其中 WebApp 类加载器和 Jsp 类加载器通常会存在多个实例,每一个 Web 应用程序对应一个 WebApp 类加载器,每一个 JSP 文件对应一个 Jsp 类加载器。

从图中的委派关系中可以看出,CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。而 JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class,它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。

3.类加载的过程:

  类生命周期:加载,连接(验证,准备,解析),初始化,使用,卸载。如下图:

3.1 加载:

  将类的二进制文件流加载到JVM内存,并转化成相对应的Class对象

  补充:加载阶段是类加载过程中可控性最强的阶段。可以使用默认加载器,也可以自定义加载器完成。。加载加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。

3.2 连接:

  3.2.1 验证: 验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证;

  3.2.2 准备: 为静态成员变量分配内存空间,并设置初始值

  3.2.3 解析: 将常量池中所有的符号引用转为直接引用

    常量池:里面存储的该Class文件里的大部分常量的内容。

    符号引用和直接引用:

      符号引用就是字符串,这个字符串包含足够的信息,以供实际使用时可以找到相应的位置。你比如说某个方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息,方法名,方法参数等信息。
      当第一次运行时,要根据字符串的内容,到该类的方法表中搜索这个方法。运行一次之后,符号引用会被替换为直接引用,下次就不用搜索了。直接引用就是偏移量,通过偏移量虚拟机可以直接在该类的内存区域中找到方法字节码的起始位置。

 

3.3 初始化:

  为类变量赋值

  注意事项:1. 初始化非实例化;

       2. 将语句按顺序存放到一个特殊方法中(<clinit>方法,即类/接口初始化方法,该方法只能在类加载的过程中由JVM调用)。

       3. 如果超类还没有被初始化,那么优先对超类初始化,但在<clinit>方法内部不会显示调用超类的<clinit>方法,由JVM负责保证一个类的<clinit>方法执行之前,它的超类<clinit>方法已经被执行。

       4. JVM保证,并发情况下,最终只会有一个线程成功执行类的初始化,完成后通知其他线程,(所以可以利用静态内部类实现线程安全的单例模式,文末简单提一下单例模式存在的问题及解决方案)

  初始化触发时机:

       1.为一个类型创建一个新的对象实例时(比如new、反射、序列化)

       2. 调用一个类型的静态方法时(即在字节码中执行invokestatic指令)

       3. 调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式

       4. 调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)

       5. 初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)

       6. JVM启动包含main方法的启动类时。

4. 自定义类加载器

参看文章:https://www.cnblogs.com/szlbm/p/5504631.html

https://www.cnblogs.com/taojietaoge/p/10269844.html

简单写一下:

What

Why/WHERE/WHEN

参阅:https://blog.csdn.net/u011490072/article/details/81560295

总结:1. 了解完类加载器的作用范围后就不难发现,有的class文件的位置可能不在CLASSPATH下,甚至不在本地环境中,这是就需要自定义ClassLoader来加载类了。

    2. 可以自定义类的实现机制,用于对class文件的加解密,防止代码泄露;类的热加载等场景

How

0. 继承ClassLoader类

1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可

2、如果想打破双亲委派模型,那么就重写整个loadClass方法

   2.1 补充:ClassLoader提供了模板方法protected Class<?> loadClass(String name, boolean resolve);  在方法里定义了双亲加载的算法骨架,子类只需要实现findClass方法即可;

   2.2 补充:今天看ClassLoader的loadClass方法代码,发现了一个很巧妙的地方,与大家分享一下:

     自定义多层次的类加载器时,如果父类找不到,及时的且必须抛出ClassNotFoundException

     View Code

3. 可使用 defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class----只要二进制字节流的内容符合Class文件规范。

    3.1 补充:defineClass 是父类(ClassLoader)的final方法,底层调用的是native方法。定义如下:  protected final Class<?> defineClass(String name, byte[] b, int off, int len);

    一般用法为:自定义类加载器实现ClassLoader类,如果不想打破双亲委派模型,那么只需要重写findClass方法即可,可以从数据库,从网络等路径获取到class的字节码数据,

          在findClass方法的最后,调用defineClass即可。

 5. JAVA热部署

热部署只是自定义类加载器的一个应用分支,这里谈论一下,加深对类加载器的理解。

参看文章:https://blog.csdn.net/qq_21508059/article/details/81843498

前提概念:

  1.同一个类名,同一个类加载器实例加载的,代表是同一个类

 

How:

  0.不能使用默认的类加载实例,因为同一个类加载器实例加载一次后会存起来,后面的class文件就算更新了也不会再次加载了。

  1. 使用定时器或者利用线程无限循环不断地创建ClassLoader来loadClass,缺点是即使文件不变更也会重新加载,性能浪费

  2. 利用nio监控,如果有文件发生了变化则去重新加载之。

Note:当使用eclipse等IDE编辑时,会有自动保存/编译这种东西

posted on 2019-01-08 16:53  jadestoner  阅读(260)  评论(0编辑  收藏  举报