今天是愚人节,首先祝大家节日快乐,有心仪对象的伙伴,如果还没有表白的那要抓紧时间了,今天是一个很好的机会! 没有对象的伙伴也不要担心,今天我给你 new
一个对象出来,一个貌美“如花” 的对象!
本文开始Java内存管理第二季,如果你还没有阅读过上一季的内容,我推荐你先阅读一下上一季:第一季总结:由浅入深JAVA内存管理 Core Story
稍微再回顾一下第一季的一些核心内容,我们知道了Java 虚拟机,知道了JVM的内存模型,以及类加载模型和机制,并实现自定义加载器。虽然第一季内容看似比较多,但是对于优秀的Java虚拟机来说,还仅仅是个开端,JVM里面的东西还多着呢! 从大方面对虚拟机和虚拟机的内存有了了解后,让我们也来窥探一下内存中数据的一些细节,如 如何创建一个对象,内存的布局,以及如何进行访问。也就是本文讲解HotSpot虚拟机在Java堆中对象是如何创建、内存分配布局和访问方式。
给你创建一个对象
如果你是一直从第一季看过来的,那一定知道前面有个地方讲过类的整个生命周期,之前只是讲到了初始化阶段,类是如何使用和类是如何被卸载还没有进行讲解!那本文就简单介绍一下类的使用,我们 new
一个 “如花” 似玉的 girl
!
这里再回顾一下,类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了七个阶段:
-
加载(Loading)
-
验证(Verification)
-
准备(Preparation)
-
解析(Resolution)
-
初始化(Initialization)
-
使用(Using)
-
卸载(Unloading)
在Java中我们用使用一个类,很多时候是创建这个类的一个实例,也就是常说的创建一个对象。其实在Java程序运行过程中,无时无刻都有对象被创建出来。创建对象(如克隆、反序列化)通常仅仅是一个 new
关键字而已。但是在Java虚拟机中一个对象(只是普通的java对象,不包括数组和Class对象等)的创建是怎么一个过程呢?
第一:虚拟机遇到一条 new
指令时,首先会去检查这个指令的参数是否能够在常量池中定位到一个类的符号引用。然后检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有进行类加载则执行相应的类加载的过程。 记住:要new对象,要先加载类!
第二:类加载检查通过后,虚拟机将为新生的对象分配内存。对象所需的内存大小在类加载的时候便可以完全确定(如何确定对象的下文说明) 。为对象分配内存的任务等同于把一块确定大小的内存从Java堆中划分出来(对象在堆上的划分,这是个复杂的问题,后文继续探讨,这里只要明白是在对象是在堆上分配内存即可)。 记住:要new对象,要有先分配内存空间!
第三:内存分配完成,虚拟机需要将分配的内存空间都初始化为零值(零值这个概念之前文章也介绍过,这里就不再说明),这一步的操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,因为程序能访问这些字段的数据类型对应的零值。 记住:要new对象,虚拟机会帮你为对象的实例字段自动赋予零值!
第四:虚拟机要对对象进行必要的设置,如这个对象是哪个类的实例、如何才能找到类的元数据信息(JDK7是方法区保存)、对象的哈希码、对象的GC分代年龄等信息。这些信息都存放在对象的对象头(Object Header)中。
上面工作都完成之后,在虚拟机看来,一个对象就已经产生了。但是从Java程序的角度看,对象的创建才刚刚开始,因为 <init>
方法还还没有执行,所有的字段都是为零值。所以一般来说,在 new
指令之后会接着执行 <init>
方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来!
记住:对象不是你想new,想new就可以new的!
下面用通过图解的例子简单说明(版本jdk1.7):
第一: 一个PrettyGirl类!
-
public class PrettyGirl {
-
-
/**
-
* 姑娘姓字名谁
-
*/
-
String name;
-
-
/**
-
* 芳龄几何
-
*/
-
int age;
-
-
/**
-
* 家住何方
-
*/
-
static String address;
-
-
/**
-
* 可曾婚配否
-
*/
-
boolean marry;
-
-
void sayHello(){
-
System.out.println("Hello...");
-
}
-
-
}
方法区默认最大容量为 64M,方法区除去保存类的结构,还保存静态属性与静态方法。编写中小型程序时,一般不会造成方法区的内存溢出!在JDK1.8 没有方法区的概念,前面文章中也有提到,这里为了讲解使用图解还是JDK1.7!
第二:实例化new两个漂亮女孩!
-
public static void main(String[] args) {
-
PrettyGirl pg1 = new PrettyGirl();
-
pg1.name = "Alice";
-
pg1.age = 18;
-
pg1.address = "changsha";
-
-
PrettyGirl pg2 = new PrettyGirl();
-
pg2.name = "Alexia";
-
pg2.age = 28;
-
-
System.out.println(pg1 + " ---" + pg1.address);
-
System.out.println(pg2 + "----" + pg2.address);
-
-
}
-
----打印结果:--------
-
PrettyGirl{name='Alice', age=18, marry=false} ---changsha
-
PrettyGirl{name='Alexia', age=28, marry=false}----changsha
!
在栈内存为 pg1 变量申请一个空间,在堆内存为PrettyGirl对象申请空间,初始化完毕后将其地址值返回给pg1 ,通过pg1 .name和pg1 .age修改其值,静态的变量address是类公有的!
堆默认最大容量为 64M,堆存放对象持有的数据,同时保持对原类的引用。可以简单的理解为对象属性的值保存在堆中,对象调用的方法保存在方法区。
由于时间原因,本文今天就只为你new一个对象! 下一文介绍 对象的内存布局和 如何“约”(定位)一个对象! 敬请期待!
因为我也是在整理和学习中,如果文中内容有不对的地方,欢迎留言指出,谢谢!
参考资料
《深入理解Java虚拟机》