什么是Java對象分配率?
類似“不可持續的內存分配率”和“你需要維持低的內存分配率”這樣的短語看起都像是屬于 Java 冠軍(Java Champions)的專有詞匯。復雜、嚇人、充滿神秘色彩。
這些詞語經常出現,但是如果你深入了解這些概念,它的神秘色彩就煙消云散了。這篇文章將試著揭開上面這些術語的神秘面紗。
什么是內存分配率?我們為什么要關心它?
內存分配率是指單位時間內分配內存的總數量,通常用 MB/sec 表示。不過,如果你樂意,也可以用 PB/year 來表示。這就是全部的內容——沒那么神秘,僅僅是指 Java 代碼在一定時期內內存分配的大小。
不過只知道這一點沒有太大的意義。如果你能忍受,我將帶你在實踐中應用這個概念。高分配率意味著你的程序存在性能問題。從實踐角度來說,主要影響是使得 GC(Garbage Collection) 成為了瓶頸。從硬件角度來說,即使常用的硬件也能支持每核幾 GB/sec 的分配率。而實際上,你的分配率不會超過 1 GB/sec/core。所以你可以放心,硬件基本不可能成為應用的瓶頸。
所以,當我們關注 GC 的時候,就可以和真實情況類比了——如果你創建很多的成員,之后就需要做很多清理工作。我們知道,JVM 建立垃圾回收機制需要知道內存分配率,由此來改變 GC 執行的頻率和 GC 停頓的時間。
內存分配率的測量
我們開始測量內存分配率。我們設置 JVM 參數:-XX:+PrintGCDetails -XX:+PrintGCTimeStamps 來打開 GC 日志。現在,JVM 開始以下列方式記錄 GC 停頓日志:
0.291: [GC (Allocation Failure) [PSYoungGen: 33280K->5088K(38400K)] 33280K->24360K(125952K), 0.0365286 secs] [Times: user=0.11 sys=0.02, real=0.04 secs] 0.446: [GC (Allocation Failure) [PSYoungGen: 38368K->5120K(71680K)] 57640K->46240K(159232K), 0.0456796 secs] [Times: user=0.15 sys=0.02, real=0.04 secs] 0.829: [GC (Allocation Failure) [PSYoungGen: 71680K->5120K(71680K)] 112800K->81912K(
根據上述 GC 日志,我們可以從年青代(Young Generation)上一次回收后的大小及下一次回收前的大小來計算出分配率。利用上面的例子,我們可以抽取出如下信息:
- JVM 啟動291毫秒后,加載的對象大小是33280K。第一次 minor GC 清理后,年青代剩余的對象大小是 5088K。
- 啟動446毫秒后,年青代占用的空間已經增長到38368K,并觸發了下一次 GC,這次 GC 后,年青代占用的空間減少到5120K。
- 啟動829毫秒后,年青代的大小是71680K,GC 后再次減少到 5120K。 </ul>
- 設置 Eden 區為100M,運行上面的例子,內存分配率降低到100MB/sec。
- 增加個 Eden 區到1GB,增加的內存分配率接近 200MB/sec。 </ul> </ul>
這些數據用如下的表格展示,計算出來的內存分配率添加年青代占用空間的后面:
事件 | 時間 | 添加年輕代之前 | 添加年輕代之后 | 已分配 | 分配率 | </tr> </tbody>|||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1st GC | 291ms | 33,280KB | 5,088KB | 33,280KB | 114MB/sec | </tr>|||||||||||||||||||
2nd GC | 446ms | 38,368KB | 5,120KB | 33,280KB | 215MB/sec | </tr>|||||||||||||||||||
3rd GC | 829ms | 71,680KB | 5,120KB | 66,560KB | 174MB/sec | </tr>|||||||||||||||||||
Total | 829ms | N/A | N/A | 133,120KB | 161MB/sec | </tr> </tbody> </table>