總結日常Java開發常見優化策略,持續更新。
盡可能使用局部變量
調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧中,速度較快,其他變量,如靜態變量、實例變量等,都在堆中創建,速度較慢。另外,棧中創建的變量,隨着方法的運行結束,這些內容就沒了,不需要額外的垃圾回收。
及時關閉流
Java編程過程中,進行數據庫連接、I/O流操作時務必小心,在使用完畢后,及時關閉以釋放資源。因為對這些大對象的操作會造成系統大的開銷,稍有不慎,將會導致嚴重的后果。而釋放資源最好的地方就是finally
代碼塊。
盡量減少對變量的重復計算
明確一個概念,對方法的調用,即使方法中只有一句語句,也是有消耗的,包括創建棧幀、調用方法時保護現場、調用方法完畢時恢復現場等。所以例如下面的操作:
for (int i = 0; i < list.size(); i++){
...
}
建議替換為:
for (int i = 0, length = list.size(); i < length; i++){
...
}
這樣,在list.size()很大的時候,就減少了很多的消耗
盡量采用懶加載的策略,即在需要的時候才創建
例如:
String str = "aaa";
if (i == 1){
list.add(str);
}
建議替換為:
if (i == 1){
String str = "aaa";
list.add(str);
}
慎用異常
異常對性能不利。拋出異常首先要創建一個新的對象,Throwable接口的構造函數調用名為fillInStackTrace()
的本地同步方法,fillInStackTrace()
方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,Java虛擬機就必須調整調用堆棧,因為在處理過程中創建了一個新的對象。異常只能用於錯誤處理,不應該用來控制程序流程。
如果能估計到待添加的內容長度,為底層以數組方式實現的集合、工具類指定初始長度
比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder為例:
- StringBuilder() // 默認分配16個字符的空間
- StringBuilder(int size) // 默認分配size個字符的空間
- StringBuilder(String str) // 默認分配16個字符+str.length()個字符空間
可以通過類(這里指的不僅僅是上面的StringBuilder)的構造函數來設定它的初始化容量,這樣可以明顯地提升性能。比如StringBuilder吧,length表示當前的StringBuilder能保持的字符數量。因為當StringBuilder達到最大容量的時候,它會將自身容量增加到當前的2倍再加2,無論何時只要StringBuilder達到它的最大容量,它就不得不創建一個新的字符數組然后將舊的字符數組內容拷貝到新字符數組中----這是十分耗費性能的一個操作。試想,如果能預估到字符數組中大概要存放5000個字符而不指定長度,最接近5000的2次冪是4096,每次擴容加的2不管,那么:
- 在4096 的基礎上,再申請8194個大小的字符數組,加起來相當於一次申請了12290個大小的字符數組,如果一開始能指定5000個大小的字符數組,就節省了一倍以上的空間
- 把原來的4096個字符拷貝到新的的字符數組中去
這樣,既浪費內存空間又降低代碼運行效率。所以,給底層以數組實現的集合、工具類設置一個合理的初始化容量是錯不了的,這會帶來立竿見影的效果。但是,注意,像HashMap這種是以數組+鏈表實現的集合,別把初始大小和你估計的大小設置得一樣,因為一個table上只連接一個對象的可能性幾乎為0。初始大小建議設置為2的N次冪,如果能估計到有2000個元素,設置成new HashMap(128)、new HashMap(256)都可以。
當復制大量數據時,使用System.arraycopy()命令
乘法和除法使用移位操作
例如:
for (val = 0; val < 100000; val += 5){
a = val * 8;
b = val / 2;
}
用移位操作可以極大地提高性能,因為在計算機底層,對位的操作是最方便、最快的,因此建議修改為:
for (val = 0; val < 100000; val += 5){
a = val << 3;
b = val >> 1;
}
移位操作雖然快,但是可能會使代碼不太好理解,因此最好加上相應的注釋。
循環內不要不斷創建對象引用
例如:
for (int i = 1; i <= count; i++){
Object obj = new Object();
}
這種做法會導致內存中有count份Object對象引用存在,count很大的話,就耗費內存了,建議為改為:
Object obj = null;
for (int i = 0; i <= count; i++){
obj = new Object();
}
這樣的話,內存中只有一份Object對象引用,每次new Object()的時候,Object對象引用指向不同的Object罷了,但是內存中只有一份,這樣就大大節省了內存空間了。
基於效率和類型檢查的考慮,應該盡可能使用array,無法確定數組大小時才使用ArrayList
盡量使用HashMap、ArrayList、StringBuilder,除非線程安全需要,否則不推薦使用Hashtable、Vector、StringBuffer,后三者由於使用同步機制而導致了性能開銷
盡量避免隨意使用靜態變量
要知道,當某個對象被定義為static的變量所引用,那么gc通常是不會回收這個對象所占有的堆內存的,如:
public class A{
private static B b = new B();
}
此時靜態變量b的生命周期與A類相同,如果A類不被卸載,那么引用B指向的B對象會常駐內存,直到程序終止
及時清除不再需要的會話
為了清除不再活動的會話,許多應用服務器都有默認的會話超時時間,一般為30分鍾。當應用服務器需要保存更多的會話時,如果內存不足,那么操作系統會把部分數據轉移到磁盤,應用服務器也可能根據MRU(最近最頻繁使用)算法把部分不活躍的會話轉儲到磁盤,甚至可能拋出內存不足的異常。如果會話要被轉儲到磁盤,那么必須要先被序列化,在大規模集群中,對對象進行序列化的代價是很昂貴的。因此,當會話不再需要時,應當及時調用HttpSession的invalidate()方法清除會話。
參考文章:
更多信息可以關注我的個人博客:逸竹小站
也歡迎關注我的公眾號:yizhuxiaozhan,二維碼: