JVM調優:帶你瞭解Java虛擬機調優工具、調優過程以及注意事項(有案例)

調優工具與操作

1.jps:jvm process status tool-java虛擬機進程狀況工具

jps -l 是輸出主類名 列出進程id

jps -m 輸出JVM啓動時傳遞給main()的參數

jps -v 顯示虛擬機參數配置

      -Xms堆內存最小,-Xmx堆內存最大,-XX:MaxPerSize=256m,永久代大小最大爲多少,-Xmn年輕代堆的大小,-Xss棧、線程棧的大小

2.jstat:虛擬機運行時的信息監控

jstat -class 監視類裝載,卸載數量,總空間以及類裝載所耗費的時間。

jstat -gc 監視類堆狀況,包括Eden區,兩個survivor區,老年代,永久代的空間和已用的空間。

jstat -gcutil 監視已使用空間佔總空間的百分比。

這兩個命令裏面還包括了新生代GC的次數和所花的時間,以及Full GC的次數和所花的時間。

3.jmap:生成虛擬機的內存轉儲快照,獲取dump文件

可以查詢finalize執行隊列,java堆和永久代的詳細信息。空間的使用率,以及使用的哪種收集器。

jmap -histo pid:

展示java堆中對象的統計信息

instances(實例數)、bytes(大小)、classs name(類名)。它基本是按照使用使用大小逆序排列的。

jmap -dump:format=b,file=lijun pid 生成當前線程的dump文件

jmap -finalizerinfo

打印等待回收對象的信息

jmap -heap pid

查看堆的年輕代年老代的使用情況

jhat lijun 分析dump文件(現在一般不用這個了,因爲分析dump文件非常消耗硬件資源,所以一般是複製到其他的機器來進行)現在一般都用visualVM來分析

4.jstack:用來顯示虛擬機的線程快照

這裏生成線程快照的原因主要是爲了定位線程出現長時間停頓的原因,比如線程死鎖,死循環,請求外部資源(數據庫連接,網絡資源,設備資源)導致的長時間等待

對於在java中最簡單的死鎖情況:
一個線程T1持有鎖L1並且申請獲得鎖L2,而另一個線程T2持有鎖L2並且申請獲得鎖L1,因爲默認的鎖申請操作都是阻塞的,所以線程T1和T2永遠被阻塞了。導致了死鎖。

另外一個原因是默認鎖的申請操作是阻塞的

要儘量避免在一個對象的同步方法裏面調用其他對象的同步方法或者延時方法。

減小鎖的範圍,只獲對需要的資源加鎖,我們鎖定了完整的對象資源,但是如果我們只需要其中一個字段,那麼我們應該只鎖定那個特定的字段而不是完整的對象。

如果兩個線程使用 thread join 無限期互相等待也會造成死鎖,我們可以設定等待的最大時間來避免這種情況。

死鎖的調優:

jstack命令生成線程快照的原因主要是爲了定位線程出現長時間停頓的原因,比如線程死鎖,死循環,請求外部資源(數據庫連接,網絡資源,設備資源)導致的長時間等待

死鎖的避免:

1.對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會 造成死鎖。說明:線程一需要對錶 A、B、C 依次全部加鎖後纔可以進行更新操作,那麼線程二的加鎖順序也必須是 A、B、C,否則可能出現死鎖。

5.jinfo實時查看和調整虛擬機的各項參數

對於高性能硬件部署,假如我們-Xms=12g,仍然會出現每隔十幾分鍾就出現十幾秒的停頓?
原因:就是我們在程序設計的時候,訪問文檔要將文檔從磁盤提取到內存中,導致內存中出現很多由文檔序列化產生的大對象,這些大對象都直接進入老年代,沒有通過minor gc清除,即便有12g的堆,我們的內存也很快被消耗殆盡,最後進行一次full gc,導致等待的時間很長

因此要想減少網站停頓的時間,我們就需要控制full gc的頻率,因此就不能有成批的長時間存活的大對象產生,這樣才能保證老年代空間的穩定。

堆外內存導致的溢出錯誤?
即使Eden區,Survivor區,老年代,永久代都正常,但還是會不斷拋出內存溢出異常。主要就是direct memory發生內存溢出異常,但是收集器一般只要在fullgc以後纔會順便處理directmemory。只能在catch裏面調用System.gc(),這種情況一般要去代碼中找IO操作。

外部命令導致的系統緩慢?
假如通過shell腳本獲取系統的一些信息,java虛擬機在執行這些命令時一般會克隆一個和當前虛擬機擁有相同環境變量的進程,再用這個新的進程去執行外部命令,頻繁執行這個操作,系統的消耗會很大。

服務器jvm進程崩潰?
http請求不能馬上處理並返回,就會導致等待的線程和socket連接越來越多,最終會導致虛擬機進程崩潰。

不恰當的數據結構導致內存佔用過大?
我們在新生代的垃圾收集器是採用的複製算法,如果在survivor區一直維持這些對象,就會導致gc暫停的時間非常長。可以通過設置參數去掉survivor區,但是仍然沒有從根本上解決問題,根本還是在於HashMap存儲數據文件效率太低。

Java虛擬機的調優

首先就是編譯時間和類加載時間的優化 通過 -Xverify:none來去掉驗證碼驗證過程。

一般我們通過jstat -gc查看java虛擬機中堆的空間使用情況,通過minor gc和full gc的次數來判斷我們的堆空間的大小設置是否合理,使用jstat查看FGC發生的頻率及FGC所花費的時間,FGC發生的頻率越快、花費的時間越高,問題越嚴重;

頻繁gc的原因一般有:

1.程序內調用了System.gc()或Runtime.gc().
2.一些中間件軟件調用自己的GC方法,此時需要設置參數禁止這些GC.
3.Java的Heap太小,一般默認的Heap值都很小.
4.頻繁實例化對象,Release對象.此時儘量保存並重用對象,例如使用StringBuffer()和String(),也就是儘量少用字符串常量String=“”。

在設置heap的大小的時候也不是越大越好,如果heap特別大,在進行full gc的時候所需要花費的時間也會非常的長。

也可以通過分析gc日誌來分析full gc如何產生的。

也可以通過觀察cpu資源使用情況來分析選擇什麼收集器。採用CMS收集器。

總結

1.如果程序內存不足或者頻繁GC,很有可能存在內存泄露情況,這時候就要藉助Java堆Dump查看對象的情況。
2.要製作堆Dump可以直接使用jvm自帶的jmap命令
3.可以先使用jmap -heap命令查看堆的使用情況,看一下各個堆空間的佔用情況,不推薦用這個,都用jstat -gc
4.使用jmap -histo:[live]查看堆內存中的對象的情況。如果有大量對象在持續被引用,並沒有被釋放掉,那就產生了內存泄露,就要結合代碼,把不用的對象釋放掉。
5.也可以使用 jmap -dump:format=b,file=命令將堆信息保存到一個文件中,再借助jhat命令查看詳細內容

一些java應用調優案例

https://tech.meituan.com/2017/12/29/jvm-optimize.html

參考:

https://blog.csdn.net/qq_42914528/article/details/81541760

https://blog.csdn.net/savilors/article/details/81907469

初始調優選擇

年輕代大小選擇
響應時間優先的應用:儘可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇)。在此種情況下,年輕代收集發生的頻率也是最小的。同時,減少到達年老代的對象。

吞吐量優先的應用:儘可能的設置大,可能到達Gbit的程度。因爲對響應時間沒有要求,垃圾收集可以並行進行,一般適合8CPU以上的應用。

年老代大小選擇
響應時間優先的應用:年老代使用併發收集器,所以其大小需要小心設置,一般要考慮併發會話率和會話持續時間等一些參數。如果堆設置小了,可以會造成內存碎片、高回收頻率以及應用暫停而使用傳統的標記清除方式;如果堆大了,則需要較長的收集時間。最優化的方案,一般需要參考以下數據獲得:

併發垃圾收集信息

持久代併發收集次數

傳統GC信息

花在年輕代和年老代回收上的時間比例

減少年輕代和年老代花費的時間,一般會提高應用的效率

吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代。原因是,這樣可以儘可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對象。

較小堆引起的碎片問題
因爲年老代的併發收集器使用標記、清除算法,所以不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合併,這樣可以分配給較大的對象。但是,當堆空間較小時,運行一段時間以後,就會出現“碎片”,如果併發收集器找不到足夠的空間,那麼併發收集器將會停止,然後使用傳統的標記、清除方式進行回收。如果出現“碎片”,可能需要進行如下配置:

-XX:+UseCMSCompactAtFullCollection:使用併發收集器時,開啓對年老代的壓縮。

-XX:CMSFullGCsBeforeCompaction=0:上面配置開啓的情況下,這裏設置多少次Full GC後,對年老代進行壓縮

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章