类生命周期
- 类从被加载到虚拟机内存开始,到卸载,生命周期包括如下:
- 加载,验证,准备,解析,初始化,使用,卸载
类加载过程
加载
-
类加载的“加载”阶段,需要完成以下三件事情
- 通过类名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为元数据区(JDK1.6的方法区)的运行时数据结构
- 在Java堆中生成一个代表这个类的java.lang.Class,作为元数据区(JDK1.6的方法区)这些数据的访问入口
-
二进制字节流获取方式:
- ZIP包,JAR,EAR,WAR等格式的包文件中,常用的jar包
- 网络中获取,
- 动态代理技术,java.lang.reflet.Proxy
- 其他文件生成,例如JSP
- 等
-
如下图:
- 类加载什么时候开始:
- JVM没有规定具体时间但是有间接说明,因为JVM规定类类初始化的开始时间,那么类加载必然在初始化之前,有以下几种情况会需要进行初始化操作:
- 遇到new 关键字直接创建对象
- 通过java.lang.reflect 包的方法反射的方式创建对象
- 初始化一个类,发现他父类没有初始化,必须先初始化他的父类
- 虚拟机启动时候,需要先初始化main方法所在的类
- 通过动态代理生成类实例时候
验证
- 验证阶段是为了保证Class文件字节流符合JVm的要求。
- 文件格式验证,例如版本号
- 元数据验证,保证符合java语言规范,例如是否有父类,所有类都有父类,最少java.lang.Object
- 字节码验证,保证字节码对应的指令正确执行
- 符号引用验证,例如类名字是否写的正确
准备
- 正式为类变量(static修饰的变量)分配内存并设置初始值
- 在jdk1.8 及以上时候会在元数据区域分配内存,在jdk1.6 在方法区
- 初始值只是初始化了0 值,并没有在堆内存中赋值
解析
- 类或者接口解析
- 字段解析
- 接口方法解析
初始化
- 初始化阶段是执行类构造器< clinit >()方法的过程
- < clinit >()方法是由编译器自动收集所有类变量的赋值操作和静态语句块(static{}块)中的语句合并产生的
- 虚拟机会保证父类的< clinit >()方法 先执行,因为会先加载父类
- < clinit >()方法对于类 或者接口是非必须的
类加载器
-
Java中类加载器可以分为两大类:
- 启动类加载器 (Bootstrap ClassLoader)
- 其他类加载器,这些加载器都由Java语言实现,独立于虚拟机外部,并且全都继承来自java.lang.ClassLoader
-
Java中要比较两个类是否相等,需要满足两个条件:
- 两个类来源于同一个Class文件
- 两个类的类加载器是同一个虚拟机类加载器
-
java程序用到的重要类加载器
- 启动类加载器(BootStrap ClassLoader):负责加载JAVA_HOME\lib目录中的,或者被-XXbootclasspath制定目录中的,并且是虚拟机识别的类库
- 扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME\lib\ext 目录中,或者被java.ext.dirs系统变量制定路径中所有类库
- 应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)上制定的类库,开发者可以直接使用这个类加载器,如果我们应用程序没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器。
双亲委派原则
-
以上类加载器的这种层次关系,称为类加载器的双亲委派模型
- 双亲委派模型要求除了顶层启动类加载器(BootStrap ClassLoader)以外,其他的类加载器都必须有自己的父类加载器
-
双亲委派模型工作过程:
- 如果一个类加载器收到了类加载请求,他首先不会自己加载这个类,而是将这个请求派发给父类加载器
- 每一层加载器都是如此,因此所有加载请求都会到达启动类加载器
- 只有当父类加载器反馈无法完成这个加载请求的时候,子加载器才会尝试自己加载。
双亲委派机制目的
- 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
- 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
JVM堆内存中对象结构
- 对象头:对象头存储对象运行时候数据,如对象hash码,GC分带年龄,锁状态标志,线程持有锁的指针等信息。在32位JVM中,对象头大小为8字节,64位JVM中,对象头大小为12字节。对象头中存储的具体结构和大小取决于具体的JVM实现
- 实例数据:实例数据是对象中存储具体数据部分,即我们代码中定义的实例变量。实例数据占用的空间大小根据对象实例变量的数量,类型和字节对齐规则等因素决定
- 对齐填充:由于JVM要求对象大小必须是8字节整数倍,所以如果实例数据部分大小不足8字节,JVM会通过填充无意义的数据来凑齐8字节整数倍,仅仅只是占位符的作用
JVM要求存储在堆中对象大小都必须是8字节整数倍
- JVM对于对象在内存中分配和对齐特有的规则,可以提高程序运行效率
- 因为内存分配和访问都是以字节为单位进行。为方便内存访问,JVM要求对象在内存中的地址必须是8字节的整数倍,这样,在内存访问的时候就可以直接根据对象地址和偏移量计算出变量的地址,而不需要进行额外的计算
- 当对象大小不是8字节整数倍,JVM通过填充的方式凑齐8字节,保障是8倍数大小。称为对齐填充
- 因此结论:对象大小必须是8字节的整数倍,是为了保证内存的访问效率和内存对齐,这样可以提高程序运行的效率特别是处理大量数据时候。