1.类创建的整体流程
1.1类加载
所谓加载,就是将 Java 类的字节码文件加载到机器内存中,并在内存中构建出 Java 类的原型——类模板对象。 所谓类模板对象,其实就是Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、类字段、类方法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获取 Java 类中的任意信息,能够对 Java 类的成员变量进行遍历,也能进行 Java方法的调用。反射的机制即基于这一基础。如果 JVM 没有将 Java 类的声明信息存储起来,则 JVM 在运行期也无法反射。 加载完成的操作: 加载阶段,简言之,查找并加载类的二进制数据,生成Class的实例。在加载类时,Java 虚拟机必须完成以下 3 件事情:
- 通过类的全名,获取类的二进制数据流
- 解析类的二进制数据流为方法区内的数据结构(Java 类模型)
- 创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口
1.2链接
链接分为三个环节:验证、准备、解析
1.2.1验证
保证加载的字节码是合法、合理并符合规范的,验证的内容则涵盖了类数据信息的格式验证、语义检查、字节码验证,以及符号引用验证等。 今天太懒了就不展开啦,以后有机会再说吧,咕咕
1.2.2准备
准备阶段,为类的静态变量分配内存,并将其初始化为默认值。 注意:这里不包含基本数据类型的字段用final修饰的情况,因为final在编译的时候就会分配了,准备阶段会显式赋值
1.2.3解析
将类、接口、字段和方法的符号引用转为直接引用(真实的内存地址) 符号引用就是一些字面量的引用,和虚拟机的内部数据结构和内存分布无关。比较容理解的就是在 Class 类文件中,通过常量池进行了大量的符号引用。但是在程序实际运行时,只有符号引用是不够,系统需要明确知道该方法的位置。 以方法为例,Java 虚拟机为每个类都准备了一张方法表,将其所有的方法都列在表中,当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法。通过解析操作,符号引用就可以转变为目标方法在类中方法表中的位置,从而使得方法被成功调用。
1.3类的初始化
执行类的初始化方法:clinit()方法 执行静态代码块,为类的静态变量赋予正确的初始值(注意这里的赋值就是程序中自己定义的咯)
1.4静态属性的赋值总结
两个环节:准备环节里是为静态变量赋初值,类的初始化环节里是为静态变量显示赋值
2.对象创建的整体流程
2.1类的加载、链接、初始化
虚拟机遇到一条new指令,首先去检查这个指令的参数能否在元空间的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,链接和初始化。(即判断类元信息是否存在)。如果没有,那么在双亲委派模式下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的 .class文件,如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class对象。 双亲委派模型工作工程: 1.当应用类加载器收到一个类加载请求时,他将请求委派给父类扩展类加载器去完成。 2.当扩展类加载器收到一个类加载请求时,他将请求委派给父类加载器引导类加载器去完成。 3.如果引导类加载器加载失败,就会让扩展类加载器尝试加载。 4.如果扩展类加载器也加载失败,就会使用应用类加载器加载。 5.如果应用类加载器也加载失败,就会使用自定义类加载器去尝试加载。
2.2开辟内存空间
首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。 根据垃圾回收器是不是有整理功能,分为两种方法:指针碰撞与空闲列表
2.2.1指针碰撞
如果内存规整:采用指针碰撞法(Bump The Pointer)来为对象分配内存。 意思是所有用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为分界点的指示器,分配内存就是把指针向空闲那边挪动一段与对象大小相等的距离(其实就是整齐的摆放)。如果垃圾收集器选择的是Serial、ParNew这种基于压缩算法的,虚拟机采用这种分配方式。一般使用带有compact(整理)过程的垃圾收集器时,使用指针碰撞。
2.2.2空闲列表
如果内存不规整:内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表法来为对象分配内存。 虚拟机维护了一个列表,记录上哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为“空闲列表(Free List)”。
2.2.3并发问题
CAS+失败重试: CAS 是乐观锁的?种实现?式。所谓乐观锁就是,每次不加锁?是假设没有冲突?去完成某项操作,如果因为冲突失败就重试,直到成功为?。 虚拟机采? CAS配上失败重试的?式保证更新操作的原?性。 TLAB: 为每?个线程预先在 Eden 区分配?块?内存, JVM 在给线程中的对象分配内存时,?先在 TLAB 分配,当对象?于 TLAB 中的剩余内存或 TLAB 的内存已?尽时,再采?上述的 CAS 进?内存分配
2.3赋默认值
初始化分配到的内存即属性的默认初始化,保证对象实例字段在不赋值可以直接使用 和类的创建中赋默认值的环节差不多
2.4设置对象头
将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。
2.5初始化
执行init()方法:显示初始化(类内方法外)、示例代码块中的初始化、构造器中初始化
2.6实例属性的赋值总结
先是赋初始值,然后显示赋值和实例代码块(顺序由程序顺序决定),最后构造函数
|