熟悉 Java 的内存分配、垃圾回收机制是为了更好的管理 JVM 内存,这样才能提高程序的运行性能,下面列出几个内存管理的小技巧。
1 尽量使用直接量
当需要使用字符串,以及基本类型对应的包装类(Byte、Short、Integer、Long、Float、Double、Boolean、Character)的实例时,程序应该直接采用直接量来创建,而不建议采用 new 的方式。如:
String str = "hello";
该代码创建了一个“hello”字符串,而且 JVM 的字符串缓存池还会缓存这个字符串。
但若使用 new 的方式:
String str = new String("hello");
同样创建了一个缓存在字符串缓存池中的“hello”字符串,此外 str 所引用的 String 对象底层还包含了一个char[] 数组,这个数组里依次存放了 h、e、l、l、o 等字符。
2 使用 StringBuilder 和 StringBuffer 进行字符串连接
String、StringBuilder、StringBuffer(线程安全) 都可以定义字符串,String 代表字符串序列不可变的字符串,而 StringBuilder 和 StringBuffer 都代表字符序列可变的字符串。
若使用多个 String 对象进行字符串连接运算,在运行时将生成大量临时字符串,这些字符串会保存在内存中从而导致程序性能下降。
3 尽早释放无用对象的引用
大部分时候,方法局部引用变量所引用的对象会随方法结束而变成垃圾,因为局部变量的生存时间很短,当方法运行结束后,该方法内部的局部变量就结束了生命周期。因此,大部分时候程序无需将局部、引用变量显式释放(置空)。
public void fun(){
Object obj = new Object();
// 操作obj
obj = null;
// 执行耗时、耗内存的操作
}
但是在用完对象之后,代码还需要执行耗时、耗内存的操作(或调用耗时、耗内存的方法),那置空就是必要的:这样可以尽早释放对 Object 对象的引用。因为在执行耗时、好内存的操作期间,垃圾回收器可能开始回收 obj 之前所引用的 Object 对象。
4 尽量少用静态变量
由于垃圾回收机制判断一个对象是否是垃圾的标准是该对象是否有引用变量引用它,因此建议尽早释放对象的引用。
而如果某个对象是被静态变量所引用的,那么垃圾回收器通常不会回收这个对象,如:
class Person {
static Object obj = new Object();
}
对于 Object 对象而言,只要 obj 变量还引用到它,它就不会被回收。
obj 是静态变量,因此它的生命周期与 Person 类同步,在 Person 类不被卸载的情况下,Person 类对应的 Class 对象会常驻内存,直到程序运行结束。
JVM 会将程序中的类信息存入 Permanent 代,即 Person 类、静态 obj 引用变量都将存在 Permanent 代理,所以 obj 变量一直有效,其所引用的对象也不会被回收。
5 避免在经常调用的方法、循环中创建对象
循环一般写在方法内,如果在循环内创建对象,虽然局部变量在循环结束时这些局部变量会失效,但由于循环导致对象会不断被创建,因此系统需要不断为创建的对象分配内存空间,执行初始化操作,这些对象的生存时间不长,接下来系统又需要回收它们所占的内存空间。
在这种不断分配、回收操作中,加重了系统负担,程序的性能当然会受到一定程度的影响。
6 缓存经常使用的对象
若某些对象需要被经常使用,可以考虑把这些对象用缓存池保存起来。典型的缓存就是数据库连接池,数据库连接池里缓存了大量数据库连接,每次程序需要访问数据库时都可直接取出数据库连接使用。
此外,若系统中还有一些常用的基础信息,如信息化系统里包含的员工信息、物料信息等,也考虑进行缓存。
实现缓存时通常有两种方式:
1) 使用 HashMap 进行缓存;
2) 使用某些开源的缓存项目。
若使用 HashMap,需要手动控制 HashMap 中的 key-value 对不至于太多,否则会导致 HashMap 占用过大的内存,以致系统性能下降。
对于开源的缓存项目,这些缓存项目都会主动分配一个一定大小的缓存容器,再按照具体算法清掉容器中不需要继续缓存的对象。这样既可以通过缓存常用对象来提高系统的运行效率,又可以控制缓存容器避免无限制扩大,从而减少系统内存开销。
开源缓存项目如:OSCache、Ehcache,大都实现了 FIFO、MRU(Most recently used,最近最常使用算法) 等常见的缓存算法。
7 尽量不要主动调用 finalize 方法
垃圾回收器回收对象前会先调用该对象的 finalize() 方法来执行资源清理。处于尽早释放资源的考虑,可能开发者会考虑使用 finalize() 方法来进行资源清理。
而实际上,将资源清理放在 finalize 方法中完成不是很好的选择,垃圾回收机制的工作量已经够大了,尤其是回收 Young 代内存时,大都会引起应用程序暂停,使得用户难以忍受。
在垃圾回收器本身已经严重制约应用程序性能的情况下,再主动调用 finalize 方法进行资源清理,这将导致垃圾回收器的负担更大,从而导致程序运行效率更差。
8 考虑使用软引用对象
当程序需要创建长度很大的数组时,考虑使用软引用(SoftReference)来包装数组元素,而不是直接让数组元素来引用对象。
当内存足够时,软引用等同于普通引用;当内存不够时,会被垃圾回收器回收。
由于通过软引用获取的对象可能为 null,因此在使用取出的软引用时,要显式的进行判断是否 null。
参考资料:
疯狂Java:突破程序员基本功的16课-Java 的内存回收