跟着锋哥学Java

深入浅出JVM(二)之类加载器

1.类装载器(ClassLoader)

 1. 负责加载class文件,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,ClassLoader只负责class文件的加载

 至于它是否可以运行,则由ExecutionEngine(执行引擎)决定 ,class文件在文件开头有特定的文件标示(32字节用16进制表示是"cafebabe")

 2.类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。

1.1类装载器ClassLoader分类

虚拟机自带的加载器分为启动类加载器,扩展类加载器和应用程序类加载器

 

1.1.2启动类加载器

启动类加载器((Bootstrap)C++)也叫根加载器,是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用

2.1.3扩展类加载器

扩展类加载器(ExtensionClassLoader)是用JAVA编写,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包或者由java.ext.dirs系统属性指定的jar包.

2.1.3应用程序类加载器

应用程序类加载器(System classLoader):又称为系统类加载器,主要用于加载CLASSPATH路径下我们自己写的类,它的父加载器为Ext ClassLoader

2.2.loadClass的类加载过程

其中loadClass的类加载过程有如下几步:加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载

   

1.2.1加载(Loading)

 1.通过一个类的全限定名获取定义此类的二进制字节流
    2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    3.在内存中生成一个代表这个类的java.lang.Class对象(此对象在堆中),作为方法区这个类的各种数据的访问入口

   4.注意:主类在运行过程中如果使用到其它类,会逐步加载这些类。jar包或war包里的类不是一次性全部加载的,是使用到时才加载。

    *补充:加载.c1ass文件的方式

  ·从本地系统中直接加载
  ·通过网络获取,典型场景:Web Applet
  ·从zip压缩包中读取,成为日后jar、war格式的基础
  ·运行时计算生成,使用最多的是:动态代理技术
  .由其他件生成,典型场景:JSP应用
  ·从专有数据库中提取.class文件,比较少见
  ·从加密文件中获取,典型的防class文件被反编译的保护措施

1.2.1验证Verify)

  目的在于校验字节码文件的正确性,主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

 1.2.2准备(Prepare)

  给类的静态变量分配内存,并赋予默认值, 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

1.2.3解析(Resolve)

  将常量池内的符号引用转换为直接引用的过程。

   1.符号引用:

         1) 以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可;

          2)例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现;

          3)符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替;

   2.直接引用: 

       1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)

  2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)

  3)一个能间接定位到目标的句柄

  3.静态链接:
     当一个字节码文件被装在进JVM内存时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接而引用的过程称之为静态链接

1.2.4初始化(initilalization)

   对类的静态变量0初始化为指定的值,执行静态代码块

 2.类加载器的双亲委派机制

2.1双亲委派机制介绍(先找父亲加载,不行再由儿子自己加载)

1)如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行:

2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器:

3)如果父类加载器可以完成类加载任务,就成功返回,子加载器就不会加载,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制

2.2双亲委派机制的加载流程图

2.3双亲委派机制的作用

  (1)沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心
     API库被随意篡改
  (2)避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一
次,保证被加载类的唯一性

解释:

 1.防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全.
2.保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了.

3.不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

2.4打破双亲委派机制

    打破双亲委派机制的场景有很多:JDBC、JNDI、Tomcat等,我们以Tomcat为例来说明

2.4.1Tomcat为什么要打破双亲委派机制

   Tomcat是个web容器, 那么它要解决什么问题:

      1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的
不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是
独立的,保证相互隔离
      2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程
序,那么要有10份相同的类库加载进虚拟机。
       3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的
类库和程序的类库隔离开来。
       4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中
运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

2.4.2Tomcat 如果使用默认的双亲委派类加载机制行不行?

      不行1.然而双亲委派机制他的职责就是保证唯一性,那么是无法加载两个相同类库的不同版本的,默认

的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份
      2.同时在修改jsp文件后,因为类名一样,默认的类加载器不会重新加载,而是使用方法区中已经存在的类,所以需要每个jsp对应一个唯一的类加载器,
当修改jsp的时候,直接卸载唯一的类加载器,然后重新创建类加载器,并加载jsp文件
      3.tomcat 这种类加载机制违背了java 推荐的双亲委派机制,很显然,tomcat 不是这样实现。
        tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制。 

  2.4.3Tomcat的类加载机制

 

2.4.4tomcat的几个主要类加载器

1.commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
2.catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
3.sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
4.WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,
每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本; 

posted on 2022-03-20 14:21  跟着锋哥学Java  阅读(208)  评论(0编辑  收藏  举报

导航