JVM对象的创建流程
上章介绍完java虚拟机的运行时数据区之后,我们大致了解了java虚拟机的概况,java虚拟机运行时内存的原理和存放了一些什么,这章我们来了解一下java虚拟机中对象是如何创建的如何分配的对象。java是一门面向对象的编程语言,在java程序运行过程中无时无刻都有对象被创建出来。在语言层面上,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字而已,而在虚拟机中,对象(限于普通java对象,不
上章介绍完java虚拟机的运行时数据区之后,我们大致了解了java虚拟机的概况,java虚拟机运行时内存的原理和存放了一些什么,这章我们来了解一下java虚拟机中对象是如何创建的如何分配的对象。
java是一门面向对象的编程语言,在java程序运行过程中无时无刻都有对象被创建出来。在语言层面上,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字而已,而在虚拟机中,对象(限于普通java对象,不包括数组和Class对象等)的创建又是怎么一个过程呢。在学习java虚拟机对象创建的时候笔者建议脑海中要自己绘画出一份流程图,先通过下面的流程图来简单了解一下
例如:new User()
1 类加载检查
虚拟机遇到一条new指令时,虚拟机首先会去方法区的常量池中定位到这个类的符号引用(上章已经介绍方法区初始化的时候加载的类信息),并且检查这个符号引用代表的类是否已被加载,解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2 分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可以完全确定(如何分配后续章节会继续讲解)。为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。
3 java堆中分配内存的两种方法
这里需要注重说明一下:java堆的内存分配方式是取决于垃圾回收器是否带有压缩整理功能决定的。在使用Serial,ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表方式
1) 指针碰撞:假设java堆中内存是完全规整的,所有用过的内存都放到一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配的内存就是仅仅把那个指针向空闲的空间那边挪动一段与对象大小相同的距离。这种分配方式称为‘指针碰撞’。
2)空闲列表:假设java堆中的内存不是规整的。已使用的内存和空闲的内存是相互交错的。那就没有办法通过简单的指针碰撞分配内存了。虚拟机就必须维护一个列表,记录上那些内存块是可用的,在分配的时候从列
表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种方式称为’空闲列表‘。
****这里说一下上面的Compact过程的收集器和Mard-Sweep算法的收集器,这里简单的介绍一下后续会讲解着三种算法。
标记复制(copying):从根集合开始扫描出存活的对象,然后将存活的对象复制到一块新的没有使用的内存空间中,当要回收的空间中存活的对象较少,比较高效
标记清除(Mark-Compact):从根集合开始扫描,对存活的对象进行标记,比较完成后,没有标记的对象记性回收,不需要对对象进行移动压缩,所以这种算法的方式一般使用空闲列表的分配内存方法
标记压缩(Mark-Compact):从根集合和开始扫描对存活的对象记性标记,比较完成后,没有标记的对象进行回收,但是这种算法会对存活的对象在内存空间中进行移动,好处是减少了内存空间的碎片,缺点是成本比较高,这种自带压缩的算法分配内存空间一般通过指针碰撞。
4 分配内存时解决并发的2种方式
对象在虚拟机中创建是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也不是线程安全的,可能出现正在给对象A分配内存。指针还没来得级修改,对象B又开始使用原来的指针进行分配内存的情况,解决这种问题有两种方法
1)同步处理:对内存空间的操作进行同步处理–实际上虚拟机采用CAS配上失败重试的方式保证了更新操作的原子性。
2)TLAB(Thread Local Allocation Buffer):把内存分配的操作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地县城分配缓冲区,那个线程要分配内存,就在那个线程的TLAB上分配,只有TLAB用完并分配新的内存时,在进行同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。
5 内存空间初始化
内存分配完成之后,虚拟机需要将分配到的内存空间进行初始化为零值(不包括对象头),如果使用TLAB,这一工作也可以提前到TLAB分配时处理。这一步操作保证了对象的实例在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型的零值。
接下来,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例,如何才能找打类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。这些信息存放在对象的对象头之中。根据虚拟机当前运行状态的不同,是否启用偏向锁等,对象头会有不同的设置方法。关于对象头的具体内容(后续章节会继续讲解)
在上面工作都完成之后,再虚拟机的角度一个对象已经产生了,但是在java程序的角度来看,对象创建才刚刚开始《init》方法还没有执行,所有的字段都还是零值。所以一般来说(字节码中是否跟随invokespecial指令所决定),执行new指令之后会接着执行init方法,把对象按照程序的意愿进行初始化,这样一个真正完全的对象才算生产出来。
下面的代码清单是HotSpot虚拟机bytecodeInterpreter.cpp中的代码片段(这个解释器实现很少有机会实际使用,因为大部分平台上都是用模板解释器;当代码通过JIT编译器执行时差异就更大了。不过,这段代码用于了解HotSpot的运作过程是没有问题的)
HotSpot解释器的代码片段
更多推荐
所有评论(0)