《深入理解JVM之类加载机制》

前言:

        我希望我的希望不再是希望

正文:

                                                                              类加载过程 


       什么是类加载机制

        Java虚拟机把描述类的数据从Class文件加载进内存,并对数据进行校验,转换解析和初始化,最终形成可以把虚拟机直接使用的Java类型,即为虚拟机的类加载机制。

      类加载过程

          

         加载


         (1)通过一个类的全限定名来获取定义此类的二进制字节流

         (2)将这个字节流所代表的静态存储结构转化为方法区的运行时的数据结构

         (3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区的这个类的各种数据的访问入口。


            验证 -确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全


           (1)文件格式验证

              验证字节流是否符合Class文件格式的规范,比如是否以魔数0xVAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型

           (2)元数据验证

             对字节码描述的信息进行语义分析,以保证其描述的信息是否符合Java语言规范的要求,例如:这个类是否有父类,除了java.lang.Object之外。

           (3)字节码验证

             通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的

           (4)符号引用验证 

            确保解析动作能正确执行


            准备


          准备阶段是正式为类变量分配内存并设置类变量初始值的阶段

          举个栗子,public static int value = 123;在准备阶段,value的值被初始化为零,在初始化阶段之后,value的值才是123

          然而在准备阶段,有一种特殊情况,public static final int value = 123,value被final修饰,在准备阶段value的值就为123


          解析


         解析阶段是虚拟机将常量池内的符号引用转换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、和调用点限定符7类符号引用进行。


          初始化 


          类初始化阶段类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。

    面试题--类的实例化顺序

        原则:先静态先父后子

       先加载父类和子类的静态数据,再加载父类的构造函数和方法,最后加载子类的构造函数和方法 


                                                                                   类加载器 


      什么是类加载器 

          虚拟机设计团队把类加载阶段中的”通过一个类的全限定名来获取描述此类的二进制字节流“这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这部分的代码模块成为“类加载器”。

      类与类加载器的关系 

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

          比较两个类是否相等,只有在这两个类是同一个类加载器加载的前提下才有意义。 

       类加载器类型 

            

          详细解说下从Java开发的人员角度的各个类加载器:

          1.启动类加载器(Bootstrap ClassLoader): 这个类加载器负责将存放在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实现。由于这个类加载器是ClassLoader中的getSystemClassLoader方法的返回值,所以成为系统类加载器。它负责加载用户类路径(ClassPath)上指定的类库。开发者可以直接使用这个类加载器,如果应用中没有定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。

          关系图:

    什么是双亲委派模型 

         各个类加载器之间的关系称为双亲委派模型(Parents Dlegation Mode),其要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。它们之间的父子关系都使用组合关系来复用父加载器的代码,它不是强制性的约束模型,是Java设计者推荐给开发者的一种类加载器实现方式。

     双亲委派模型的工作原理

          如果一个类加载器收到加载类的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都会传送给顶层的启动类加载器。只有当父加载器反馈自己无法去完成这个请求时,子加载器才会尝试自己去加载。

       为什么要用双亲委派模型 

            如果没有使用双亲委派模型,由各个类加载器自行加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统将会出现多个不同的Object类,Java类型体系中最基础的行为就无法保证。应用程序也将会变得一片混乱。

            保证Java类型体系中最基础的行为

      如何实现双亲委派模型

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundExeption
{
    synchronized (getClassLoadingLock(name)){
        //首先,检查请求的类是否已经被加载过了
        Class<?> c = findLoadClass(name);
        if (c == null){
           Long t0 = System.nanoTime();
           try{
             if (parent != null){
                c = parent.loadClass(name, false)
             }else{
                c = findBootstrapClassOrNull(name);
             }
           }catch (ClassNotFoundException e){
                //如果父类加载器抛出ClassNotFoundException
                //说明父类加载器无法按时完成加载请求
           }
        }
        if (c == null){
           //在父类加载器无法加载的时候
           //再调用本身的findClass方法来进行加载
           Long t1 = System.nanoTime();
           c = findClass(name);

           //这是定义类装入器;记录数据
           sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 * t0);
           sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
           sun.misc.PerfCounter.getFindClass().increment();
        }
    }
    if (resolve){
           resolveClass(c);
    }
    return c;
}

        具体说明:先检查类是否已经被加载过,若没有加载则调用父加载器的loadClass方法,如父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器失败,抛出ClassNotFoundException异常后,再调用自己的findClass方法进行加载。

    如何破坏双亲委派模型

 

         (1)JDK1.2发布之前

        (2)模型自身的缺陷。双亲委派模型很好的解决了各个类加载器的基础类的统一问题。但是,如果基础类调用用户的代码,就不遵循着这样的规则了。解决此问题可以用:线程上下文类加载器(Thread Context ClassLoader)

        (3)热插拔、热部署、模块化

     破坏示例---Tomcat

          1。tomcat是一个Web容器,解决的问题是:

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

         (2)部署在同一个Web容器中相同的类库相同的版本可以共享。否则如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机,不可能这么设计   ---默认类加载器支持

         (3)基于安全考虑,Web容器自己依赖的类库与应用程序的类库相互隔离

         (4)Web容器要支持JSP的修改。支持在程序运行后修改jsp不用重启。

          2.如何破坏

        (1)如果使用默认的类加载机制,无法加载两个相同类库的不同版本,默认的类加载器只在乎全限定类名,并且只有一份。

         (2)使用默认的类加载器。如果部署的jsp文件修改了,因为其类名是不变的,所以类加载器会直接取方法区中已经存在的,修改后的jsp不会被重新加载,需要卸载这个jsp的类加载器,重新创建类加载器,重新加载jsp文件。 

      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可见;

       Tomcat 为了实现隔离性,没有遵守除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。

        如果Tomcat的Common ClassLoader想加载WebApp ClassLoader中的类,可以使用线程上下文类加载器实现让父类加载器请求子类加载器去完成类加载的动作。

结语:

        

        

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值