以简单对象的创建为例,说明对象创建过程,新建ObjectTest.java
,代码如下:
public class ObjectTest {
public static void main(String[] args) {
Person person = new Person("Json");
System.out.println(person.getName());
}
static class Person {
private String mName;
public Person(String mName) {
this.mName = mName;
}
public String getName() {
return mName;
}
public void setName(String mName) {
this.mName = mName;
}
}
}
随后执行javac ObjectTest.java
命令,编译该文件,生成ObjectTest.class
,使用javap -c ObjectTest.class
查看字节码,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MorOv3VM-1666491038279)(https://gitee.com/tuozhaobing/image/raw/master/ObjectTest%E6%96%87%E4%BB%B6%E5%AD%97%E8%8A%82%E7%A0%811.png)]
从字节码可以看出Person对象创建时,首先在code 0位置调用new关键词创建Person对象,随后在code 6位置使用invokespecial调用Person类的init函数,随后代码中就开始进行Person对象的属性获取,调用该对象的相关函数了,从这里来看一个Java对象创建应该分为两步:new指令 和 执行init函数。
那么问题来了,new指令到底干了什么,为什么执行玩就创建了一个对象,从代码中我们可以看到并没有声明init函数,那么这个函数又是从哪儿来的?
对于Java虚拟机而言,new指令意味着创建普通对象,该指令接受一个操作数,指向常量池中的一个引用,该引用用于指示要创建的对象类型,以前文代码为例,#2位置为new指令接受参数,其指向的是Person这个类,如下图
那么虚拟机中又是怎么响应new指令的呢?
在虚拟机中,处理new指令一般分为以下几步:
在new指令执行完成后,也就意味着在内存中我们拥有了一个类的实例对象,到此就会执行我们编写的构造函数,根据开发人员的设计进行初始化,使得对象的状态符合开发人员预期,init函数执行完成后,一个Java对象的创建也就完成了。
在Hotspot虚拟机中,Java对象由对象头,实例数据,对齐填充三部分组成
从HotSpot虚拟机官方文档中可以看到是这样描述对象头的:
object header
Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.
大概含义是对象头是受GC管理的每一个对象的开头部分,其结构是通用的(每个oop都指向一个对象头)。对象头中包含有关堆对象布局,类型,GC状态,同步状态和身份哈希码的基本信息,其由两部分组成,在数组中,紧随其后的是一个长度字段。需要注意的是Java对象和VM内部对象都具有共同的对象头。
继续查找官方文档,我们可以看到对象头的两个部分分别为 klass pointer(类指针) 和 mark word(标记词) :
HotSpot虚拟机官方文档中这样描述Mark Word:
mark word
The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.
大概含义是 Mark Word是每个对象对象头的第一部分。通常是一组位域,包含同步状态和身份哈希码。也可以是指向同步相关信息的指针(具有特征低位编码)。在GC执行期间,有可能包含GC状态。
Mark Word在32位JVM中的长度是32位,在64位JVM中长度是64位。Mark Word在不同的锁状态下存储的内容不同,32位存储内容如下图:
64位存储内容如下:
从上面两张图可以看出,对于32或64位JVM中的Mark Word而言,虽然其数据占位长度有所差异,但其中组成内容基本是一致的:
HotSpot虚拟机官方文档中这样描述kclass pointer:
klass pointer
The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the “klass” contains a C++ style “vtable”.
大概含义是类指针是kclass pointer是每个对象对象头的第二部分。指向描述原始对象布局和行为的另一个对象(元对象)。对于Java对象而言,kclass中包含C++样式的“vtable”。
虚拟机正是通过这个指针来确定这个对象是那个类的实例。
我们都知道对象在构造后,可以传递给其他对象,当然也可以在其他对象内构造持有该对象,此时我们可以称该对象被其他对象引用,因被其他对象引用后发生GC(见对象管理中的说明)时,是否可以回收该对象,又可以把引用分为强引用,弱引用,软引用,虚引用四种引用方式,各引用方式与是否可被GC回收如下图所示:
引用类型 | GC时是否可回收 | 备注 |
---|---|---|
强引用 | 不可回收 | 无论何时,只要强引用关系存在,则该对象不会被垃圾收集器回收 |
软引用 | 内存溢出前,选中软引用对象做二次回收 | 当软引用对象回收后,如果内存仍然不足,则会继续跑出内存溢出异常 |
弱引用 | 下次垃圾收集器工作时被回收 | 弱引用对象只能存活到下次垃圾收集器工作之前 |
虚引用 | 最弱的引用方式,虚引用完全不会对对象的生存时间造成影响 | 为对象设置虚引用关联的唯一目的是在这个对象被垃圾收集器回收时收到一个通知 |