NO.11 对象是怎样炼成的 | Java敲黑板系列
开场白
老铁 :Java是一门完全面向对象的语言,在我们的编程过程中,对象无处不在,为此我们对Java对象已经司空见惯。但是,对于我们经常使用的技术,我们真的理解对象吗?
其中,一个常见的对象认识误区就是,我们常会低估JVM创建对象所带来的成本。实际上,JVM在创建一个对象时,其空间、时间成本比我们通常意识到的要高得多。
对象在内存中构建过程
对象构建并不是我们通常所理解的只是进行内存分配、初始化成员变量等简单内容。真实的构建过程包括了很多步骤,通过对这些步骤的深入理解,可以指导我们编程过程中精简对象并减少对象数量,提升JVM的内存利用率与运行效率。以下是对象在内存中构建过程:
敲黑板
- 从堆上分配内存,该内存用于存放对象的全部实例变量,以及父类方法地址信息。
- 对象的实例变量被初始化为相应的缺省值。如int类型为0;double类型为0.0;对象类型为null;布尔类型为false。
- 按照继承深度,从浅到深依次调用父类的构造函数,这个一直持续到调用java.lang.Object构造函数位置。(Object类是所有Java类的祖先类)为此,最顶层的父类(Object类)构造函数最先执行,本对象的构造函数最后执行。
- 在上述所有构造函数执行之前,先执行本类的实例变量的初始化赋值与初始化区块语句,然后再执行构造函数的语句。
对象分类
从对JVM构造对象的复杂度出发,我们可以将对象分为以下两类:
轻型对象:继承深度较浅,并且不包含许多其他对象。
重型对象:轻型对象以外的其他对象。
根据上述不同类型对象的定义,并结合上述对象在内存中的构建过程可知,重型对象构造步骤更加复杂,所耗费的堆内存也更大。
轻型对象构造过程
轻型对象应用举例,如代码1所示:
class Light{
private int count; //实例变量
private boolean isDone = true; //实例变量;初始化
//构造函数
public Light(int count){
this.count = count;
}
//....
}
下面我们结合下述语句来说明轻型对象是如何进行构建的:
Light light = new Light(100);
- 从堆中分配内存,用来存放Light class的实例变量,以及父类方法地址信息;
- 实例变量count、isDone分别初始化为相应缺省值0,false;
- 调用构造函数,传入数值100;
- 构造函数调用其父类Object类的构造函数;
- Object构造函数执行返回后,对Light类的实例变量执行初始化操作,将isDone赋值为true;
- 将this.count赋值为100;Light构造函数执行完成并返回;
- 对象引用light执行在heap中刚刚建立完成的Light对象;
- 打完收工。
重型对象构造过程
重型对象应用举例,如代码2所示:
import java.awt.Color;
import java.awt.Point;
class animal{
private String name;//实例变量
public animal(String n){
this.name =n;
}
}
class bird extends animal{
private boolean canFly = true;//实例变量;初始化
public bird(String n,boolean b){
super(n); //显式调用父类
this.canFly = b;
}
}
class parrot extends bird{
private Point location;//实例变量
private Color color;//实例变量
public parrot(String n, boolean b){
super(n,b);//显式调用父类
this.location = new Point(1,1);//新生成Point对象
this.color = new Color(0,255,255);//新生成Color对象
}
}
下面我们结合下述语句来说明轻型对象是如何进行构建的:
parrot p = new parrot(“air”, true);
首先,parrot类继承了bird,bird类继承了animal,animal继承了Object类,可见parrot类的继承深度为3;此外,parrot类还包括了Color类对象、Point对象,为此parrot类完全符合重型对象的定义。下面,我们来看看重型对象是如何产生的:
- 从堆中分配内存,用来存放p的实例变量(location、color)、bird类的实例变量(canFly)、以及animal类的实例变量(name),以及父类方法地址信息;
- 分别对上述实例变量初始化为相应缺省值。对象引用location、color、name被初
- 始化为null;canFly被初始化为false;
- 调用parrot构造函数,传入“air”、true;
- parrot构造函数调用父类bird的构造函数;
- bird构造函数调用父类animal的构造函数;
- animal的构造函数调用父类Object的构造函数;
- Object构造函数返回后,animal构造函数将其实例变量name赋值为“air”,然后返回;
- bird构造函数进行实例变量初始化,将canFly赋值为true,然后返回;
- parrot构造函数本体开始执行,建立一个Point对象与一个Color对象。此时,分别针对这两个对象分别有从上述的步骤1开始重复执行全部过程(这也是为什么不要包含太多类对象的原因);
- 对象引用p指向堆内存中创建的parrot对象;
- 再次打完收工。
怎么办?
如上所述,创建一个重型对象需要更多的步骤;为此,重型对象比建立轻型对象的性能相差很多。当一个类符合如下特征的时候,我们就要小心我们是否已经上了重型对象的“道”了:
- 构造函数中包括了大量的代码;
- 类定义中包含了大量的类对象;
- 太深的继承层次。
敲黑板
每一个被创建出来的对象,也是垃圾回收器跟踪和可能释放的目标。所以,不仅仅建立对象需要付出代价,垃圾回收器管理与回收这些对象同样也需要付出代价。
通过性能评测,确定是因为重型对象而造成的性能瓶颈,可以采用以下方法进行优化:
- 对类进行“瘦身”。重新设计这个class,将这个class分解为多个轻型class,并使得性能需求最高的代码只使用轻型class;
- 使用延迟初始化技术;
- 尽可能复用对象。
转载自公众号:代码荣耀
下一篇: Dialog的Window创建过程