首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > PowerDesigner >

Java 程序的优化札记(110708 update)

2012-12-18 
Java 程序的优化笔记(110708 update)系统的优化是一个比较宽泛的话题,涉及到硬件、软件和网络的优化等,软件

Java 程序的优化笔记(110708 update)
系统的优化是一个比较宽泛的话题,涉及到硬件、软件和网络的优化等,软件又包括操作系统、数据库和应用软件优化等。
优化没有一个特定的规则,如何进行优化往往以特定的架构设计下为前提。所以说好的架构设计不仅方便整个系统后期的维护和扩展,还有对系统的优化也会有很大的影响。
以下只针对 Java 语言分别从虚拟机、技术框架和代码编写三个方面来简单说明一下。

一、Java 虚拟机
在保障稳定的前提下,尽量使用新的虚拟机版本,每次虚拟机版本的提升都会带来性能的提高。例如新版本中 Java 反射的性能也有很大的提高。

使用新垃圾收集机制,因为虚拟机主要的性能瓶颈之一就是垃圾回收,不论是年经代还是年老代的回收。JDK 6.0 增加了新的并发的回收机制。
默认情况下虚拟机运行在 Client 模式下,一般服务器下建议使用 Server 模式,只需要在命令行加上 -server 参数。
JVM给了三种收集器:串行收集器、并行收集器、并发收集器。以下列出主要的配置参数:
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片。

根据实际的情况,优化虚拟机配置参数,主要考虑吞吐量优先还是响应时间优先。来设置虚拟机整个内堆的大小,再分别调整年轻代、年老代和持久代的大小。
响应时间优先的应用年轻代尽可能设大,年老代使用并发收集器。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

以下是一个 Linux 环境 JDK 6.0  下虚拟机参数的配置:
-Xms256m -Xmx256m -Xmn96m -Xss256k -XX:PermSize=128m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection
-XX:CMSInitiatingOccupancyFraction=80 -XX:LargePageSizeInBytes=8m -XX:+DisableExplicitGC -XX:+UseFastAccessorMethods -Dfile.encoding=GBK

垃圾回收统计信息:
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:PrintHeapAtGC
-Xloggc:filename


二、Java 新技术

1、使用 JDK 本身的新技术。每次虚拟机版本的提升不仅会带来性能的提高,同时也会有新的技术,新技术往往会很好的解决以前的技术难题。
1.4 版的新 IO 接口,通过非阻塞的 IO 极大提高的磁盘文件读写和网络通信的性能
5.0 版的泛型、Annotation等等都给编程带来极大的方便。另外,java.util.concurrent 包的类极大提高并发程序的效率。
6.0 版整体性能提升的同时,也增加了新的并发的垃圾回收机制,使得大并发的情况下程序运行的反应时间更快。

2、使用第三方的新技术。
还有其它三方的工具类来代替 JDK 本身的工具类。例如 Kryo 代替本身续列化;Joda-Time 代替本身的 Calendar 操作;使用 fastutil 代替本身的容器类;Logback 代替本身的日志记录等等。
第三方的软件来解决特定的问题。例如字节码操作代替反射;选择类似 FreeMarker 模板技术代替 JSP 显示页面;使用 proxool 等数据库连接池技术等等。


三、Java 最佳实践编码

1、尽量使用位操作是直接对二进制位的操作,速度最快。
一个整数 i 乘以 2 的操作正常应该为 i * 2 ,实际上我们可以使用 i << 1 ,当然 i 除以 2 的是右移操作
判断一个整数 i 是否为奇数:(i & 1) == 1
类似多个选项的选择问题,例如多项选择题选项 A B C D  ,程序中可以如下设置:

public class Test {public static final int A = 0x0001; // 二进制 0001public static final int B = 0x0002; // 二进制 0010public static final int C = 0x0004; // 二进制 0100public static final int D = 0x0008; // 二进制 1000public static void main(String[] args) {// 根据选择项的不同进行或操作,例如结果选择 A B Cint i = Test.A | Test.B | Test.C;System.out.println(Integer.toBinaryString(i));// 根据结果判断选择项进行与操作int j = i & Test.A;System.out.println(Integer.toBinaryString(j));}}

这样操作不仅结果存储很小,同时操作也很方便。


2、容器类对象的处理。也就是 List 和 Map 对象的优化。
如果容器的对象数量确定,尽量使用数组代替容器类,并且数组复制使用 System.arraycopy() 方法
使用List 或 Map 时尽量设置初始值。
List l = new ArrayList(100); // 如果不指定初始值,默认的大小是 10
如果 size 不够则会再创建一个新的数组,大小为旧数据的 1.5 倍加1,并将旧数据复制过去。
   // 扩展 List 的代码    Object oldData[] = elementData;    int newCapacity = (oldCapacity * 3)/2 + 1;        if (newCapacity < minCapacity)newCapacity = minCapacity;            // minCapacity is usually close to size, so this is a win:            elementData = Arrays.copyOf(elementData, newCapacity);

如果不设置大小,随着 List 的增加它将频繁的复制数据。
Map m = new HashMap(100, 0.8); // 如果不指定初始值,默认的大小分别是 16 和 0.75
当大小超过 初始值乘以加载因子,即超过 100*0.8=80 时就会重新创建内部结构,扩大为原来的 2 倍。
  // 扩展 Map 的代码        if (size++ >= threshold)            resize(2 * table.length);

如果不设置初始值,随着 Map 的增加它也会频繁的复制数据。
取得 Map 容器中的 Key 和 Value 使用 Entry
Map m = new HashMap();Iterator it = m.entrySet().iterator();while (it.hasNext()) {Entry entry = (Entry) it.next();request.setAttribute((String)entry.getKey(), entry.getValue());}


3、使用 StringBuffer 和 StringBuilder 代替 + 号的字符串连接

4、不需要继承的类,尽量指定它的 final 修饰符

5、不需要外部访问类的方法和属性,尽量指定它的 private 修饰符

6、尽量重用已经创建的对象,例如可以通过缓存对象来重用
创建一个对象并不是 new 一下那么简单,它要耗费 CPU 和内存资源,特别是继承比较深或者属性复杂的类。

7、尽量降低同步带来的开销,例如使用同步块代替同步函数并且使用不同的锁
同步操作需要增加相关的虚拟机指令,不仅增加类文件的大小而且执行的指令比其它指令更耗资源。
如果是 JDK 5.0 ,同步操作尽量使用 java.util.concurrent 提供的技术,例如 Executors、CopyOnWriteArrayList、ConcurrentMap、ReentrantLock、AtomicInteger 等等。
//private List<Listener> listeners = new ArrayList<Listener>();public boolean addListener(Listener listener) {synchronized (listeners) {return listeners.add(listener);}}private List<Listener> listeners = new CopyOnWriteArrayList <Listener>();public boolean addListener(Listener listener) {return listeners.add(listener);}//privateMap<String, Object> map = new HashMap<String, Object>();public Object getObj(String key) {synchronized (map) {Object bean = map.get(key);if (bean == null) {map.put(key, createObj());bean = map.get(key);}return bean;}}private ConcurrentMap<String, Object> map = new ConcurrentHashMap<String, Object>();public Object getObj(String key) {Object bean = map.get(key);if (bean == null) {map.putIfAbsent(key, createObj());bean = map.get(key);}return bean;}//private final ReentrantLock lock = new ReentrantLock();public void m() {      lock.lock();  // block until condition holds     try {       // ... method body     } finally {       lock.unlock()     }}//private volatile int count = 0;public synchronized void increment() {count++;}private AtomicInteger count = new AtomicInteger();public void increment() {count.incrementAndGet();}


8、尽量使用栈变量(方法的本地变量)和基本类型
public int add(int i) {int j = i ;  //使用本地变量for(int k=0; k < 10000; k++) {i += 1;}i = j;return i;}


热点排行