Java对象与JVM(一)
Java对象在Java虚拟机中的创建过程
在《Java内存区域 JVM运行时数据区》文章了解到Java中几乎所有的实例对象存储在Java堆内存中。
下面我们详细了解Java程序中new一个普通对象时,HotSpot虚拟机是怎么样创建这个对象的,包括5个步骤:相应类加载检查过程、在Java堆中为对象分配内存、分配后内存初始化为零、对对象进行必要的设置、以及执行对象实例方法<init>,最后我们再从JVM指令角度来解释下Java对象创建。
1、相应类加载检查过程
通过《JVM字节码指令及反编译分析》可以知道:Java程序中的“new”操作会转换为Class文件中方法的“new”字节码指令。
JVM(本文特指HotSpot)遇到new指令时,先检查指令参数是否能在常量池中定位到一个类的符号引用:
(A)、如果能定位到,检查这个符号引用代表的类是否已被加载、解析和初始化过;
(B)、如果不能定位到,或没有检查到,就先执行相应的类加载过程;
2、为对象分配内存
对象所需内存的大小在类加载完成后便完全确定(JVM可以通过普通Java对象的类元数据信息确定对象大小);
为对象分配内存相当于把一块确定大小的内存从Java堆里划分出来;
(A)、分配方式:
(I)、指针碰撞
如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;
分配内存只需向空闲那边移动指针,这种分配方式称为"指针碰撞"(Bump the Pointer);
(II)、空闲列表
如果Java堆不是规整的:用过的和空闲的内存相互交错;
需要维护一个列表,记录哪些内存可用;
分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为"空闲列表"(Free List);
Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的;
所以,使用Serial、ParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存;而使用CMS这种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式;
后面再介绍垃圾收集算法和垃圾收集器,了解垃圾收集时应注意这里的内容;
(B)、线程安全问题
并发时,上面两种方式分配内存的操作都不是线程安全的,有两种解决方案:
(I)、同步处理
对分配内存的动作进行同步处理:
JVM采用CAS(Compare and Swap)机制加上失败重试的方式,保证更新操作的原子性;
CAS:有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做;
(II)、本地线程分配缓冲区
把分配内存的动作按照线程划分在不同的空间中进行:
在每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer,TLAB);
哪个线程需要分配内存就从哪个线程的TLAB上分配;
只有TLAB用完需要分配新的TLAB时,才需要同步处理;
JVM通过"-XX:+/-UseTLAB"指定是否使用TLAB;
3、对象内存初始化为零
对象内存初始化为零,但不包括对象头;
如果使用TLAB,提前至分配TLAB时;
这保证了程序中对象(及实例变量)不显式初始赋零值,程序也能访问到零值;
4、对象内存初始化为零
主要设置对象头信息,包括类元数据引用、对象的哈希码、对象的GC分代年龄等(详见下节);
5、执行对象实例方法<init>
该方法把对象(实例变量)按照程序中定义的初始赋值进行初始化;
通常,经过上面5步对象才完全new出来。
另外,还可以参考HotSpot虚拟机源码中的"bytecodeInterpreter.cpp"文件,这个文件有表示解释器处理"new"指令基本类似上面的5个过程。
6、Java对象创建的JVM指令
通过前面一些文章,我们还可以从JVM指令的角度来看对象的创建过程:
(A)、new指令
"new"指令有一个类符号引用的常量,JVM解析该常量也就对应步骤1"相应类加载检查过程";
"new"指令执行完毕后,一个代表(指向)该对象实例内存数据的reference类型变量数据将压入到操作数栈中;
(B)、dup指令
接着会执行"dup"指令复制该reference数据,这时操作数栈栈顶就有两个指向该对象实例内存的reference数据;(如果<init>方法有参数,还需要把参数加载到操作栈)
(C)、invokespecial指令
再执行"invokespecial"指令调用对象实例方法<init>,这时操作数栈最上面的一个reference数据会出栈(如果有参数,包括方法参数);
然后在Java虚拟机栈中创建<init>方法的栈帧,把出栈的reference数据(和参数)放入该栈帧的局部变量表中,该reference数据在方法中也就是"this",表示对该对象实例进行的操作;
当然这些参数的数值、数据类型和顺序都必须遵循实例方法的描述符中的描述;
另外,操作数栈中还有一个对象reference数据一般被“astore”到局部变量表或保存到字段变量,给后面访问对象使用。
到这里,我们大体了解Java对象在HotSpot虚拟机中的创建过程, 后面我们将分别去了解:对象的内存布局、对象的访问定位、方法的调用与执行、JIT编译、以及JVM垃圾收集相关内容……
【参考资料】
1、《The Java Virtual Machine Specification》Java SE 8 Edition:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
2、《Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide》:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html
3、《Memory Management in the Java HotSpot™ Virtual Machine》:http://www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf
4、HotSpot虚拟机参数官方说明:http://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
5、《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版 第2章