java效能優化

hixiaoxiaoniao發表於2019-01-12

目錄

基準測試和JIT

GC相關

堆記憶體

原生記憶體

執行緒

JavaEE相關

Java SE 相關


 

基準測試和JIT

即便是單執行緒的基準測試用例,變數也要申明成 volatile型別的,這樣可以防止編譯器做優化

要加入一定的熱身期

監控命令
jcmd 列印java程式所涉及的基本類,執行緒和VM資訊
jconsole
jhat,讀取記憶體堆轉儲,並有助於分析
jmap,提供堆轉儲和其他JVM記憶體使用的資訊
jinfo,檢視JVM的系統屬性,可以動態設定一些系統屬性
jstack
jstat
jvisualvm

比如
jcmd [process_id] VM.system_properties
或者 jinfo -sysprops [process_id]
jcmd [process_id] VM.version
jcmd [process_id] VM.command_line
jcmd [process_id] VM.flags [-all]
列印所有的調優標誌
java other_options -XX:+PrintFlagsFinal -version

jstack [process_id]
或者 jcmd [process_id] Thread.print

關閉某個屬性
jinfo -flag -PrintGCDetails [process_id]
可以在執行期間設定屬性開啟/關閉,但不是所有屬性都支援熱關閉/開啟的

Java Mission Control 商業監控工具,其關鍵特性是java飛行記錄器(Java Flight Recorder)
 

 


GC相關

GC輸出

-verbose:gc
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimestamps
-XX:+PrintGCDateStamps
-Xloggc:filename
-XX:+UseGCLogfileRotation
-XX:+NumberOfGCLogfiles

 

Througput GC演算法是一種空間和時間的權衡
開始時可以用預設的自適應方式
如果把暫停時間設定的太短,就會觸發更頻繁的GC,會導致吞吐量下降
如果把GC比率設定太低,也會導致堆空間變小,更可能會頻繁觸發GC
有兩個相關的引數
-XX:MaxGCPauseMillis=N
-XX:GCTimeRatio=N

Through收集器
分為Minor GC,Full GC
暫停時間越短GC就會越多,GC次數減少意味著時間增大堆空間增加
對through調優是一種取捨

CM收集器
初始標記,併發標記,預清理,重新標記,併發清理,(清理失敗)
增量式CMS垃圾收集器,在單CPU上會增加CPU負擔,增量收集採用間歇性的方式收集,1.8之後被廢棄

G1收集器
初始標記,併發標記,預清理,重新標記,併發標記
分成2048個region
新生代+老年代 混合收集模式
併發模式失敗(類似CMS),疏散失敗(Srvivor和老年代沒有空間容納倖存物件)
調整後臺收集執行緒數
調整垃圾收集器執行頻率(預設佔到45%時回收)-XX:InitiatingHeapOccupancyPercent=N
調整混合垃圾收集週期
 分割槽預設為35%時啟動收集 -XX:G1MixedGCLiveThresholdPercent
 最大混合式GC週期數,可解決晉升失敗問題,但會增加GC週期停頓時間,-XX:G1MixedGCCountTarget=N
 最大停頓時間 -XX:MaxGCPauseMillis

晉升調整
-XX:MinSurvivorRatio=N
-XX:InitialTenuringThreshold=N
-XX:TargetSurvivorRatio
-XX:+AlwaysTenure
-XX:+NeverTenure

分配大頁
-XX:+UseTLAB
-XX:TLABWasteTargetPercent=N
-XX:TLABWasteIncrement=N
-XX:G1HeapRegionSize=N

 

堆記憶體

記憶體溢位錯誤
1.原生記憶體不足
 Exception in thread "main" java.lang.OutOfMemoryError
 unable to create new native thread

2.永久代或元空間記憶體不足
  Exception in thread "main" java.lang.OutOfMemroyError: PermGen space

3.堆記憶體不足
  Exception in thread "main" java.lang.OutOfMemoryError: java heap space

4.達到GC的開銷限制
  Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

-XX:+UseGCOverheadLimit 如果超過了這個設定值,比如預設98%的時間用在GC上
-XX:+GCHeapFreeLimit    如果釋放的記憶體少於這個指標,預設是2%,釋放的記憶體不足堆的2%

減少記憶體使用
減少物件大小
延遲物件初始化
不可變物件
字串保留 intern()

物件重用
1.執行緒池
2.JDBC池
3.EJB池
4.大陣列
5.原生NIO緩衝區
6.安全相關類
7.字串編解碼器物件
8.StringBuilder協助者
9.從NDS查詢到的名字
10.ZIP編解碼器

弱引用,軟引用,幻引用
Finalizer

 

原生記憶體

記憶體佔用最小化
1.堆
2.執行緒棧
3.程式碼快取
4.直接位元組緩衝區

原生記憶體跟蹤
jcmd [process_id] VM.native_memory summary
-XX:NativeMemoryTracking=off|summary|detail

原生NIO快取
-XX:MaxDirectMemorySize

大頁 
+XX:+UseLargePages
+XX:LargePageSizeInBytes

指標壓縮
+XX:+UseCompressedOops

jcmd 11674 VM.native_memory summary
11674:

Native Memory Tracking:

Total: reserved=1573497KB, committed=36193KB
-                 Java Heap (reserved=253952KB, committed=16384KB)
                            (mmap: reserved=253952KB, committed=16384KB) 
 
-                     Class (reserved=1056875KB, committed=4971KB)
                            (classes #389)
                            (malloc=107KB #122) 
                            (mmap: reserved=1056768KB, committed=4864KB) 
 
-                    Thread (reserved=10304KB, committed=10304KB)
                            (thread #11)
                            (stack: reserved=10260KB, committed=10260KB)
                            (malloc=33KB #52) 
                            (arena=12KB #20)
 
-                      Code (reserved=249631KB, committed=2567KB)
                            (malloc=31KB #295) 
                            (mmap: reserved=249600KB, committed=2536KB) 
 
-                        GC (reserved=839KB, committed=71KB)
                            (malloc=7KB #79) 
                            (mmap: reserved=832KB, committed=64KB) 
 
-                  Compiler (reserved=132KB, committed=132KB)
                            (malloc=1KB #22) 
                            (arena=131KB #3)
 
-                  Internal (reserved=203KB, committed=203KB)
                            (malloc=171KB #1206) 
                            (mmap: reserved=32KB, committed=32KB) 
 
-                    Symbol (reserved=1356KB, committed=1356KB)
                            (malloc=900KB #64) 
                            (arena=456KB #1)
 
-    Native Memory Tracking (reserved=32KB, committed=32KB)
                            (malloc=2KB #29) 
                            (tracking overhead=30KB)
 
-               Arena Chunk (reserved=171KB, committed=171KB)
                            (malloc=171KB) 
 

 

執行緒

執行緒池ThreadPoolExecutor設定
1.最小任務數
2.最大任務數
3.任務佇列,有界佇列,無界佇列

ForkJoinPool
使用map-reduce原理
如果整個待執行的任務是均衡的用ThreadPoolExecutor更好
否則用ForkJoinPool更好
竊取雙端佇列

同步的方式
volatile
CAS
原生同步
競爭不大用CAS,競爭比較多用原生同步

Amdahl定律
volatile導致的偽共享會讓效能急劇下降
偏向所,優先處理訪問頻繁的執行緒
自旋鎖,JDK8廢棄

 

 

JavaEE相關

減少輸出,包括減少空格
合併CSS和JavaScript
壓縮輸出
不要使用JSP動態編譯

減少會話中保留的資料,減少會話時間
servlet可能有專門的執行緒池,EJB也有專門的執行緒池,同樣JMS也是

EJB bean的執行過程
1.建立EJB
2.處理註解並且將依賴的資源注入這個新的EJB物件
3.呼叫註解為@PostConstruct的函式
4.如果是有狀態bean,呼叫註解為@Init的函式或者ejbCreate()函式
5.執行業務方法
6.呼叫任何註解為@PreRemove的函式
7.如果是有狀態bean,則呼叫remove()函式
EJB池化後,只需要執行業務方法,其他初始化銷燬的都可以不做
有狀態bean的鈍化(Passivation),儘量避免這麼做

及時在一個伺服器中,本地介面也比遠端介面快,因為少了序列化/反序列化步驟
所有遠端EJB都必須支援IIOP(CORBA)協議
Web Service使用JAX-WS
RESTful使用JAX-RS
XML和Json的解析方式,XML的驗證會影響很大效能
建立JAXB物件比建立DOM物件昂貴,但JAXB寫XML速度要快於DOM物件
Serializable標記介面
Externalizable自定義序列化介面(包含readExternal和writeExternal)

序   號

區   別

Serializable

Externalizable

1

實現複雜度

實現簡單,Java對其

有內建支援

實現複雜,

由開發人員自己完成

2

執行效率

所有物件由Java統一儲存,

效能較低

開發人員決定哪個物件儲存,

可能造成速度提升

3

儲存資訊

儲存時佔用空間大

部分儲存,

可能造成空間減少

在一般的開發中,因為Serializable介面的使用較為方便,所以出現較多


JDBC驅動程式型別1-4,四種
PrepareStatment可以重用sql語句,比Statement效能更好
預處理語句必須依靠單個連線進行池化,而預處理語句會消耗大量堆空間
資料庫查詢返回的取捨,頻繁的呼叫資料庫每次取一小部分,或者一次取很多但會造成GC

Container Mnaged Transactions CMT 使用容器管理事務
User Managed Transaction UMT  使用使用者管理事務
XA事務
減少寫入的欄位,不必要跟新的不寫
延遲載入大型欄位
不太容易衝突的使用樂觀鎖,經常發生衝突的使用悲觀鎖
加入JPA快取

 


Java SE 相關

I/O緩衝區,socket I/O也需要緩衝
ClassLoader的雙親委派模型,多執行緒建立類時候會有影響效能,1.7之後改為並行模式效能提升
ClassLoader使用loadClass()尋找類,如果找不到呼叫findClass(),我們自己的類載入只需實現findClass即可
-XX:+AlwaysLockClassLoader 還原為JDK6的持有鎖的類載入模式

Random 執行緒安全的
ThreadLocalRandom 實現方式類似Random,一種偽隨機數
SecureRandom,獲得了更多的隨機數實現確保更安全,如果無法獲取更多隨機數可能會等待好幾秒

現在的JNI未必比Java程式碼更快了,若一定要使用應限制Java到C的呼叫次數,這會影響效能
現在的JVM處理異常速度也很快了,但獲取異常棧資訊(尤其棧很深)會損耗效能
防禦性程式碼比丟擲異常更好
可以用 -XX:-StrackTraceInThrowable 禁止生成異常棧資訊(不推薦)
String str = "aa"+"bb"+"cc" 編譯器會做優化
str += "aa";
str += "bb";
會變成 
str = new StringBuilder(str).append("aa");
str = new StringBuilder(str).append("bb");

Java集合類
執行緒安全的集合
設定集合的大小,IO類中的ByteArrayOutuptStream也是類似的
ConcurrentHashMap
AggressiveIpts 會在啊一些基本的類中開啟某些優化,導致一些副作用
AutoBoxCacheMax 自己裝箱cache

lambda效能並不差,可能比匿名類效能更好
應該從方便程式設計的角度出發,因為效能上沒什麼差別

list.stream().filter(s->s.charAt(0)!='A').(s->s.charAt(1)!='A').(s->s.charAt(2)!='A').(s->s.charAt(3)!='A')


過濾器會先判斷最後一個
(s->s.charAt(3)!='A')
拿到資料之後交給倒數第二個,然後倒數第三個。。。
過濾器的效能比迭代器好,因為能少處理很多資料
但多個過濾器會有開銷,實現時需要注意

 

 

 

 

參考

G1垃圾收集器入門-原創譯文

詳解 JVM Garbage First(G1) 垃圾收集器

 

相關文章