一. 調優須要關注的幾個方面
內存調優
CPU 使用調優
鎖競爭調優
I/O 調優java
二. Twitter 最大的敵人:延遲
致使延遲的幾個緣由?
最大影響因素是 GC
其餘的有:鎖和線程調度、I/O、算法數據結構選取不當效率低python
三. 內存性能調優算法
(1)內存佔用調優
OutOfMemoryError 異常緣由:可能真的數據量太大、可能要數據顯示的太多、可能內存泄露緩存
數據量太大觀察及解決:
查看 GC 日誌, 看 Full GC 先後內存變化, 變化不大說明確實數據量太大
嘗試增長 JVM 的內存使用
考慮這些數據是否真的須要都在內存中嗎? 能夠考慮使用: LRU 算法換入換出等, 弱引用(Soft References)數據結構
數據臃腫(Fat data)
當你想作一些奇怪的事情時候回發生數據佔用太大問題,好比:把整個社交圖譜加載到單個 JVM 實例上、加載所有用戶的元數據到單個 JVM 實例上
在 Twitter 這樣大的規模下減小內部數據呈現工做多線程
數據臃腫緣由:
(1)對象頭(JVM 對象頭通常佔用兩個機器碼,在 32-bit JVM 上佔用 64bit, 在 64-bit JVM 上佔用 128bit 即 16 bytes, 例如:new java.lang.Object() 佔用 16 bytes; new byte[0] 佔用 24 bytes) 更多對象頭內容參考:http://blog.csdn.net/wenniuwuren/article/details/50939410
(2)填充補全
看個例子 併發
public static class D {
byte d1;
}
public static class E extends D {
byte e1;
}jvm
new D() 佔用 24 bytes 空間, new E() 佔用 32 bytes 空間。 具體空間計算參考:http://blog.csdn.net/wenniuwuren/article/details/50958892ide
如今通常是 64-bit 的 JVM,64-bit 的指針會致使 CPU 緩存相比 32-bit 指針減小不少, 因此建議 JVM 參數加入 -XX:+UseCompressedOops 採用指針壓縮將 64-bit 指針壓縮爲 32-bit, 可是卻又能使用 64-bit 的內存空間, 達到一箭雙鵰的做用。另外,建議最大堆小於 30G。工具
儘可能別使用原始類型對象的包裝類
在 Scala 2.7.7 中:Seq[Int] 存 Integer,Array[Int] 存 int, 第一個空間佔用 (24 + 32*length) bytes,第二個空間佔用 (24 + 4*length) bytes。
在 Scala 2.8 中修復了這個問題, 從這咱們能夠看出:
你不清楚你所使用類庫的性能特徵(好比能用 int 就用 int)
除非在性能分析工具下運行, 不然你可能永遠不知道這個問題
Map 空間佔用(Map footprints)
Guava MapMaker.makeMap() 佔用 2272 bytes
MapMaker.concurrencyLevel(1).makeMap() 佔用 352 bytes
當心使用 Thread Local
典型的問題在線程池 m*n 的資源相關,如 200 線程池使用了 50 個鏈接,最終有 10000 個鏈接緩存
考慮使用同步對象或者每次新建一個對象
四. 與延遲作鬥爭
性能三角
圖1:內存佔用降低,延遲降低,吞吐量上升
![](http://static.javashuo.com/static/loading.gif)
圖2:壓縮(Compactness,即減少內存佔用)率上升,吐量上升,響應速度上升
![](http://static.javashuo.com/static/loading.gif)
新生代是如何工做的?
全部新對象分配在 Eden 代,由於新生代 GC 有壓縮,因此內存分配用指針碰撞
當 Eden 滿的時候,進行一次 stop-the-world 的 Minor GC,存活下來的放到 Survivor
通過幾回 Minor GC,還存活下來的對象會被提高(tenured)到老年代
理想化得新生代操做
Eden 代足夠容納超過一組併發的請求和響應對象(這樣沒有 stop-the-world,吞吐量會比較高)
每一個 Survivor 空間足夠容納活躍對象和有年齡的對象(減小過早提高到老年代)
提高閾值正好能讓存活時間長的對象早點提高到老年代(給 Survivor 騰出空間)
重新生代開始調優
打印詳細 GC 日誌, 如開啓 JVM 參數:-XX:+PrintGCDetails,-XX:+PrintGCDateStamps,-XX:+PrintHeapAtGC,-XX:+PrintTenuringDistribution 等等...
關注 Survivor 大小,設置合適的 Survivor 大小
關注提高閾值,使長期存活對象快速提高到老年代
(1)-XX:+PrintHeapAtGC
Heap after GC invocations=1 (full 0):
par new generation total 943744K, used 54474K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
eden space 838912K, 0% used [0x0000000757000000, 0x0000000757000000, 0x000000078a340000)
from space 104832K, 51% used [0x00000007909a0000, 0x0000000793ed2ae0, 0x0000000797000000)
to space 104832K, 0% used [0x000000078a340000, 0x000000078a340000, 0x00000007909a0000)
concurrent mark-sweep generation total 1560576K, used 0K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
concurrent-mark-sweep perm gen total 159744K, used 38069K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
}
(2)-XX:+PrintTenuringDistribution
Desired survivor size 53673984 bytes, new threshold 4 (max 6)
- age 1: 9165552 bytes, 9165552 total
- age 2: 2493880 bytes, 11659432 total
- age 3: 6817176 bytes, 18476608 total
- age 4: 36258736 bytes, 54735344 total
: 899459K->74786K(943744K), 0.0654030 secs] 1225769K->401096K(2504320K), 0.0657530 secs] [Times: user=0.55 sys=0.00, real=0.07 secs]
CMS 調優
CMS 收集器須要更多的內存, 儘可能多分配就對了
減小碎片、避免 Full GC
-XX:CMSInitiatingOccupancyFraction=n n通常設置爲 75-80(太早啓動下降吞吐量,太晚啓動致使 concurrent mode failed)
響應速度仍是太慢?
Minor GC 時有太多存活對象,嘗試減小新生代空間,減小 Survivor 空間,減小晉升閾值
太多線程。嘗試找到最小的併發層次或者增長更多 JVM 實例
嘗試使用 volatile 而不是 synchronized 減小鎖競爭,嘗試使用 Atomic* 的原子類
用分配 slab 應對 CMS 的碎片問題
Apache 的 Cassandra 內部使用 slab 分配。每一個 slab 大小爲 2MB,使用 CAS 複製 byte[] 到裏面,使用 Cassandra 前開銷爲 30-60 秒每小時, 使用後在3天零十小時開銷 5 秒。
使用分配 slab 的方式有一些侷限性:在緩存滿的時候才把緩存內容寫進磁盤,並且對象須要轉化爲二進制等問題
原文出處 http://www.slideshare.net/aszegedi/everything-i-ever-learned-about-jvm-performance-tuning-twitter/2-Everything_I_everlearned_about_JVMperformance