深入理解jvm系列-类加载机制的深入解析

类加载过程

加载

这是整个类加载过程的第一个阶段

1根据类的权限定名获取此类的二进制流

2把字节流中的静态存储结构存放到方法区

3在堆区生成一个对应的class对象

数组的加载过程和类不太一样,数组是不需要加载的,只需要在内存中划分一段内存,而数组的元素类型是需要加载的,数组的引用类型也需要采用类加载器加载,还需要把这个数组标识在类加载器类名称空间上

验证

将class字节流加载到内存后,需要进行验证,防止这个二进制流执行危险的操作危害虚拟机,要看他是否符合java虚拟机规范,

1文件格式验证,比如是否以cafebabe魔数开头,主次版本号是否是虚拟机接受范围,常量池中是否有不被支持的常量类型,

2元数据验证,验证java 代码的规范性,比如该类是否有父类,这个类是否继承了不被允许继承的类final,这个类不是抽象类,是否实现了接口或者父类中要求实现的所有方法, 总之就是跟据java语言规范验证

3字节码验证

验证方法体中的code,是否存在危害程序的问题,比如定义了一个int类型的数据却把他当作long使用,或者把一个父类对象强制转换成子类对象,

这里有个悖论,就是不能用一个程序来准确的确定另一个程序是否存在bug ,jdk6以后javac替代了这项工作,然后生成一个stackmaptable的表,用来记录这些内容的合法性,所以在验证时,只需要看stackmaptable中记录是否合法

4符号引用验证

验证符号引用的字面量是否存在访问不到的情况,比如一个类是private的就可能访问不到,符号引用验证是为了在解析过程中能访问到直接引用.

准备阶段

这个阶段为刚刚的类型加载的方法区中的静态变量赋予原始值.原始值一般来说是0.但是如果加了final修饰,就不一定了

private static final int a = 123;

虚拟机会在内存中生成一个constantvalue的属性,就是123,所以原始值就不是0

解析

解析阶段是根据符号引用来找到对应的直接引用,如果一个类有直接引用,则他已经在内存中存在了.解析动作主要根据类和接口,字段,接口方法.类方法. 等

1类和接口的解析

假设当前代码所处的类为A ,把一个未被解析过的符号引用,解析成类B的直接引用

1他首先把这个符号引用传递给加载A的类加载器,让他去加载类B,如果类B被加载失败了,就表示解析失败了

2如果是一个数组类型,则他会去把这个数组的元素类型交给类加载器去加载,然后由虚拟机生成一个代表该数组维度和元素的数组对象.

3如果上面两步没有异常就会检查类A对B的访问权限

2字段解析

先对字段表中class_inde中的索引constant_class_info符号进行解析,说白了,就是先解析字段所属的类,然后在解析字段,比如解析的类为A

1A中包含了简单名称和字段描述符都与想找到的一样,则直接返回直接引用,

2否则查找他的父接口

3查找他的父类

4查找失败抛异常

3方法解析

先解析方法所在的类C

1如果C中有这个方法直接返回引用

2如果没有查找父类

3如果还没有查找父接口,这时候说明类c是一个抽象类,

4查找失败

4接口方法解析

先解析这个方法在class_index表中的对应的接口

1如果接口C中直接有,返回

2如果接口C中没有,查找父接口

3由于java允许实现多个接口,如果存在多个父接口重名的情况,随便返回一个,具体要看虚拟机如何实现的

初始化

这个部分才是真正的开发者用来初始化一个类的

也就是执行静态代码块中的内容

变量在准备阶段时已经赋予初值0,这个阶段是是根据程序员编写的代码来初始化类变量和其他资源

静态代码块中的内容是按先后顺序定义的,定义在前面的内容能赋值给后面的变量.,但是不能访问,由于一个类被加载的时候优先加载其父类,所以在执行静态代码块时也会有优先执行其父类的代码块

接口中虽然不能写静态代码块但是可以赋予变量初值,因此接口也会生成clint方法,但是一个接口被初始化的时候,其父接口不一定会被初始化,只有使用到父接口的变量才会被初始化.

java虚拟机对一个类的clint方法实现了同步操作,如果多个线程同时初始化一个类,只有一个线程执行clint方法,其他线程都需要阻塞,如果clint中有耗时很长的操作,他就会造成多个线程阻塞.

        

类加载器

类与类加载器

一个类加载器和一个类本身才能在虚拟机中确定其唯一性如果一个类,被两个加载器加载到虚拟机内存中,则这两个类是不想等的,尽管他们是同一个class文件,但是是不同的加载器.他们就不会相同.

类加载器

启动类加载器, 用于加载java_home/lib目录的内容,主要是一些基础的类,这个类加载器是用c++写的是虚拟机的一部分无法得到他的名字,只能使用null代替,如果用户想把类加载给这个类加载加载的话,就得把类加载器值赋值为null

扩展类加载器 用于加载java_home/lib/ext目录的内容.加载java扩展的类库

应用程序类加载器,主要加载用户自定义的类

 

双亲委派模型

双亲委派模型的工作原理是,如果一个类加载器需要加载某一个类,他会先把符号引用教给自己的父加载器,如果父加载器没办法加载,在找父加载器,如果还不能则返回给自己加载.这就保证了一个类的唯一性,因为越基础的类就越是上层加载器加载,这样object类永远都只有一个,

如果不采用双亲委派模型,用户自己定义一个object类,被加载,这样虚拟机中就存在两个object类,他们是不用的类加载器加载的.

破坏双亲委派模型

双亲委派模型第一次被破坏是在双亲委派模型出现之前,即jdk1.2面世以前,由于类加载器的概念和抽象类java.lang.classloader在java第一个版本中就已经存在,所以当时已经存在了用户自定义的类加载器,重写了loadclass方法,就覆盖了父类加载器的loadclass方法,破坏了双亲委派模型,为了兼容这些代码,java设计者们在java.lang.classloader中添加了一个protected的findclass()方法,并引导用户重写这个方法.

双亲委派模型第二次被破坏时因为这个模型的自身缺陷导致的.双亲委派模型很好的解决了基础类型一致性的问题(越基础的类会被越上层的类加载器加载),而如果i基础类型中要调用用户写的代码,而基础类型并不认识用户写的代码.为了解决这个问题,java的设计者引入了一个Java.lang.thread类,线程上下文类加载器.这个类加载器可以通过setContext-classloader()方法设置,如果没设置就从父类继承一个.

这个线程上下文加载器是一种父类加载器调用子类加载器请求完成加载的行为,这种行为实际上违背了双亲委派模型从上到下的层次结构

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值