java内存优化实践
一般在java程序中,内存是个比较头痛的话题。虽然jvm能够通过GC机制很智能地回收资源,但是由于内存的释放都是jvm在进行操作,不恰当的使用会导致java的程序内存持续增大,直至最终OOM(out of memery)
那么,如何对java进行内存优化呢?一方面可以通过调整jvm的一些配置(内存,GC等),从jvm层优化配置;另一方面,从java程序角度,在代码层次上进行优化。
近期,做了些在java程序方面内存优化实践,积累了一些心得,总结如下:
-
对于资源消耗较大的对象(如数据库连接,Stream,大的collection等),尽早进行释放,还得考虑到异常情况
对于这种情况,就没必要释放obj了,因为在执行完func后,GC会自动回收资源
Public void func(){
Object obj =new Object();
……
Obj=null;
}
但是对于这类操作就有必要提前释放了,特别是一些连接资源,大内存对象等
Public void func(){
Object obj =new Object();
……
Obj=null;
xxxx; // 耗时操作
}
而且对于数据库资源/stream,在释放的时候,要考虑到出现exception的情况,建议在finalize中释放连接
public static Properties loadProperties(String fileName) throws IOException {
FileInputStream stream = new FileInputStream(fileName);
try {
Properties props = new Properties();
props.load(stream);
return props;
}
finally {
stream.close();
}
} -
优化逻辑,避免不必要对象的创建,且尽量使用局部变量
由于局部变量是在stack上进行分配的,速度较快,且生命周期是固定的,同时可以优化程序的逻辑,避免一些不必要的逻辑的创建,如A a = new A();
if(i==1){list.add(a);}应该改为
if(i==1){
A a = new A();
list.add(a);
}
还有,比如:
FileInputStream stream = new FileInputStream(fileName);
if (xxx) {
return;
}
stream.xxx;
应该改成:
if (xxx) {
return;
}
FileInputStream stream = new FileInputStream(fileName);
stream.xxx; -
优化循环逻辑,减少循环逻辑里对象的创建和调用
for (int i = 0; i < xxx.size(); i++) {
xxx
}
这样xxx.size()会被调用多次,效率较低,可以使用:
for (int i = 0, len = xxx.size(); i < len; i++) {
xxx
}
同样要在循环里面对于对象的创建要慎重,如
for (int i = 0; i < 1000; ++i) {
Map myMap = new HashMap<>();
myMap.xxx;
}
应该改成
Map myMap = new HashMap<>();
for (int i = 0; i < 1000; ++i) {
myMap.clear();
myMap.XXX();
} -
使用基本类型替代对象类型
比如使用int替代Integer,long替代Long等,数组替代vector等,减少对象创建。而且函数中,基本类型是存放于栈上创建和使用效率较高。 -
使用stringBuffer和stringBuilder替代多次String对象
因为String对象是定长的,任何关于String的变更操作都会导致新的String对象创建。因此可以在合适的时机使用stringBuferr或者stringBuilder替代String对象,单线程使用StringBuilder,多线程情况下使用StringBuffer。 -
提前分配stringBuffer,数组,array,vector等容量
对于需要连续分配的对象,最好先提前分配容量,一般默认的初始容量都比较小,如stringBuffer的容量是只有16,如果按照默认的容量很容易在容量满了之后重新分配一块大的资源,耗时费内存。因此可以通过提前分配资源的方式避免这个情况 -
缓存经常要使用的对象和数据
对于经常需要使用的对象和数据,可以使用缓存存起来(比如内存中的共享内存,或者redis,memcached等remote cache server),减少一些对象的重复创建计算代价 -
慎用exception
当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。因此要慎用异常,一般而言,异常是只有当前处理不了的异常才往上层抛,否则可以通过函数返回值(bool false,object=null)等方式向上呈现结果。 -
数组复制时,使用System.arraycopy()命令。
arrayCopy命令比循环复制数组快很多