Java类的加载和实例化全过程解析

Java类的加载和实例化全过程解析

  • 1.类加载
    • 1.1 类加载器选择
    • 1.2 类装载
    • 1.3 类验证
    • 1.4 分配内存
    • 1.5初始化零值
    • 1.6设置对象头
    • 1.7执行init方法
  • 特例
    • String类型

本篇解析中,默认首次new时类还未加载,并使用的是应用程序类加载器。

1.类加载

1.1 类加载器选择

根据双亲委派模型,选择出该类的类加载器。

1.2 类装载

  1. 类加载器通过全限定名获取类的.class文件。
  2. 解析二进制数据流中的数据转换为方法区运行时的数据结构,类似于封装的一个过程。
  3. 堆中创建该类的Class对象,作为方法区数据访问的入口。

(数组类型不通过类加载器创建,而是JVM直接创建)

1.3 类验证

验证是为了保证加载的字节码是合法并符合规范的。

主要验证的有格式检查(版本号,长度,魔数)、语意检查(检查规定的规则,例如是否继承了final,抽象方法是否有实现这类的)、字节码验证(参数的赋值类型是否一致,函数调用是否传递正确参数这类)、符号引用验证(检查引用的类、方法是否存在)。

1.4 分配内存

类加载检查通过后,JVM为新对象分配内存,对象所需的内存大小在类加载完成后就可以确定了。

分配内存的方式有指针碰撞空闲列表两种,选择哪一种分配方式由堆内存是否规整在决定,堆内存是否规整由采用的垃圾回收器决定(会不会产生内存碎片)。

指针碰撞:

  1. 适用场合:适用于没有内存碎片的情况下。
  2. 原理:垃圾清理时,生存的对象全部整合到一边连续的内存中,中间有个分界指针,只要从指针开始向未分配的方向移动指定大小的空间即可。
  3. GC回收算法:使用标记压缩法、复制算法等整理内存的算法。

空闲列表:

  1. 适用场合:适用于堆内存不规则的情况下
  2. 原理:JVM会维护一个列表,列表中记录哪些内存块是可用的和大小,在分配的时候,从列表中寻找足够大的内存块划分给对象实例,然后更新列表

内存分配时主要是使用CAS乐观锁来解决线程安全问题。

1.5初始化零值

分配完内存后,为对象中的成员变量设置“零值”,例如int类型零值为0,布尔类型零值为false。

1.6设置对象头

初始化零值完毕后,虚拟机需要对对象头中的信息进行设置,例如元数据信息,对象的哈希码,对象的GC分带年龄信息等等。

1.7执行init方法

完成上述方法后,对象在虚拟机中算是创建完成了,接着就需要按照程序的编写进行初始化,例如对成员变量的赋值,构造方法的执行等。

特例

String类型

String类型的创建比较特殊,因为它内部维护了一个常量池,常见的创建String的方式有:

String str = "abc";
String str2 = "abc";
String strObj = new String("abc");
String strObj2 = new String("abc");

System.out.println(str == str2);   // true
System.out.println(str == strObj);   // false
System.out.println(strObj == strObj2); // false
  1. 使用双引号声明的String类型是在常量池中创建的,所以str和str2对比的是常量池中的地址
  2. 使用new关键字创建的都是堆内存中的新对象,所以后面两个的答案都是false。
    Java类的加载和实例化全过程解析_第1张图片
  • intern() 这是一个native方法,它的作用是如果常量池中含有此String对象的值,则返回常量池中该字符串中的引用;如果没有则在常量池中创建一个与此String内容相同的字符串,并返回常量池中创建的字符串的引用。
String str = new String("abc");
String str2 = str.intern();
String str3 = "abc";
System.out.println(str2 == str3); // true

你可能感兴趣的:(Java类的加载和实例化全过程解析)