详解Java代码常见优化方案
首先,良好的编码规范非常重要。在java程序中,访问速度、资源紧张等问题的大部分原因,都是代码不规范造成的。
单例的使用场景
单例模式对于减少资源占用、提高访问速度等方面有很多好处,但并不是所有场景都适用于单例。
简单来说,单例主要适用于以下三个方面:
- 多线程场景,通过线程同步来控制资源的并发访问。
- 多线程场景,控制数据共享,让多个不相关的进程或线程之间实现通信(通过访问同一资源来控制)。
- 控制实例的产生,单例只实例化一次,以达到节约资源的目的;
不可随意使用静态变量
当某个对象被定义为static变量,那么GC通常是不会回收这个对象所占有的内存。
示例如下:
publicclassA{ privatestaticBb=newB(); }
此时静态变量b的生命周期与A类同步,即如果A类不卸载,b对象会常驻内存,直到程序终止。
创建Java对象使用注意事项
根据业务使用场景,尽量避免在循环中new对象。
这是因为系统要花费时间来创建对象,而且还要花时间对这些对象进行管理和垃圾回收。所以在可以控制的范围内,尽量保证最大限度地重用对象,最好能用基本的数据类型或数组来替代对象。
final修饰符使用注意事项
final修饰符的类是不可派生的,即不可被继承。在java核心代码中,有很多被final修饰的类,如java.lang.String类。
如果一个类是final的,则该类所有方法都是final的。java编译器会寻找机会内联(inline)所有的final方法,这与具体的编译器实现有关。这样做能够使性能平均提高50%。
示例如下:
classA{ publicvoidsetSize(intsize){ this.size=size; } privateintsize; } //setSize方法变为final,性能更好 classA{ finalpublicvoidsetSize(intsize){ this.size=size; } privateintsize; }
让访问实例变量的getter/setter方法变成final:简单的getter/setter方法应该被置成final,这会告诉编译器此方法不会被重载,可以变成”inlined”。
局部变量使用规范
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快;其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。
处理好包装类型和基本类型的使用场所
基本类型:byte,short,int,long,float,double,char,boolean
对应包装类型:Byte,Short,Int,Long,Float,Double,Character,Boolean
基本类型和包装类型在使用过程中可以相互转换,但它们所产生的内存区域是完全不同的。基本类型的产生和处理都在栈中处理,包装类型是引用类型,其对象是在堆中产生实例。
在集合类对象,有对象方面需要的处理使用包装类型合适,其他情况的处理提倡使用基本类型。
使用基本数据类型代替对象
示例如下:
Strings1="hello";
这种方式会创建一个“hello”字符串,而且JVM的字符缓存池会缓存这个字符串。
Strings2=newString("hello");
这种方式除了创建字符串外,s2所引用的String对象底层包含一个char[]数组,其中依次存放了h,e,l,l,o。
synchronized使用规范
实现同步需要很大的系统开销作为代价的,甚至可能造成死锁。所以尽量避免无谓的同步控制。
synchronize方法被调用时会直接把当前对象锁住,在该方法执行完之前其他线程无法调用当前对象的其它方法。比较灵活的用法是使用代码块同步代替在方法中同步。
finalize使用规范
不要将资源清理放在finalize方法中完成,这种方法也很少使用。
由于GC的工作量很大,尤其是回收Young代内存时,大都会引起应用程序暂停。如果选择使用finalize方法进行资源清理,会导致GC负担加大,程序运行效率变差。
不需要线程同步,应尽量使用HashMap、ArrayList
HashTable、Vector等使用了同步机制,导致降低。
HashMap使用规范
创建一个比较大的hashMap时,应该使用带有参数的构造函数创建对象。
示例如下:
publicHashMap(intinitialCapacity,floatloadFactor);
hash扩容是一件很耗费性能的事,默认构造函数创建的对象的initialCapacity只有16,loadFactor是0.75,最好准确的估计所需要的最佳大小。同样对于Hashtable,Vectors也是如此。
减少对变量的重复计算
for(inti=0;i避免不必要的创建对象
Aa=newA(); if(i==1){ list.add(a); } //应该改为 if(i==1){ Aa=newA(); list.add(a); }finally使用规范
在try-catch里,使用到的资源要能够被释放,以避免资源泄漏,这最好在finally块中去做。无论程序执行是否有异常,finally里的代码总是会执行的,这样可以确保资源的正确关闭。
StringBuffer使用规范
StringBuffer的无参构造函数会创建一个默认16的字符数组。在使用过程中,如果数组长度超出16,就要重新分配内存,创建一个容量更大的数组,并将原先的数组复制过来,再丢弃旧的数组。
在多数情况下,可以在创建StringBuffer的时候指定大小,避免了在容量不够的时候自动增长,以提高性能。
StringBuffersb=newStringBuffer(intcapacity);显式释放空间让gc回收对象
多数情况下,方法内部的局部引用变量所引用的对象会随着方法结束而变成垃圾被回收。因此在程序中无需将局部引用变量显式设为null。
示例如下:
voidgcTest1(){ Objectobj=newObject(); …… obj=null; }随着方法gcTest1()的执行完成,程序中局部引用变量obj的作用域就结束了,这时没有必要执行obj=null。
反例如下:
voidgcTest2(){ Objectobj=newObject(); …… obj=null; //耗时,耗内存操作 …… }此时需要尽早释放不再使用的空间,执行obj=null显式释放局部引用变量obj。
二维数组使用规范
二维数据占用的内存空间大概是一维数组的10倍以上。
split使用场景
尽量避免使用split。split使用正则表达式,效率比较低,如果是频繁的调用将会耗费大量资源。
如果确实需要频繁的调用split,使用apache的StringUtils.split(string,char)较好,可以缓存结果。
ArrayList和LinkedList使用规范
对于线性表及链表,随机查询的操作ArrayList优于LinkedList,LinkedList需要移动指针。增加删除的操作LinkedList优于ArrayList,ArrayList需要移动数据。
System.arraycopy()使用规范
尽量使用System.arraycopy()复制数组,它要比通过循环来复制数组快的多。
缓存对象
将经常使用的对象进行缓存时,可以使用数组或者HashMap等容器来缓存。这种方式需要自己管理这些容器,可能导致系统占用过多的缓存,性能下降。
也可以使用一些第三方的开源工具,如EhCache、Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。
尽量避免非常大的内存分配
有的问题不是由于堆内存不够造成的,而是因为内存分配失败造成的。(gc会进行内存碎片整理)
如果分配的内存块都必须是连续的,随着堆越来越满,找到较大的连续块会越来越困难。
try/catch使用场景
不要在循环中使用try/catch语句,应该把try/catch放在循环最外层。
以上所述是小编给大家介绍的Java代码常见优化方案详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!