类加载过程
当Java程序需要使用某个类时,如果该类还未被加载到内存中,JVM会通过加载、链接(验证、准备、解析)、初始化三个步骤来对类进行初始化。
1、加载
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2 )将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3 ) 将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象, 作为方法区这个类的各种数据的访问入口。
具体是使用通过ClassLoader的loadClass()方法来加载类,是线程安全的,使用双亲委派机制。
2、链接
Java类的链接指的是将Java类的二进制代码合并到JVM的运行状态之中的过程。在链接之前,这个类必须被成功加载。
1)验证是用来确保Java类的二进制表示在结构上是完全正确的。如果验证过程出现错误的话,会抛出java.lang.VerifyError错误。
2)准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配 。
3)解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程(解析的过程可能会导致其它的Java类被加载), 解新动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行,分别对应于常量池的CONSTANT_Class_info、 CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_IntrfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info和CONSTANT_InvokeDynamic_info7种常量类型。这一步是可选的。可以在符号引用第一次被使用时完成,即所谓的延迟解析(late resolution)。
- 符号引用(Symlxiuc References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量, 只要使用时能无歧义地定位到目标即可,引用的目标并不一定已加裁到内存中。
- 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。 如果有了直接引用,那引用的目标必定已经在内存中存在。
3、初始化
当一个Java类第一次被真正使用到的时候,JVM会进行该类的初始化操作。初始化过程的主要操作是执行静态代码块和初始化静态域。在一个类被初始化之前,它的直接父类也需要被初始化。但是,一个接口的初始化,不会引起其父接口的初始化。在初始化的时候,会按照源代码中从上到下的顺序依次执行静态代码块和初始化静态域。
需要注意的是,当访问一个Java类或接口中的静态域的时候,只有真正声明这个域的类或接口才会被初始化。
对象创建过程
对象创建分为三个步骤:为对象分配内存空间、初始化对象、将对象的内存地址赋给引用。
分配内存空间
创建对象的第一步就是要在内存空间中划分一块内存区域给对象使用,而对象所需要的内存空间大小在类加载完成时便可以确认。虚拟机划分内存区域的方法主要有指针碰撞法、空闲列表法、TLAB。
指针碰撞法
指针碰撞法主要适用于内存绝对规整的情况,也就是将使用过的内存与未使用的内存严格分隔开。中间的分界点指示器其实是一个内存地址的指针,当有新的对象创建需要分配内存空间时,只需要移动分界点指示器,也就是改变指针的值,使其往空闲区域移动就好。但是这种方式对于内存不规整的情况就不适用,因为这种情况很容易造成内存空间的浪费。
空闲链表法
空闲列表的方式很容易理解,就是在虚拟机内部维护一个空闲内存区域的列表,记录当前哪些内存区域是可用的。当有对象创建需要分配内存空间时,只要在列表中找到合适大小的区域,然后修改列表的内容即可,这种方法不要求内存区域的规整性。(虚拟机在底层采用CAS的方式来保证此操作的原子性和安全性)
TLAB(Thread Local Allocation Buffer)
为了保证Java对象的内存分配的安全性,同时提升效率,每个线程在Java堆中可以预先分配一小块内存,这部分内存称之为TLAB(Thread Local Allocation Buffer),这块内存的分配时线程独占的,读取、使用、回收是线程共享的。
初始化对象
当虚拟机为新创建的对象分配内存区域后,会进行对象的初始化操作。如给对象中所有的基本数据变量赋上初始化值,以至于当我们未对它们进行赋值操作时就可以使用对象了。
内存地址赋给引用
当内存空间划分成功,完成对象初始化操作后,虚拟机会将刚创建好对象的内存地址赋给引用对象。完成此操作后,便可以在程序中通过引用访问对象的实例数据。