JVM簡單調優及工作中例子記錄

前序:
Jvm調優需要我們對系統有所瞭解,其中比較關鍵的是對核心業務的理解,特別是會造成頻繁GC的部分,比如高併發造成的不及時回收。
 

要知道爲什麼會造成頻繁GC,首先我們要懂怎麼估算java類的大小
下面列舉各個基本類型和字符串估算的表格,以及測試的類
 
基本類型
大小(字節)
取值範圍
裝箱基本類型
int
4
-2^31 ~ 2^31-1
Integer
char
2
 
Character
byte
1
-2^7 ~ 2^7-1
Byte
short
2
-2^15 ~ 2^15-1
Short
long
8
-2^63 ~ 2^63-1
Long
float
4
 
Float
double
8
 
Double
boolean
1或4
true~false
Boolean
註釋:1字節(byte) = 8比特(bit),1kb = 1024字節。爲什麼boolea需要4個字節,原則上只需要1個比特,但是操作系統以字節作爲單位,所以至少要一個字節。又因爲jvm用int替代boolean,所以需要4字節。
 
講解完基礎類型,我們大概看一下String字符是多少字節(默認就是2字節):
 
Java規定了字符的內碼要用UTF-16編碼,一個字符是2個字節。外碼字符所佔字節取決於具體編碼。幾種常見的編碼換算如下: 
 
-- ASCII編碼       1字節編碼,只有英文字符,不能編碼漢字。 
-- GBK編碼         英文1字節                漢字2字節。 
-- UTF-8編碼       英文1字節                漢字3字節。 
-- Unicode編碼     英文2字節                漢字2字節。
 
下面可以藉助getObjectSize()來獲取類的大小(以下是一個demo,可自行測試):
public class Test01 {
    public Test01() {
        Demo1 demo1 = new Demo1();
        long objectSize = getObjectSize(demo1);
        System.out.println(objectSize);
    }
    public static void main(String[] args) throws UnsupportedEncodingException {
        new Test01();
        // 字符串大小
        System.out.println("測試".getBytes("ISO8859-1").length);
    }
}
    /**
    * 對齊填充(8字節補齊)
    * 這裏有一個注意點:如果大小未滿8字節的倍數,會自動補齊8字節倍數。
    */
    class Demo1 {
        private String str;
        // private int intVal;
        // private int intVal2;
        //private double doubleVal;
    }
 

接下來就舉一個簡單的jvm調優參考例子:
---------------調優前-----------------
假設有一個日活過億的電商網站(日均活躍用戶500w,平均每人點擊20-30次),下單率10%,也就是
    下單人數 = 10% * 500w = 50w
把這一千萬在整天進行平攤(考慮到用戶活躍時間一般只有4-5小時(具體數字見自己系統的分析),所以在這5小時進行平攤)
        每秒下單數 = 50w / (5 * 60 * 60) = 27
如果只有27這個數字是很小的,如果把當前服務部署成3個,那每個每秒就9個下單請求,基本不會對系統造成影響,JVM的垃圾回收肯定是正常的。
        
但是這些系統一般會有一個高峯,在短時間內產品了一天的下單量,比如搞活動雙十一啥的,這時候公式計算就變成下面這樣:
        下單人數 = 10% * 500w = 50w
用戶活躍時間就不是4-5個小時了,可能是幾分鐘內涌入大量訂單
         每秒下單數 = 30w / 幾分鐘 = 1000多
這時候我們也部署成3個,每臺300來個訂單,這時候300明顯遠大於正常情況,假如三臺都是(4G8核)的機器,我們使用2G作爲老年代,1G作爲新生代(Eden區:s1區:s2區 = 8:1:1 = 800M:100M:100M)
 

 
高峯導致的問題(平時都是很平穩的):
高峯的時候會發現系統頻繁FullGC告警,頻繁FullGC會導致STW(stop the world),讓頁面產生卡頓,對用戶體驗不好(正常情況下STW不應該這麼頻繁)。
 
假設這是一個訂單需求上線後纔出現的(也做過jvm參數的變更)。這時候可以從jvm參數來入手如下:
-- 1 估算下單時產生的內存大小
    假定每個訂單有幾十個字段,每個字段8個字節,放大來看:8 * 100 = 800byte,約等於1KB(我們就當它1KB算了)
    每秒300個請求就是: 300 * 1KB = 300KB 的對象生成,
    然後還有一起雜七雜八的庫存、優惠券、積分等,放大20倍:300KB * 20 = 6MB,然後雜七雜八的查詢再放大10倍:6MB * 10 = 60MB
    然後Eden=800MB,所以每13秒會minor GC一次:800MB / 60MB = 13,並且由於第13次的60M是可能還執行完,其引用還存在(CG root可達),這時候第15次的60MB不會被minor GC,本來應該會從移動s區,但是由於s區只有100MB,60MB 大於 100MB*50%,所以這個60MB會直接移動到老年代,導致老年代一點點上上漲,最後滿了觸發fullGC,導致STW。
 
--------------調優解決方法--------------------------------
將新生代設置爲2GB,其中Eden=1.8GB,s1=s2=200MB。這樣的話60MB放進s區的時候也不會超過50%,隨後就能被minorGC掉,從而不會放到老年代中,導致老年代短時間一直漲。導致系統卡頓。
 

其他編程中的小例子:
1 編程中假設查詢出來僅僅有兩個字段,就儘量使用只有兩個成員變量的類來接收這些數據,不要爲了方便用一個含幾十個成員變量的類來接收,這樣會很佔用內容。
 
2 編程中性能會涉及到網絡IO、磁盤IO等,比如我們查數據庫,把數據從一次從庫查出來和循環中一個個查出來很大區別,主要體現在查數據庫的時候有網絡IO和磁盤IO,多次查詢的網絡IO+磁盤IO累計起來很可怕。
 
3 使用中不要以爲用redis就一定很快,比如你用redis在循環中查數據,假設每次吊redis耗時1ms,但是數據一多(假設2w條)就會變成2w * 1ms = 20s,這樣是無法忍受的,相反一次性從redis查兩位萬條可能就1-50ms之間,性能完全沒得比。
 
 
 
發佈了171 篇原創文章 · 獲贊 730 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章