【一】jvm 类加载基础

1.我们先通过工具去编写 .java代码。然后通过 javac 编译为 .class字节码文件。

2.类加载器会把 .class字节码文件 加载到 jvm 的工作内存中。

3.接下来jvm 的解析器去执行我们的代码。

类的加载主要过程

 加载 ->  连接(验证 - > 准备 -> 解析)-> 初始化 -> 使用 -> 卸载

注意:只有使用到的class类,在当前jvm类加载器中,找不到的时候,才会触发类的加载。

     1.创建对象

     2.访问静态属性,或者调用静态方法

     3.如果初始化一个类的时候,发现他的父类还没有初始化,那么必须先初始化他的父类

     4.包含 "main()" 方法的主类,必须先初始化

1.加载

就是把 编译好的.class 文件,根据全限定名称,加载到jvm的方法区中,并且生成一个 java.lang.Class对象,作为方法区这些数据的访问入口(对于HotSpot虚拟机而言,Class 对象比较特殊,虽是对象,但是存放在方法区)。

加载途径:

 1.从压缩包中获取。(jar)

 2.从网络中获取(Applet)

 3.动态代理

 4.jsp文件生成对应的class文件

 5.数据库读。

这里也要注意一点,如果这个类是数组,有些不同。

数组会由虚拟机进行创建,本身就是不通过类加载进行创建的。

如果是引用数组,那么会递归采用正常的加载过程进行加载这个引用数组的类型组件。

如果是基本类型数组,会标记与引导类加载器关联。

数组的可见性将默认为public。

2.验证

就是根据 java虚拟机的规范,对加载进来的 .class 文件,进行检验,包括 文件格式验证、元数据验证、字节码验证和符号引用验证。比如调用到别的类的私有方法,重写了 final修饰的方法等等,检验合格后,后续才能交给jvm去执行。

3.准备

 在准备的阶段中,会给当前类的静态变量(staic 修饰)分配内存空间,对于基本变量会给一个初始值(0 或 null),对于当前类的静态常量,这个时候,会直接进行赋值操作。

 tip: 

        1.访问一个类的静态常量的时候,静态代码块不会执行。

        2.对于一般的成员变量是在类实例化的时候,随对象一起分配到堆内存中。   

4.解析

  指检查类是否引用了其他的类、接口,包括类或接口、字段、类方法、接口方法的解析,就是把符号引用替换为直接引用。

 public void gotoWork(){

      car.run(); //这段代码在Worker类中的二进制表示为符号引用        

 }

  在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名 和 相关描述符组成。在解析阶段,Java虚拟机        会把这个符号引用替换为一个指针,该指针指向Car类的run()方法在方法区的内存位置,这个指针就是直接引用。

5.初始化

类初始化是类加载的最后一步,除了加载阶段,用户可以通过自定义类加载器参与,其他阶段都完全由虚拟机主导可控制。到了初始化阶段才真正的执行java代码。

在这个阶段,会执行此类中的静态代码块,并执行静态变量赋值操作。

public class Test{

     public static String p1 = Test2.P2;

      public static String p3 ;

      static{

          p3 = "p3";

         }

 }

6.使用

就是正常使用,如创建对象 或者 访问属性、方法。

7.卸载

如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就 结束了。

 1、该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。

 2、加载该类的ClassLoader已经被回收。

 3、该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

类加载器和双亲委派机制

  • 对于任意一个类,都需要由加载它的类加载器和这个类本身来一起确立其在Java虚拟机中的唯一性。

1.启动类加载器 bootstrap ClassLoader 第一层

 他主要是负载加载jave安装目录下 “JAVA_HOME/lib”下的核心类。或者被 -Xbootclasspath 参数指定的路径中的,并且是虚拟机识别的(仅按照名称,如 rt.jar 名字不符合的类库,即使放到 lib 目录下也不会重载)。

2.扩展类加载器 Extension ClassLoader 第二层

 这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JAVA_HOME/lib/ext 目录下的,或者被java.ext.dirs 系统变量所指定的路径种的所有类库。开发者可以直接使用扩展类加载器。

3.应用加载器 Application ClassLoader 第三层

 这个类加载器由sun.misc.launcher$AppClassLoader 实现, 他负责加载用户类路径(classPath)上所指的类库。由于这个类是 getSystemClassLoader方法的返回值,所以也称为 系统类加载器。    

主要是负责夹杂 “ClassPath”环境变量所指定的类。其实就是加载我们编写的类。

4.自定义加载器

我们也可以根据需要,自己实现类加载器。比如 可以对字节码进行加密,然后在自定义加载器 进行类加载的时候,解密。

image.png

5. 双亲委派机制

当应用类需要加载一个类的时候,会先委派自己的父类加载器物种加载,若父类不存在,则继续向上委派。若最终父类加载器,没能加载到,才会自己去加载。这样做的好处,1

1.安全,避免重新定义核心类,导致服务异常的问题出现。

2.避免重复加载同一个类。

双亲委派模型的破坏者-线程上下文类加载器

在Java应用中存在这个很多服务提供者接口( Service provider interface, SPI),  这些接口允许第三方为他们提供实现,如常见的SPI 有 JDBC、JNDI等,这些SPI的接口属于 Java核心库,一般存在在 rt.jar包中,由 BootStrap类加载,而以来的第三方实现代码的jar ,存放在classPath 下,所以双亲委派的机制下,bootStrap加载器,无法直接去加载。

从jdk 1.2 开始已入了 线程上下文类加载器(contextClassLoader),我们可以通过 java.lang.Thead 类中的 

getContextLoader() 和 setContextClassLoader(ClassLoader cl) 方法来获取和设置,如果没有设置线程的上下文加载器,线程将继承其父线程的类加载器,初始线程的上下文类加载器就是系统类加载器(AppClassLoader)。在线程运行的代码中可以通过此类加载器来加载资源。这种方式就破坏了 '双亲委派模型'。

Tomcat 的类加载器是怎么设计破坏双亲委派的?

首先,我们来问个问题:

Tomcat 如果使用默认的类加载机制行不行?

我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:

  1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。

  2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,这是扯淡的。

  3. web容器也有自己依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。

  4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,否则要你何用? 所以,web容器需要支持 jsp 修改后不用重启。

再看看我们的问题:Tomcat 如果使用默认的类加载机制行不行?

答案是不行的。为什么?我们看,第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的累加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。第三个问题和第一个问题一样。我们再看第四个问题,我们想我们要怎么实现jsp文件的热修改(楼主起的名字),jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。

Tomcat 如何实现自己独特的类加载机制?

所以,Tomcat 是怎么实现的呢?牛逼的Tomcat团队已经设计好了。我们看看他们的设计图:

我们看到,前面3个类加载和默认的一致,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可见;

image.png

从图中的委派关系中可以看出:

CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。

WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。

而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。

好了,至此,我们已经知道了tomcat为什么要这么设计,以及是如何设计的,那么,tomcat 违背了java 推荐的双亲委派模型了吗?答案是:违背了。 我们前面说过:

双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。

很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。

我们扩展出一个问题:如果tomcat 的 Common ClassLoader 想加载 WebApp ClassLoader 中的类,该怎么办?

看了前面的关于破坏双亲委派模型的内容,我们心里有数了,我们可以使用线程上下文类加载器实现,使用线程上下文加载器,可以让父类加载器请求子类加载器去完成类加载的动作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容