美文网首页
java Class和加载机制精华一页纸

java Class和加载机制精华一页纸

作者: 轩居晨风 | 来源:发表于2017-04-11 22:13 被阅读37次

    Java 是一个解释型语言(使用了JIT后, 也有变成本地机器码的, 但一般意义上都是先预编译成字节码, 解释执行),那字节码里面包含了那些?解释的过程如何?

    1、Class 字节码

    I、基本信息

    Class识别、版本信息

    II、常量池

    类似TLV表述的结构,数据类型主要是用 U1、U2 ... (对应 1,2,4,8个字节)的无符号数。

    常量池并不是指类里面的常量,而是类里面所有名称和限定符(字面常量、编译常量)

    因为,class中用来描述常量长度是 u2类型,即最大只能描述65535,所以java中变量名最大只能到 65535

    常用的特殊类型

    对象类型是用 字符 L

    数组类型使用 [

    比如 java.lang.String[] 则表示为[Ljava/lang/String

    III、各种表集合

    字段表 - 域变量 (类型、操作指令、行数等)

    方法表 - 类方法 (类型、操作指令、行数等)

    指令用u1 类型来标识,那说明最多智能有 256条指令;code_length使用 u4 类型,理论上可以有4294967295条指令,实际上java只允许最多 65535条指令,否则编译失败(以后不知道会否放开),因为这个限制在一些超长的方法(比如复杂jsp编译后的java),会编译失败

    如果子类没有重写父类的字段和方法,就不会在子类的class文件中出现父类的参数和方法。

    javap -verbose XXXClass 可以反编字节码

    2、Class 加载和解析

    I、加载过程

    字节码的加载流程: 装载 - 链接(验证 - 准备 - 解析) - 初始化

    装载(load):查找并加载二进制数据

    链接(Link)- 在这一阶段,主要是 验证:确保加载类的正确性;准备:为静态变量分配内存,并初始化默认值;解析:把类的符号引用转换成直接引用

    初始化:类的初始化发生在(I、创建类实例new;II、调用类的静态方法和变量;III、反射调用class.forName;IV、初始化子类;V、JVM启动时标明的启动类)

    II、加载机制

    Java的 类加载机制:双亲委派模型

    Java的类加载是一个委托模型,三级加载,双亲委派模式

    System(系统) – Extension(扩展) – Bootstrap(引导) 当System加载器碰到一个类时,首先委托Extension,委托Bootstrap加载,如果无法加载,才自己加载

    这样有利于类的层次,也避免恶意破坏

    两个类是否相同?

    java判断是否是同一个类,除了检查类的名称(全路径),同时还判断该类的类加载器是否相同,如果被加载到不同的类加载器,类也是不同的。所以核心类都必须是引导加载

    III、双亲委派模型的缺陷 - 机制的补充

    这种单向委派的机制,对于框架设计有个严重的问题, 框架只设计接口, 实现由具体应用完成。

    现在,子类加载器加载的类 知道父类,父类加载器加载的类不知道子类, 这样就无法完成闭环。

    比如a、Java规范提供了很多接口,比如JDBC,需要具体的厂商来实现,这时候加载;b、典型的Java Servlet规范,要求每个Web应用使用自己的加载类

    解决方法就是,线程上下文加载器;

    通过类java.lang.Thread getContextClassLoader()和setContextClassLoader() 来获取和设置线程的上下文加载器。创建线程时, 如果未设置上下文加载器,从父线程继承一个线程上下文加载器,默认的是 系统 上下文加载器

    这样 父类加载器 发现无法加载类时,可以通过 获取线程上下文加载器(即子类加载器),请求子类加载器去加载代码

    线程上下文加载器 VS 当前加载器 如何选

    a、默认使用当前加载器,除非有 父依赖子的情况

    b、更好的策略 是 当前加载器和线程上下文加载器谁是子,就使用谁

    何时需要关注类加载器?

    当我们使用 反射\动态代理 等需要加载类功能的时候,背后就是在使用类加载器。特别是Web应用,比如tomcat,每个web应用都有一个对应的类加载器实例。该类加载器首先会加载应用,找不到再代理给父类加载器。这与一般类加载器是反的,也是 Java Servlet 规范推荐的,目的是Web应用的优先级高于Web容器的优先级(不加载核心库)

    tomcat的classloader怎么隔离类

    Tomcat的类加载机制

    JDK默认的加载器

    common类加载器 - CommonClassLoader (公用部分)

    Catalina类加载器 CatalinaClassLoader (服务器部分) Shared类加载器 SharedClassLoader(应用部分)

    WebApp类加载器 WebappClassLoader

    Jsp类加载器 JsperLoader

    其中CatinaClassLoader用来加载 tomcat自己的类,而WebAppClassLoader每个应用都有一个自己的类加载器

    在Context容器(对应一个web应用)启动时,就会启动一个WebAppClassLoader,所以每个应用互不干涉

    IV、如何自定义 类加载器

    最佳的方式,利用已有的加载器,做一些扩展,比如 URL 的加载器 URLClassLoader

    或者自己实现 ClassLoader类,重点是 实现 findClass 读取字节码,并调用 defineClass转换为Class 对象

    V、加载即其他

    利用类加载的灵活机制,有很多衍生的技术

    动态代理

    asm

    cglib

    OSGi

    ...

    如果加载的类是确定的,当类特别多的话,如果需要加快加载时间,可以关闭加载验证过程 –Xverify:none;

    VI、初始化顺序的问题

    a、类加载时, 首先加载静态变量和静态方法

    b、因为子类不持有父类的 属性,所以直接通过子类引用父类静态属性,不会触发子类初始化

    c、构成初始化的几个条件,比如new

    父类静态 > 子类静态 > 父类构造 > 子类构造

    加载(静态)优先, 长辈优先

    3、Class执行 - 多态实现的关键

    有些方法(静态方法、私有方法等等)在解析时,就能确定方法的入口指针(直接引用),但其他方法, 可能存在override, 还有 overload的一些情况。

    public class Dispatch {

    static class QQ{}

    static class _360{}

    public static class Father{

    public void hardChoice(QQ arg){

    System.out.println("father choose qq");

    }

    public void hardChoice(_360 arg){

    System.out.println("father choose 360");

    }

    }

    public static class Son extends Father{

    public void hardChoice(QQ arg){

    System.out.println("son choose qq");

    }

    public void hardChoice(_360 arg){

    System.out.println("son choose 360");

    }

    }

    public static void main(String[] args) {

    Father father = new Father();

    Father son = new Son();

    father.hardChoice(new _360());

    son.hardChoice(new QQ());

    }

    }

    public static void main(java.lang.String[]);

    Code:

    Stack=3, Locals=3, Args_size=1

    0: new #16; //class ch08/Dispatch$Father

    3: dup

    4: invokespecial #18; //Method ch08/Dispatch$Father."":()V

    7: astore_1

    8: new #19; //class ch08/Dispatch$Son

    11: dup

    12: invokespecial #21; //Method ch08/Dispatch$Son."":()V

    15: astore_2

    16: aload_1

    17: new #22; //class ch08/Dispatch$_360

    20: dup

    21: invokespecial #24; //Method ch08/Dispatch$_360."":()V

    24:invokevirtual #25; //Method ch08/Dispatch$Father.hardChoice:(Lch08/Dispatch$_360;)V

    27: aload_2

    28: new #29; //class ch08/Dispatch$QQ

    31: dup

    32: invokespecial #31; //Method ch08/Dispatch$QQ."":()V

    35:invokevirtual #32; //Method ch08/Dispatch$Father.hardChoice:(Lch08/Dispatch$QQ;)V

    38: return

    LineNumberTable:

    line 30: 0

    line 31: 8

    line 32: 16

    line 33: 27

    line 34: 38

    LocalVariableTable:

    Start Length Slot Name Signature

    0 39 0 args [Ljava/lang/String;

    8 31 1 father Lch08/Dispatch$Father;

    16 23 2 son Lch08/Dispatch$Father;

    }

    overload通过指定参数类型,编译时指向时有对应的参数类型,执行时通过不同的参数类型,就能找到具体的方法,称为静态分派

    override编译出来的类型,都是相同的 类型, JVM 在运行时提供一个虚拟方法表, 如果子类改写了父类,则方法入口地址为子类方法地址,这样就能识别指向查找的方法了, 称为动态分派

    相关文章

      网友评论

          本文标题:java Class和加载机制精华一页纸

          本文链接:https://www.haomeiwen.com/subject/agfpattx.html