ArrayList插入1000w條數據之後,我懷疑了JVM.....

來源:https://mp.weixin.qq.com/s/X2MdX7ypJUCDevK8oEvlcA作者:佔小狼

"狼哥,我發現新大陸了,等會發你代碼"

"咋了,這麼激動"

"等會..."

ArrayList插入1000w條數據之後,我懷疑了JVM.....

"我在一個ArrayList中連續插入1千萬條數據,結果耗時不一樣,分別是 2346 797 沒搞明白 "

我看了一眼,就知道這小夥底盤不穩。

"你加個 -XX:+PrintGCDetails -XX:+PrintGCDateStamps,看下第一次是不是有Full GC"

"明白,我再試試看"

幾分鐘後...

2019-09-28T09:49:07.519-0800: [GC (Allocation Failure) [PSYoungGen: 54888K->10738K(76288K)] 54888K->36180K(251392K), 0.0520111 secs] [Times: user=0.24 sys=0.03, real=0.06 secs]
2019-09-28T09:49:07.590-0800: [GC (Allocation Failure) [PSYoungGen: 74092K
->10736K(141824K)] 99534K->80803K(316928K), 0.0693607 secs] [Times: user=0.39 sys=0.03, real=0.06 secs]
2019-09-28T09:49:07.751-0800: [GC (Allocation Failure) [PSYoungGen: 141808K->10736K(141824K)] 211875K->188026K(320512K), 0.1829926 secs
] [Times: user=1.02 sys=0.10, real=0.18 secs]
2019-09-28T09:49:07.934-0800: [Full GC (Ergonomics) [PSYoungGen: 10736K->
0K(141824K)] [ParOldGen: 177290K->171620K(402432K)] 188026K->171620K(544256K), [Metaspace: 3062K->3062K(1056768K)], 1.8672996 secs] [Times: user=5.96 sys=0.03, real=1.87 secs]
2365
2019-09-28T09:49:09.832-0800: [GC (Allocation Failure) [PSYoungGen: 129254K->10738K(196608K)] 300875K->282609K(599040K), 0.1039307 secs] [Times: user=0.74 sys=0.07, real=0.10 secs]
2019-09-28T09:49:09.936-0800: [Full GC (Ergonomics) [PSYoungGen: 10738K->0K(196608K)] [ParOldGen: 271871K->36047K(372736K)] 282609K->36047K(569344K), [Metaspace: 3067K->3067K(1056768K)], 0.4510440 secs] [Times: user=1.82 sys=0.01, real=0.45 secs]
2019-09-28T09:49:10.440-0800: [GC (Allocation Failure) [PSYoungGen: 185856K->10752K(264704K)] 221903K->171359K(637440K), 0.1292143 secs] [Times: user=0.97 sys=0.01, real=0.12 secs]
772

"狼哥,第一次Full GC果然耗時了1.87s,那我把堆調大看看,避免Full GC"

幾分鐘後...

"這次沒有GC了,但是每次運行,前一個都比後一個耗時多點,這是怎麼回事?"

"你試試放在不同線程中運行?"

"好"

又幾分鐘後...

"在不同線程中執行,兩者耗時幾乎一致,這是為什麼?"

"你知道OSR嗎?"

"不知道."

"那我跟你大概講講."

OSR(On-Stack Replacement ),是一種在運行時替換正在運行的函數/方法的棧幀的技術。

在現代的主流JVM中,都具備了多層編譯的能力,一開始以解釋的方式進行執行,這種性能相對來說(和c++比)會慢一點,但是一旦發現某一個函數執行很頻繁的時候,就會採用JIT編譯,提高函數執行性能(大部分比c++還快)。

但是,如果以函數為單位進行JIT編譯,那麼就無法應對main函數中包含循環體的情況,這個時候,OSR就派上了用場。

與其編譯整個方法,我們可以在發現某個方法裡有循環很熱的時候,選擇只編譯方法裡的某個循環,當循環體執行到 i = 5000 的時候,循環計數器達到了觸發OSR編譯的閾值,等編譯完成之後,就可以執行編譯後生成的代碼。所以在上面例子中,當我們第二次執行循環體的時候,已經在執行OSR編譯後的代碼,那麼在性能上會比前一次會快那麼一點點。

OSR更具體的實現原理,本文就不多加深究了,有興趣的同學可以閱讀下R大的知乎。https://tinyurl.com/y3yxu8fc


分享到:


相關文章: