类加载机制
- 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析以及初始化,最终形成可以被虚拟机直接运行的Java类型
- Java中类型加载和链接是在运行时完成,增加性能开销,但天生具有动态加载的语言特性,即运行时才知道具体的实现类
- 生命周期:
加载--》验证--》准备--》解析--》初始化--》使用--》卸载
- 其中,解析阶段比较特殊,可以放到初始化后面!从而支持了动态绑定(运行时绑定)
- 类加载时机,主动引用,当且进当:
- 实例化对象(new), 读取/设置类静态字段(非常量)(getstatic,putstatic),调用类的静态方法(invokestatic)
- 反射调用类(java.lang.reflect)
- 子类初始化引发父类初始化
- 执行主类(main函数)
- 除上面四种主动引用外,其他的引用类方法都不会初始化类,称为被动引用
- 子类引用父类静态字段,子类不会初始化
- 定义类引用数组,不会出发类初始化(TODO 类对象引用大小固定的原因?)
- 如定义:
MyClass[] a = new MyClass[]
只会触发[LMyClass
初始化(newarray引起)
[LMyClass
直接继承Object,数组类型,这个类包含了访问数组的基本方法和变量,如length
clone
,数组的访问都是通过该类,所以较C(++)更安全
- static final的常量在编译阶段就存入调用类的常量池中,所以本质上没有直接引用到常量的定义类
- 接口的初始化类似与类,但是有所不同的是,子接口初始化不一定引起所有父接口初始化
类加载过程
- “加载”阶段,读取class文件,三件事:
- 通过类的全限定名获取定义此类的二进制字节流(可以有各种来源:压缩包,网络,运行时生成(反射),由其他文件生成(jsp))
- 将字节流代表的静态存储结构转化为方法区的运行时数据结构
- 在Java堆中生成一个代表这个类的
java.lang.Class
对象,作为方法区数据的入口
- 链接阶段 – 验证
- 目的是保证class文件格式满足虚拟机要求
- Java语言本身是安全的,使用纯粹的java不会造成数组越界,错误类型转换等问题,但是Class文件不一定是Java源码编译来的,甚至可以是手工编写的
- 从字节码层面上看,Java代码无法做的事情都是可以实现的,至少语义上是可以表达的
- 四个阶段
- 文件格式验证,魔数,编码,版本号等
- 元数据验证,父类是否存在,是否允许被继承等
- 字节码验证,数据流和控制流分析,全包安全
- 符号引用验证,将符号引用转化为直接引用,看能不能找到
- 链接阶段 – 准备
- 为类变量分配内存并设置初始值(方法区)
- 一般设置初始值为0,因为现在还没有初始化类,不会执行类的任何方法(
putstatic
在cinit
中,初始化类的时候执行)
- 特殊的是如果类变量是常量(final static),编译的时候就会将该常量设置ConstantValue属性,准备阶段就会根据这个属性设值
- 链接阶段 – 解析
- 将符号引用解析为直接引用
- 解析动作主要针对类或接口,字段,类方法,接口方法四类符号引用进行解析
- 初始化阶段:即执行类构造器
<cinit>()
方法的过程
<cinit>()
方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,收集顺序是这些语句在源文件中的出现次序,静态语句块只能访问定义在其之前的类变量,定义在其后面的变量,只能对其赋值,而不能访问TODO test
<cinit>()
和类的构造方法不同,它不需要显式地调用父类构造器,虚拟机会保证父类的构造器先执行
- 父类的static块要先于子类的类变量复制
- 如果一个类中没有静态变量(语句块),虚拟机就不会为之生成()函数
- 虚拟机会保证一个类的
<cinit>()
方法正确的加锁同步。多个线程同时去初始化一个类就会涉及到阻塞等待…MARK
类加载器
- 类加载器是实现‘通过类的权限定名获取其二进制字节流’的代码模块
- 对于任何一个类,其唯一性由加载它的类加载器和这个类本身共同确定
- eclipse中运行java web程序时,应该是有两个类加载器,java本身的和tomcat的
- 分类
- 启动类加载器,默认路径
<JAVA_HOME>/lib
,或由-Xbootclasspath
设定,并且是按照文件名加载的,如rt.jar,java程序无法直接使用
- 扩展类加载器,默认路径
<JAVA_HOME>/lib/ext
或由java.ext.dirs
设定
- 应用程序类加载器,加载用户的
classpath
,最常用,ClassLoader
中getSystemClassLoader()
返回它
- 自定义类加载器,重写3中的类加载器
- 双亲委派模型(Parent Delegate Model)
- 类加载器使用组合(composition)关系复用‘父类’的加载器
- 如果一个类加载器接到类加载请求,不会直接尝试加载这个类,而是把请求委派给父类加载器去完成,直到最顶层,父类反馈自己无法加载,子类再去尝试加载
- java类随着其加载器有了优先级树形层次结构,这样更安全,比如重写rt.jar中的类将无法被加载,因为root加载器只加载指定目录和文件名中的类
- 双亲委派模型不是强制的,只是java的建议
- 随着对程序动态性的需求的兴起,如代码热替换,模块热部署,即想让代码想优盘一样即插即用,双亲委派模型不断被‘破坏’
- OSGi是’事实上’的java模块化标准,OSGi实现模块化热部署的关键就是其自定义的类加载器,每个程序模块(OSGi称为bundle)都有各自的类加载器,当需要更换bundle时,就把bundle连同类加载器一起换掉…
- OSGi中的类加载器是一个网状结构…