天天看點

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

由于我們的tomcat出現過記憶體溢出的情況,由此分析一下記憶體溢出的原因

tomcat記憶體溢出有三種情況:

1.堆記憶體(Heap)溢出

2.持久代空間(Perm Gen)溢出

3.無法建立新線程

由此又引出Heap和Perm Gen是什麼的問題

是以深入的學習了解一下JVM的知識,不過JVM需要學習的東西太多了,我隻是學習了皮毛,在此記錄一下,以後還要繼續學習

JVM是Java Virtual Machine(Java虛拟機)的縮寫,Java語言的一個非常重要的特點就是與平台的無關性。而使用Java虛拟機是實作這一特點的關鍵。一般的進階語言如果要在不同的平台上運作,至少需要編譯成不同的目标代碼。而引入Java語言虛拟機後,Java語言在不同平台上運作時不需要重新編譯。Java語言使用Java虛拟機屏蔽了與具體平台相關的資訊,使得Java語言編譯程式隻需生成在Java虛拟機上運作的目标代碼(位元組碼),就可以在多種平台上不加修改地運作。Java虛拟機在執行位元組碼時,把位元組碼解釋成具體平台上的機器指令執行。這就是Java的能夠“一次編譯,到處運作”的原因。

JVM體系分為三部分:類裝載器子系統、運作時資料區、執行引擎

每一個Java虛拟機都由一個類加載器子系統(class loader subsystem),負責加載程式中的類型(類和接口),并賦予唯一的名字。每一個Java虛拟機都有一個執行引擎(execution engine)負責執行被加載類中包含的指令。JVM的兩種類裝載器包括:啟動類裝載器和使用者自定義類裝載器,啟動類裝載器是JVM實作的一部分,使用者自定義類裝載器則是Java程式的一部分,必須是ClassLoader類的子類。

主要的執行技術有:解釋,即時編譯,自适應優化、晶片級直接執行其中解釋屬于第一代JVM,即時編譯JIT屬于第二代JVM,自适應優化(目前Sun的HotspotJVM采用這種技術)則吸取第一代JVM和第二代JVM的經驗,采用兩者結合的方式 。

我們重點學習的是運作時資料區的内容

運作時資料區又包括:方法區、堆、Java虛拟機棧、程式計數寄存器和本地方法棧

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

方法區和堆由所有線程共享,也就是每一個程序會有獨立的記憶體空間進行活動,之後的每個線程都共享這一塊記憶體。

Java虛拟機棧、程式計數寄存器是由線程獨享的,每條線程都需要有一個獨立的程式計數器,各條線程之間的計數器互不影響,獨立存儲。每條線程包含一個棧區,隻儲存基礎資料類型的對象和自定義對象的引用,每個棧區互相獨立,互不幹擾。

這就是我們需要關注的地方了,tomcat記憶體溢出的原因之一

堆記憶體分為三部分:Eden Space、Survivor Space、Tenured Gen

Eden Space和Survivor Space統一稱作年輕代(young Generation)

Tenured Gen被稱為老年代

Eden Space:對象被建立的時候首先放到這個區域,記憶體大小相對較小,GC頻繁,進行垃圾回收後,不能被回收的對象被放入到空的survivor區域。

Survivor Space:用于儲存在eden space記憶體區域中經過垃圾回收後沒有被回收的對象。Survivor有兩個,分别為To Survivor、 From Survivor,這個兩個區域的空間大小是一樣的。執行垃圾回收的時候Eden區域不能被回收的對象被放入到空的survivor(也就是To Survivor,同時Eden區域的記憶體會在垃圾回收的過程中全部釋放),另一個survivor(即From Survivor)裡不能被回收的對象也會被放入這個survivor(即To Survivor),然後To Survivor 和 From Survivor的标記會互換,始終保證一個survivor是空的。

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

年輕代中執行的垃圾回收被稱之為Minor GC(因為是對年輕代進行垃圾回收,是以又被稱為Young GC),每一次Young GC後留下來的對象age加1。年輕代的空間越小,GC的頻率就越高。

Tenured Gen:用于存放年輕代中經過多次垃圾回收仍然存活的對象,也有可能是年輕代配置設定不了記憶體的大對象會直接進入老年代。經過多次垃圾回收都沒有被回收的對象,這些對象的年代已經足夠old了,就會放入到老年代。當老年代被放滿的之後,虛拟機會進行垃圾回收,稱之為Major GC。由于Major GC除并發GC外均需對整個堆進行掃描和回收,是以又稱為Full GC。GC相對不頻繁。

是以Heap區也就是堆記憶體的大小=年輕代+老年代=Eden Space+Survivor Space+Tenured Gen 這三部分的大小

然後我們來說一說堆記憶體溢出了怎麼辦

JVM初始配置設定的堆記憶體由-Xms指定,這個參數在tomcat下的bin/catalina.sh檔案中定義,預設是實體記憶體的1/64;JVM最大配置設定的堆記憶體由-Xmx指定,預設是實體記憶體的1/4。預設空餘堆記憶體小于40%時,JVM就會增大堆直到-Xmx的最大限制;空餘堆記憶體大于70%時,JVM會減少堆直到-Xms的最小限制。是以伺服器一般設定-Xms、-Xmx 相等以避免在每次GC 後調整堆的大小。Heap Size最大不要超過實體記憶體的80%

例如:JAVA_OPTS='$JAVA_OPTS -server -Xms128m -Xmx128m',一般為2的n次幂($JAVA_OPTS是保留原先設定)

還可以指定Young Generation的大小-Xmn,也就是Eden Space+Survivor Space的大小,一般為-Xmx的3、4分之一

如果堆記憶體空間還是不夠的話,就隻能加實體記憶體了

Java 虛拟機管理堆之外的記憶體都屬于非堆記憶體,主要分析了Code Cache和Perm Gen兩部分

Code Cache:代碼緩存區,它主要用于存放JIT所編譯的代碼。CodeCache代碼緩沖區的大小在client模式下預設最大是32m,在server模式下預設是48m,這個值也是可以設定的,它所對應的JVM參數為ReservedCodeCacheSize 和 InitialCodeCacheSize,可以通過如下的方式來為Java程式設定

-XX:ReservedCodeCacheSize=128m CodeCache緩存區是可能被充滿的,當CodeCache滿時,背景會收到CodeCache is full的警告資訊

Perm Gen:Permanent Generation space,是指記憶體的永久儲存區域,因而稱之為永久代。這個記憶體區域用于存放類定義、位元組碼、常量等很少變更的資訊,Class在被加載的時候被放入這個區域。預設大小為實體記憶體的1/64。這個區域一般被認為是運作時資料區中的方法區

Perm Gen這個空間不足就是造成我們tomcat記憶體溢出的第二個原因了

GC不會在主程式運作期間對Perm Gen進行清理,但如果程式加載的類太多了,或者使用了大量的第三方jar包,其大小超過了jvm預設的大小,就有可能會造成記憶體溢出。

JVM使用-XX:PermSize設定非堆記憶體初始值,預設是實體記憶體的1/64;由-XX:MaxPermSize設定最大非堆記憶體的大小,預設是實體記憶體的1/4。

造成tomcat記憶體溢出的第三個原因:無法建立新的線程

(這種現象比較少見,也比較奇怪,主要是和jvm與系統記憶體的比例有關。

這種怪事是因為JVM已經被系統配置設定了大量的記憶體(比如1.5G),并且它至少要占用可用記憶體的一半。有人發現,線上程個數很多的情況下,你配置設定給JVM的記憶體越多,那麼,上述錯誤發生的可能性就越大。

每一個32位的程序最多可以使用2G的可用記憶體,因為另外2G被作業系統保留。這裡假設使用1.5G給JVM,那麼還餘下500M可用記憶體。這500M記憶體中的一部分必須用于系統dll的加載,那麼真正剩下的也許隻有400M,現在關鍵的地方出現了:當你使用Java建立一個線程,在JVM的記憶體裡也會建立一個Thread對象,但是同時也會在作業系統裡建立一個真正的實體線程(參考JVM規範),作業系統會在餘下的400兆記憶體裡建立這個實體線程,而不是在JVM的1500M的記憶體堆裡建立。在jdk1.4裡頭,預設的棧大小是256KB,但是在jdk1.5裡頭,預設的棧大小為1M每線程,是以,在餘下400M的可用記憶體裡邊我們最多也隻能建立400個可用線程。)這是我從網上看到的,我的了解就是建立線程的時候不光在JVM裡建立,還要在作業系統裡建立一個真正的線程,然後這個真正的線程是占用實體記憶體的,線程很多的情況下,實體記憶體就不夠用了。

我們在用zabbix監控tomcat的時候不光要監控jvm記憶體的使用情況,剛才說到加載的類太多會造成Perm Gen這個空間不足,是以還要監控java類加載的情況;tomcat線程數太多也會造成問題,還要監控tomcat線程情況

java檔案在代碼編譯後,就會生成JVM(Java虛拟機)能夠識别的位元組碼檔案(.class)。而JVM把Class檔案中的類描述資料從檔案加載到記憶體,也就是方法區,并對資料進行校驗、轉換解析、初始化,使這些資料最終成為可以被JVM直接使用的Java類型,這個說來簡單但實際複雜的過程叫做JVM的類加載機制。類從被加載到虛拟機記憶體中開始,到解除安裝出記憶體為止,它的生命周期包括了:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、解除安裝(Unloading)七個階段,其中驗證、準備、解析三個部分統稱連結。類用到才被加載。

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

當我們編寫一個java的源檔案後,經過編譯會生成一個字尾名為class的檔案,這種檔案叫做位元組碼檔案,隻有這種位元組碼檔案才能夠在java虛拟機中運作,java類的生命周期就是指一個class檔案從加載到解除安裝的全過程。

Java虛拟機自帶的類加載器所加載的類,在虛拟機的生命周期中,始終不會被解除安裝。Java虛拟機自帶的類加載器包括根類加載器、擴充類加載器和系統類加載器。Java虛拟機本身會始終引用這些類加載器,而這些類加載器則會始終引用它們所加載的類的Class對象,是以這些Class對象始終是可觸及的。由使用者自定義的類加載器加載的類是可以被解除安裝的。

tomcat每一個進來的請求都需要一個線程,直到該請求結束。tomcat伺服器每個執行個體就是一個程序,預設一個大線程池用于運作所有的webapp。在tomcat的conf/server.xml檔案中可以定義maxThreads,就是實際可同時處理的請求數,預設為200;還有acceptCount,當同時連接配接的人數達到maxThreads時,還可以接收排隊的連接配接,超過這個連接配接的則直接傳回拒絕連接配接。

Zabbix2.0起添加了支援用于監控JMX應用程式的服務程序,稱為“Zabbix-Java-gateway”,它是用java寫的一個程式。

1.安裝Zabbix-Java-gateway,把他安裝到/etc/zabbix目錄下

2.修改Java-gateway的配置檔案并啟動它(zabbix_java_gateway.conf)

啟動

3.修改zabbix_server的配置檔案并重新開機(zabbix_server.conf)

然後配置被監控端,開啟JMX

1.下載下傳catalina-jmx-remote.jar

将下載下傳後後的jar包放到被監控的tomcat執行個體的lib目錄下。

2.修改tomcat的server.xml檔案,添加下面這句,定義JMX端口

<code>&amp;lt;Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener" rmiRegistryPortPlatform="13373" rmiServerPortPlatform="13374" /&amp;gt;</code>

我一開始從網上查到的端口資訊要加到下面的catalina.sh檔案那句話裡,但是後來測試的時候 jmx始終報紅,可是防火牆也已經開放端口了,後來我又查到,除了定義好的端口之外,JMX還會随機開放一個端口用來擷取資料,是以兩個端口就定義到server.xml檔案中,在防火牆開放這兩個端口就可以了

3.修改tomcat/bin/下的catalina.sh,添加如下内容:

<code>CATALINA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=ip位址"</code>

hostname為要擷取資料的被監控端ip

4.重新開機tomcat

5.注意添加防火牆規則,添加server.xml中定義的端口

6.測試是否可以擷取資料

指令行下測試需要cmdline-jmxclient-0.10.3.jar這個包,在服務端測試是否可以拿到資料

<code>java -jar cmdline-jmxclient-0.10.3.jar controlRole:tomcat ip:端口 java.lang:type=Memory NonHeapMemoryUsage</code>

注意cmdline-jmxclient-0.10.3.jar所在的路徑,我是在root目錄下測試,jar包也在root目錄下,如果能拿到資料,那麼配置就沒問題了

浏覽器連接配接到zabbix伺服器頁面

1.建立模闆

zabbix自帶有監控JMX模闆,但是很多都沒有用,然後就自己建立了一個模闆,監控堆記憶體,非堆記憶體,tomcat線程等等

配置tomcat線程的鍵值需要通路tomcat的端口号,于是這個不是加載模闆裡的,是一個一個複制的

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat
JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

2.建立主機

由于鍵值不能重複,是以每一個tomcat都作為一個主機,加到對應的機器組裡

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

3.連接配接模闆

建立主機的時候就連接配接模闆

4.建立觸發器

設定了記憶體超過85%就報警

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

5.建立圖形

建立圖形,添加監控項

JVM了解及zabbix監控tomcat背景JVMzabbix監控tomcat

6.測試

如果監控項顯示不支援的話,看一下鍵值是否正确,到伺服器上測試是否能擷取到資料,也有可能是加載時間較慢,耐心等待一下就有了,然後出來圖形就可以監控了

本文轉自 xinsir999 51CTO部落格,原文連結:http://blog.51cto.com/xinsir/2057660,如需轉載請自行聯系原作者