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

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 Class和加载机制精华一页纸_第1张图片

两个类是否相同?

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和加载机制精华一页纸)