程序監控與調優---------Java篇

序言

        梳理下,針對java工程的監控與對應的調優. 主要分爲兩部分,第一部分是基礎知識預備(這樣纔有思路去調優啊~),第二步是是監控工具的使用(針對不同的監控對象,會選擇一個監控工具). 另外歡迎騷擾:[email protected]

 

預備知識

Java內存模型

先看下java內存模型,後面可以針對性的看到每個東西在該模型中的位置

 

棧和堆

       Java把內存分成兩種,一種叫做棧內存,一種叫做堆內存. 堆中存的是對象。棧中存的是基本數據類型中對象的引用(另外棧中保存了對象的應用地址,可以說應用或者叫指針是放在棧裏的.)

       在函數中定義的一些基本類型的變量和對象的引用變量都是在函數的棧內存中分配。當在一段代碼塊中定義一個變量時,java就在棧中爲這個變量分配內存空間,當超過變量的作用域後,java會自動釋放掉爲該變量分配的內存空間,該內存空間可以立刻被另作他用。

       堆內存用於存放由new創建的對象和數組在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以後就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當於爲數組或者對象起的一個別名,或者代號。

       引用變量是普通變量,定義時在棧中分配內存(即指針是在棧中),引用變量在程序運行到作用域外釋放。而數組&對象本身在堆中分配,即使程序運行到使用new產生數組和對象的語句所在地代碼塊之外,數組和對象本身佔用的堆內存也不會被釋放,數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然佔着內存,在隨後的一個不確定的時間被垃圾回收器釋放掉。這個也是java比較佔內存的主要原因,實際上,棧中的變量指向堆內存中的變量,這就是 Java 中的指針! 

另:

       在棧中,會爲每一個線程創建一個棧。線程越多,棧的內存使用越大。對於每一個線程棧。當一個方法在線程中執行的時候,會在線程棧中創建一個棧幀(stack frame),用於存放該方法的上下文(局部變量表、操作數棧、方法返回地址等等)。每一個方法從調用到執行完畢的過程,就是對應着一個棧幀入棧出棧的過程。

 

程序計數器

如同其名稱一樣。程序計數器用於記錄某個線程下次執行指令位置。程序計數器也是線程私有的。

 

JVM如何判斷是否爲垃圾空間

       JVM如何判斷一個對堆空間是否爲垃圾空間的方法如下,

引用計數

       在java中是通過引用來和對象進行關聯的,也就是說如果要操作對象,必須通過引用來進行。那麼很顯然一個簡單的辦法就是通過引用計數來判斷一個對象是否可以被回收。不失一般性,如果一個對象沒有任何引用與之關聯,則說明該對象基本不太可能在其他地方被使用到,那麼這個對象就成爲可被回收的對象了。這種方式成爲引用計數法。

       這種方式的特點是實現簡單,而且效率較高,但是它無法解決循環引用的問題,因此在Java中並沒有採用這種方式(Python採用的是引用計數法)。

可達分析(Java採用的是類似於樹形結構的可達性分析法來判斷對象是否還存在引用)

        爲了解決這個問題,在Java中採取了 可達性分析法。該方法的基本思想是通過一系列的“gc Roots”對象作爲起點進行搜索,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要注意的是被判定爲不可達的對象不一定就會成爲可回收對象。被判定爲不可達的對象要成爲可回收對象必須至少經歷兩次標記過程,如果在這兩次標記過程中仍然沒有逃脫成爲可回收對象的可能性,則基本上就真的成爲可回收對象了。

 

常用的垃圾回收算法

        如同java判斷是否爲垃圾的方法有多重,關於垃圾回收的算法也有多重.並且他們可能在同一個廠家的jvm中的不同部分同時在使用

 

標記-清除(Mark-Sweep)

這是最基礎的垃圾回收算法,之所以說它是最基礎的是因爲它最容易實現,思想也是最簡單的.顧名思義分爲兩個階段,

  1. 第一階段標記,標記階段的任務是標記出所有需要被回收的對象.
  2. 第二階段清除,清除階段就是回收被標記的對象所佔用的空間(太簡單了有沒有~~~)

如下圖,灰色的就是被標記的,然後被清空了.但是有一個比較嚴重的問題就是容易產生內存碎片,碎片太多可能會導致後續過程中需要爲大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作

 

複製(Copying)

        爲了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來. 此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中。次算法每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現“碎片”問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內存空間。

 

 

標記-整理(Mark-Compact)

爲了解決Copying算法的缺陷,充分利用內存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。具體過程如下圖所示:

 

 

分代收集(Generational Collecting)

       分代收集算法是目前大部分JVM的垃圾收集器採用的算法()。它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不同的區域。一般情況下將堆區劃分爲如下的3個劃分.

  1. 老年代(Tenured Generation): 老年代的特點是每次垃圾收集時只有少量對象需要被回收
  2. 新生代(Young Generation):   新生代的特點是每次垃圾回收時都有大量的對象需要被回收
  3. 持久代(Permanent Generation): 其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。

 

新生代,老年代,持久帶詳解

先來個整體框架圖便於理解,然後對照入座理解就行了

 

新生代

       所有新生成的對象首先都是放在年輕代的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的對象。新生代又分爲如下2類區域:

  1. Eden區:大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到Survivor區(一般是兩個中的一個,同時Survivor區是可以配置爲多個的(多於兩個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能。
  2. Survivor區:當這個Survivor區滿時,此區的存活對象將被複制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的對象,將被複制“老老區(Tenured)(這裏就用到了複製(Copying)算法)

 

老年代

   當新生代中的對象經過若干輪gc後還存活/或survisor在gc內存不夠的時候(這就是個調優的方向哈,比如增大survivor的內存空間)。會把當前對象移動到老年代(這裏其實還有個閾值默認的是15次,如果對象15次內還在survivior就會放到來年代.)。老年代一般gc策略爲標記-整理(mark-compact)算法。

 

持久代

持久代一般可以不參與gc。應爲其中保存的是一些代碼/常量數據/類信息。JDK 1.8 中已經不存在持久帶

 

關於垃圾回收的分類(針對堆內存)

 

Minor GC

當Eden區被對象填滿時,就會執行Minor GC。並把所有存活下來的對象轉移到其中一個survivor區。Minor GC同樣會檢查存活下來的對象,並把它們轉移到另一個survivor區。這樣在一段時間內,總會有一個空的survivor區。經過多次GC週期後,仍然存活下來的對象會被轉移到年老代內存空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的(默認是15)。

 

Major GC

老年代的垃圾收集叫做Major GC,Major GC通常是跟full GC是等價的,收集整個GC堆。

 

分代GC

分代GC並不收集整個GC堆的模式,而是隻專注分代收集

Young GC:只收集年輕代的GC

Old GC:只收集年老代的GC(只有CMS的concurrent collection是這個模式)

Mixed GC:收集整個young gen以及部分old gen的GC(只有G1有這個模式)

 

Full GC

Full GC定義是相對明確的,就是針對整個新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局範圍的GC。full gc 只會在兩個情況下發生:1)system.gc被顯示調用時,會執行full gc。2)老年代的堆內存滿時,會執行full gc。

 

JVM參數調優方案

應該必變Full GC因爲這樣會很影響效率,還有就是避免更多的對象到老年代(特別注意新生代內不能不夠就會放到老年代的問題.).

所以新生代應該儘可能的大,避免進入老年代.同時新生代的survivor不能太小.否則也會進入老年代.同時可以將對象的手機次數增加,以提高進入老年代的門檻.  反正就是個度的問題.網上其實有很多經驗分享,但是目前先寫這些,另歡迎騷擾[email protected]

堆的初始值與堆的最大可用值相等

初始值越小,垃圾回收的次數就越多

-Xms:堆初始值
-Xmx:堆最大可用值

 

配置新生代和老年代的調優參數配置

-XX:survivorRatio = 2 Eden是from區(s0)或者to區(s1)的兩倍.默認的,Eden : from : to = 8 : 1 : 1
-XX:NewRatio = 2 設置老年區的內存大小爲新生代的兩倍



 

JConsole是什麼

從Java 5開始 引入了 JConsole。JConsole 是一個內置 Java 性能分析器,可以從命令行或在 GUI shell 中運行。您可以輕鬆地使用 JConsole(或者,它更高端的 “近親” VisualVM )來監控 Java 應用程序性能和跟蹤 Java 中的代碼。

 

啓動JConsole

直接雙擊 Java1.8\jdk1.8.0_91\bin\jconsole.exe 後本地就會啓動Jconsole.

圖示如下:

 

JAVA程序的設置讓JConsle連接

  1. 本地程序(相對於開啓JConsole的計算機),無需設置任何參數就可以被本地開啓的JConsole連接(Java SE 6開始無需設置,之前還是需要設置運行時參數 -Dcom.sun.management.jmxremote )
  2. 無認證連接 (下面的設置表示:連接的端口爲8999、無需認證就可以被連接)
    -Dcom.sun.management.jmxremote=true          開啓JVM遠程監控
    
    -Djava.rmi.server.hostname=192.168.91.166    遠程進程所在主機的IP。
    
    -Dcom.sun.management.jmxremote.port=8999     這個端口值可以任意設置,但在之後用Jconsole連接這個遠程進程的時候,遠程進程中的port一定要和此處的設置一致,並且一定不要和遠程進程的服務端口區分開。
    
    -Dcom.sun.management.jmxremote.authenticate=false  false爲不需要驗證,true爲需要驗證。
    
    -Dcom.sun.management.jmxremote.ssl=false       false爲禁用,true爲啓用。
    

     

JConsole連接遠程機器的JAVA程序(就是將上面的配置文件放置在啓動的sh裏

  1. 寫一個簡單的一直運行的JAVA程序,運行在某臺機器上如(192.168.0.181),並監聽端口8080
  2. 在另外一臺機器啓動Jconsole(別入:192.168.0.182),然後連接181上的程序 
  3. 可以使用命令直接連接 如: jconsole.exe 192.168.0.181:8080
  4. 也可以在已經打開的JConsole界面操作 連接->新建連接->選擇遠程進程->輸入遠程主機IP和端口號->點擊“連接”,如圖:

 

概覽

在連接指定的Java程序後,選擇概覽選項卡,會看到如下的圖示:(對着圖點擊右鍵可以保存數據到CSV文件,以後可以使用其他工具來分析這些數據。)

 

內存

內存選項卡如下:(內存標籤功能“執行GC”的按鈕,可以單擊執行垃圾收集。)

“詳細信息”區域顯示了當前內存信息:

  • 已用:目前使用的內存量,包括所有對象,可達和不可達佔用的內存。
  • 已提交 :保證由Java虛擬機使用的內存量。 提交的內存量可能會隨時間而改變。 Java虛擬機可能會釋放系統內存,並已提交的內存量可能會少於最初啓動時分配的內存量。 提交的內存量將始終大於或等於使用的內存量。
  • 最大值,可用於內存管理的最大內存量。 它的價值可能會發生變化,或者是不確定的。 如果Java虛擬機試圖增加使用的內存要大於提交的內存,內存分配可能失敗,即使使用量小於或等於最大值(例如,當系統上的虛擬內存不足)。
  • GC時間 :累計時間花在垃圾收集和調用的總數。 它可能有多個行,其中每一個代表一個垃圾收集器算法在Java虛擬機使用時間。(Ps MarkSweep:老年代GC,使用標記整理算法~  ,PS Scavenge: 是一種stop-the-world, 使用多個GC線程實現複製收集。如同上面複製收集一樣,但是它是並行使用多個線程。)

 

線程

在左下角的“線程”列表列出了所有的活動線程。 如果你輸入一個“過濾器”字段中的字符串,線程列表將只顯示其名稱中包含你輸入字符串線程。 點擊一個線程在線程列表的名稱,顯示該線程的信息的權利,包括線程的名稱,狀態、阻塞和等待的次數、堆棧跟蹤。

圖表顯示活動線程的數量隨着時間的推移。 兩行顯示。

  • 紅色 :峯值線程數
  •  :活動線程數。

檢測死鎖線程

要檢查如果您的應用程序已經陷入了僵局運行(例如,您的應用程序似乎是掛了),死鎖的線程可以通過點擊“檢測死鎖”按鈕檢測。 如果檢測到任何死鎖的線程,這些都顯示在一個新的標籤,旁邊出現的“主題”標籤,

 

類&VM概要

沒什麼說的

 

 

MBean

描述一個可管理的資源。是一個java對象,遵循以下一些規則:1.必須是公用的,非抽象的類 2.必須有至少一個公用的構造器 3.必須實現它自己的相應的MBean接口或者實現javax.management.DynamicMBean接口4.可選的,一個MBean可以實現javax.management.NotificationBroadcaster接口MBean的類型。 managed bean 在jmx裏面的一個管理用的bean。(這個歡迎騷擾下[email protected] 否則待續吧)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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