Java作为一种面向对象的编程语言,从JVM中底层的内层分配,垃圾回收到编译运行期间对象的构造都有着很多的优化,诸如单例模式,工厂类,枚举,常量池,防止内存泄漏等
一.对象的创建
1.使用构造器
对于每一个类,JVM都会默认生成一个无参的构造器,如果有构造函数被声明,那么JVM将不再自动创建无参构造器,如果仍然想调用无参构造器,就需要声明一个无参的构造函数
/**
* Constructs a new object.
*/
@HotSpotIntrinsicCandidate
public Object() {}
对于多个包含参数的构造器,都应该优先使用只包含必要(required)参数的构造器,虽然该构造函数会调用包含可选(optional)参数的构造器,但这样不会给调用者带来不知道多余参数如何设置以及参数顺序错误等问题
2.使用构建器
在构造多个参数的构造器,而且参数个数和类型可能会发生变化时,可以用构建器模式(Builder),这种方式对于构造多个不同类型参数的对象较便利
通用接口:
public interface Builder<T> {
public T build();
}
方法实现:
public class NutritionFacts {
private final int calories;
public static class NutritionFactsBuilder implements Builder<NutritionFacts>{
private int calories = 0;
public NutritionFactsBuilder calories(int val) {
this.calories = val;
return this;
}
@Override
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(NutritionFactsBuilder builder) {
calories = builder.calories;
}
}
3.私有构造器
对静态方法getInstance()方法的调用,都会返回一个对象引用,不会创建其他实例:
//singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
public static Elvis getInstance() {
return INSTANCE;
}
public void leaveTheBuilding() {...}
}
此外,也可以使用枚举类构造单例对象:
//Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {...}
}
通过私有构造器还可以构造不可实例化的对象(利用JVM实例化中如果没有有参构造器,就调用无参构造器的逻辑),将无参构造器访问限制为private类型:
//Noninstantiable utility class
public class UtilityClass {
//Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
... //Remainder omitted
}
4.避免创建不必要的对象
如果对象是不可变的,就可以被重用,这一点可以参考String常量池的设计
静态工厂方法Boolean.valueOf(String)相比于构造器Boolean(String)不会重复在调用时构造对象:
使用构造器创建对象:
class Person {
private final Date birthDate;
public boolean isBabyBoomer() {
//Unnecessary allocation of expensive object
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT");
gmtCal.set(1946, Calendar.JANUARY, 1,0,0,0);
Date BOOM_START = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1,0,0,0);
Date BOOM_END = gmtCal.getTime();
return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
}
}
使用静态初始化域来减少对象构建:
class Person {
private final Date birthDate;
/**
*The starting and ending dates of the baby boom
*/
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1,0,0,0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1,0,0,0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0;
}
}
关于自动装箱中基本类型及其对象类型共存中实例对象创建的问题:
psvm() {
Long sum = 0L;
for(long i=0;i<Integer.MAX_VALUE;i++) {
sum += i;
}
sout(sum);
}
这里在对象声明中使用了Long对象类型创建了2的31方个Long实例,导致程序运行速度不及预期,应该使用long基本数据类型,避免不必要对象的创建
5.对象的销毁
JVM通过引用机制来管理对象的生命周期,但如果当前对象的内存管理是程序员自己管理,就可能会出现内存泄漏问题,需要程序员在代码设计中主动销毁不再引用的对象
1.程序员自己管理的内存:
public Object pop() {
int len = size();
if(size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
//在Stack源码中并没有关于引用清零的操作
elements[size] = null;
return result;
}
如果没有对栈中的元素引用进行清零,那么就可能会带来内存泄漏的问题,需要程序员自己根据逻辑来设计
2.监听器和其他回调
如果你实现了一个API,客户端在这个API中注册回调,却没有显式取消注册,如果不采取某些行动(比如手动取消注册或只保存它们的弱引用,eg:只将它们保存成WeekHashMap键),否则这些对象就会积聚
3.缓存
一旦你把对象引用放到缓存中,如果它在很长时间内仍然留在缓存中,就会造成内存泄漏,可以用WeekHashMap实现缓存(该缓存项的生命周期由该键的外部引用情况决定,而不是由值决定),当缓存过期后,它们就会被自动删除
对于由JVM管理内存的其他一些对象,我们可以手动销毁这些对象,但请避免使用终结方法(不可预测,非必要很危险):
终结方法不能保证被立即执行,从一个对象不再被引用到终结方法执行的所花费的时间不确定,终结方法线程优先级低,而且会带来非常严重的性能损失(非集中式的垃圾回收),如果要调用finalize()方法,需要在finally结构中调用super.finalize()
如果需要释放所占用的系统资源,可以使用try-finally结构
对于常用的系统资源,可以使用InputStream & outputStream & java.sql.connection上的close()方法以及Timer上的cancel()方法来释放资源占用