沒錯,老闆讓我寫個 BUG!

前言

標題沒有看錯,真的是讓我寫個 bugjava

剛接到這個需求時我心裏沒有絲毫波瀾,甚至還有點激動。這但是我特長啊;終於能夠光明正大的寫 bug 了🙄。算法

先來看看具體是要幹啥吧,其實主要就是要讓一些負載很低的服務器額外消耗一些內存、CPU 等資源(至於背景就很少說了),讓它的負載能夠提升一些。服務器

JVM 內存分配回顧

因而我刷刷一把梭的就把代碼寫好了,大概以下:佈局

寫完以後我就在想一個問題,代碼中的 mem 對象在方法執行完以後會不會被當即回收呢?我想確定會有一部分人認爲就是在方法執行完以後回收。spa

我也正兒八經的去調研了下,問了一些朋友;果不其然確實有一部分認爲是在方法執行完畢以後回收。code

那事實狀況如何呢?我作了一個試驗。server

我用如下的啓動參數將剛纔這個應用啓動起來。對象

java -Djava.rmi.server.hostname=10.xx.xx.xx 
-Djava.security.policy=jstatd.all.policy 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.ssl=false 
-Dcom.sun.management.jmxremote.port=8888  
-Xms4g -Xmx4g  -jar bug-0.0.1-SNAPSHOT.jar

這樣我就能夠經過 JMX 端口遠程鏈接到這個應用觀察內存、GC 狀況了。內存


若是是方法執行完畢就回收 mem 對象,當我分配 250M 內存時;內存就會有一個明顯的曲線,同時 GC 也會執行。ssl


這時觀察內存曲線。

會發現確實有明顯的漲幅,可是以後並無當即回收,而是一直保持在這個水位。同時左邊的 GC 也沒有任何的反應。

jstat 查看內存佈局也是一樣的狀況。

不論是 YGC,FGC 都沒有,只是 Eden 區的使用佔比有所增長,畢竟分配了 250M 內存嘛。

那怎樣纔會回收呢?

我再次分配了兩個 250M 以後觀察內存曲線。

發現第三個 250M 的時候 Eden 區達到了 98.83% 因而再次分配時就須要回收 Eden 區產生了 YGC

同時內存曲線也獲得了降低。

整個的換算過程如圖:

因爲初始化的堆內存爲 4G,因此算出來的 Eden 區大概爲 1092M 內存。

加上應用啓動 Spring 之類消耗的大約 20% 內存,因此分配 3 次 250M 內存就會致使 YGC

再來回顧下剛纔的問題:

mem 對象既然在方法執行完畢後不會回收,那何時回收呢。

其實只要記住一點便可:對象都須要垃圾回收器發生 GC 時才能回收;無論這個對象是局部變量仍是全局變量。

經過剛纔的實驗也發現了,當 Eden 區空間不足產生 YGC 時纔會回收掉咱們建立的 mem 對象。

但這裏其實還有一個隱藏條件:那就是這個對象是局部變量。若是該對象是全局變量那依然不能被回收。

也就是咱們常說的對象不可達,這樣不可達的對象在 GC 發生時就會被認爲是須要回收的對象從而進行回收。

在多考慮下,爲何有些人會認爲方法執行完畢後局部變量會被回收呢?

我想這應當是記混了,其實方法執行完畢後回收的是棧幀

它最直接的結果就是致使 mem 這個對象沒有被引用了。但沒有引用並不表明會被立刻回收,也就是上面說到的須要產生 GC 纔會回收。

因此使用的是上面提到的對象不可達所採用的可達性分析算法來代表哪些對象須要被回收。

當對象沒有被引用後也就認爲不可達了。

這裏有一張動圖比較清晰:

當方法執行完以後其中的 mem 對象就至關於圖中的 Object 5,因此在 GC 時候就會回收掉。

優先在 Eden 區分配對象

其實從上面的例子中能夠看出對象是優先分配在新生代中 Eden 區的,但有個前提就是對象不能太大。

之前也寫過相關的內容:

大對象直接進入老年代

而大對象則是直接分配到老年代中(至於多大算大,能夠經過參數配置)。


當我直接分配 1000M 內存時,因爲 Eden 區不能直接裝下,因此改成分配在老年代中。

能夠看到 Eden 區幾乎沒有變更,可是老年代卻漲了 37% ,根據以前計算的老年代內存 2730M 算出來也差很少是 1000M 的內存。

Linux 內存查看

回到此次我須要完成的需求:增長服務器內存和 CPU 的消耗。

CPU 還好,自己就有必定的使用,同時每建立一個對象也會消耗一些 CPU。

主要是內存,先來看下沒啓動這個應用以前的內存狀況。

大概只使用了 3G 的內存。

啓動應用以後大概只消耗了 600M 左右的內存。

爲了知足需求我須要分配一些內存,但這裏有點須要講究。

不能一直分配內存,這樣會致使 CPU 負載過高了,同時內存也會因爲 GC 回收致使佔用也不是特別多。

因此我須要少許的分配,讓大多數對象在新生代中,爲了避免被回收須要保持在百分之八九十。

同時也須要分配一些大對象到老年代中,也要保持老年代的使用在百分之八九十。

這樣才能最大限度的利用這 4G 的堆內存。

因而我作了如下操做:

  • 先分配一些小對象在新生代中(800M)保持新生代在90%
  • 接着又分配了老年代內 *(100%-已使用的28%);也就是 2730*60%=1638M 讓老年代也在 90% 左右。

效果如上。

最主要的是一次 GC 都沒有發生這樣也就達到了個人目的。

最終內存消耗了 3.5G 左右。

總結

雖然說此次的需求是比較奇葩,但想要精確的控制 JVM 的內存分配仍是沒那麼容易。

須要對它的內存佈局,回收都要有必定的瞭解,寫這個 Bug 的過程確實也加深了印象,若是對你有所幫助請不要吝嗇你的點贊與分享。

你的點贊與分享是對我最大的支持

相關文章
相關標籤/搜索