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));}}
// 扩展 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);
// 扩展 Map 的代码 if (size++ >= threshold) resize(2 * table.length);
Map m = new HashMap();Iterator it = m.entrySet().iterator();while (it.hasNext()) {Entry entry = (Entry) it.next();request.setAttribute((String)entry.getKey(), entry.getValue());}
//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();}
public int add(int i) {int j = i ; //使用本地变量for(int k=0; k < 10000; k++) {i += 1;}i = j;return i;}