問題說明:
公司內網環境中部署的jenkins代碼發版平臺忽然不能訪問了,查看tomcat的catalina.out日誌發現報錯以下:java
[root@redmine logs]# tail -f /srv/apache-tomcat-7.0.67/logs/catalina.out ...... Exception in thread "http-bio-8080-exec-5" java.lang.OutOfMemoryError: PermGen space Exception in thread "http-bio-8080-exec-5" java.lang.OutOfMemoryError: PermGen space Exception in thread "http-bio-8080-exec-5" java.lang.OutOfMemoryError: PermGen space ......
上面報錯是因爲tomcat內存溢出引發的:程序員
[root@redmine logs]# ps -ef|grep tomcat root 23615 1 14 15:15 ? 00:04:45 /usr/java/jdk1.7.0_79/bin/java -Djava.util.logging.config.file=/srv/apache-tomcat-7.0.67/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/srv/apache-tomcat-7.0.67/endorsed -classpath /srv/apache-tomcat-7.0.67/bin/bootstrap.jar:/srv/apache-tomcat-7.0.67/bin/tomcat-juli.jar -Dcatalina.base=/srv/apache-tomcat-7.0.67 -Dcatalina.home=/srv/apache-tomcat-7.0.67 -Djava.io.tmpdir=/srv/apache-tomcat-7.0.67/temp org.apache.catalina.startup.Bootstrap start root 24191 24013 0 15:49 pts/6 00:00:00 grep tomcat
Tomcat默承認以使用的內存爲128MB,在較大型的應用項目中,這點內存顯然是不夠的,從而有可能致使系統沒法運行!
其中常見的內存問題是報Tomcat內存溢出錯誤,Out of Memory(系統內存不足)的異常,從而致使客戶端顯示500錯誤。
在生產環境中,tomcat內存設置很差很容易出現JVM內存溢,解決方法就是:修改Tomcat中的catalina.sh文件(windows系統下修改的文件時catalina.bat)。在catalina.sh文件中,找到cygwin=false,在這一行的前面加入參數,具體以下:web
[root@redmine bin]# pwd /srv/apache-tomcat-7.0.67/bin [root@redmine bin]# vim catalina.sh //在cygwin=false這一行的上面添加下面內容 ...... JAVA_OPTS='-Xms1024m -Xmx1024m -XX:PermSize=256M -XX:MaxNewSize=512m -XX:MaxPermSize=512m' cygwin=false ......
其中,-Xms設置初始化內存大小,-Xmx設置可使用的最大內存。通常把-Xms和-Xmx設爲同樣大算法
接着重啓tomcat便可,重啓後查看tomcat服務進程,就能看到內存信息了:數據庫
[root@redmine bin]# ps -ef|grep tomcat|grep -v grep|awk -F" " '{print $2}'|xargs kill -9 [root@redmine bin]# /srv/apache-tomcat-7.0.67/bin/startup.sh [root@redmine bin]# ps -ef|grep tomcat root 24547 1 3 15:53 pts/6 00:01:02 /usr/java/jdk1.7.0_79/bin/java -Djava.util.logging.config.file=/srv/apache-tomcat-7.0.67/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Xms1024m -Xmx1024m -XX:PermSize=256M -XX:MaxNewSize=512m -XX:MaxPermSize=512m -Djava.endorsed.dirs=/srv/apache-tomcat-7.0.67/endorsed -classpath /srv/apache-tomcat-7.0.67/bin/bootstrap.jar:/srv/apache-tomcat-7.0.67/bin/tomcat-juli.jar -Dcatalina.base=/srv/apache-tomcat-7.0.67 -Dcatalina.home=/srv/apache-tomcat-7.0.67 -Djava.io.tmpdir=/srv/apache-tomcat-7.0.67/temp org.apache.catalina.startup.Bootstrap start root 24982 24013 0 16:22 pts/6 00:00:00 grep tomcat
Tomcat鏈接數設置:在tomcat配置文件server.xml中的<Connector ... />配置中,和鏈接數相關的參數有:
minProcessors: 最小空閒鏈接線程數,用於提升系統處理性能,默認值爲10
maxProcessors: 最大鏈接線程數,即:併發處理的最大請求數,默認值爲75
maxThreads 最大併發線程數,即同時處理的任務個數,默認值是200
acceptCount: 容許的最大鏈接數,應大於等於maxProcessors,默認值爲100
enableLookups: 是否反查域名,取值爲:true或false。爲了提升處理能力,應設置爲false
connectionTimeout: 網絡鏈接超時,單位:毫秒。設置爲0表示永不超時,這樣設置有隱患的。一般可設置爲30000毫秒。apache
其中和最大鏈接數相關的參數爲maxProcessors和acceptCount。若是要加大併發鏈接數,應同時加大這兩個參數。
web server容許的最大鏈接數還受制於操做系統的內核參數設置,一般Windows是2000個左右,Linux是1000個左右。Unix中如何設置這些參數,請參閱Unix經常使用監控和管理命令編程
例如:bootstrap
<Connector port="8888" protocol="HTTP/1.1" maxThreads="500" minSpareThreads="50" maxSpareThreads="100" enableLookups="false" acceptCount="2000" connectionTimeout="20000" redirectPort="8443" />
Tomcat內存溢出,堆棧配置的JVM參數說明:Xmn、Xms、Xmx、Xss都是JVM對內存的配置參數,能夠根據不一樣須要區修改這些參數,以達到運行程序的最好效果。
-Xms:堆內存的最小大小,默認爲物理內存的1/64。即初始堆大小。
-Xmx:堆內存的最大大小,默認爲物理內存的1/4。即最大堆大小。
-Xmn:堆內新生代的大小。經過這個值也能夠獲得老生代的大小:-Xmx減去-Xmn。即新生代大小。
-Xss:設置每一個線程可以使用的內存大小,即棧的大小。在相同物理內存下,減少這個值能生成更多的線程,固然操做系統對一個進程內的線程數仍是有限制的,不能無限生成。線程棧的大小是個雙刃劍,若是設置太小,可能會出現棧溢出,特別是在該線程內有遞歸、大的循環時出現溢出的可能性更大,若是該值設置過大,就有影響到建立棧的數量,若是是多線程的應用,就會出現內存溢出的錯誤。vim
除了這些配置,JVM還有很是多的配置,經常使用的梳理以下:
1、堆設置
-XX:NewRatio: 設置新生代和老年代的比值。如:爲3,表示年輕代與老年代比值爲1:3
-XX:SurvivorRatio: 新生代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:爲3,表示Eden:Survivor=3:2,一個Survivor區佔整個新生代的1/5
-XX:MaxTenuringThreshold: 設置轉入老年代的存活次數。若是是0,則直接跳過新生代進入老年代
-XX:PermSize、-XX:MaxPermSize: 分別設置永久代最小大小與最大大小(Java8之前)。即第一個是設定內存的永久保存區域,第二個是設定最大內存的永久保存區域。
-XX:MetaspaceSize、-XX:MaxMetaspaceSize: 分別設置元空間最小大小與最大大小(Java8之後)windows
2、收集器設置
-XX:+UseSerialGC: 設置串行收集器
-XX:+UseParallelGC: 設置並行收集器
-XX:+UseParalledlOldGC: 設置並行老年代收集器
-XX:+UseConcMarkSweepGC: 設置併發收集器
3、垃圾回收統計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
4、並行收集器設置
-XX:ParallelGCThreads=n: 設置並行收集器收集時使用的CPU數。並行收集線程數。
-XX:MaxGCPauseMillis=n: 設置並行收集最大暫停時間
-XX:GCTimeRatio=n: 設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n)
5、併發收集器設置
-XX:+CMSIncrementalMode: 設置爲增量模式。適用於單CPU狀況。
-XX:ParallelGCThreads=n: 設置併發收集器新生代收集方式爲並行收集時,使用的CPU數。並行收集線程數。
以下面是一個jvm進程是查詢出來(ps -ef)的參數設置狀況:
-Xmx256m -Xms256m -Xmn64m -XX:PermSize=64m -Xss256k -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
TOMCAT組件一概使用平滑啓停模式,請確保全部應用系統能夠平滑啓停。 啓動命令: $ CATALINA_HOME/bin/startup.sh 中止命令: $ CATALINA_HOME/bin/shutdown.sh TOMCAT配置: TOMCAT組件使用標準化部署,對於jvm基本配置的調整有如下兩個途徑: 1)在tomcat安裝目錄的bin/catalina.sh腳本里調整(添加下面內容) [app@kevin bin]$ vim ../bin/catalina.sh #!/bin/sh # export JAVA_OPTS=" -server # -server:必定要做爲第一個參數,在多個CPU時性能佳 -Xms2048M # 初始Heap大小,使用的最小內存,cpu性能高時此值應設的大一些 -Xmx2048M # java heap最大值,使用的最大內存 -Xmn512M # young generation的heap大小,通常設置爲Xmx的三、4分之一 -Xss1024K # 每一個線程的Stack大小 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=7 -XX:GCTimeRatio=19 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:+DisableExplicitGC -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -Xloggc:/var/log/bsp/tomcat_8082_servicefront/gc.log" .............. .............. 2)在tomcat安裝目錄的bin/setenv.sh腳本里調整 [app@kevin bin]$ vim ../bin/catalina.sh JAVA_HOME=/root/base/jdk8 JAVA_OPTS=" -server -Xms2048M -Xmx2048M -Xmn521M -Xss1024K -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=7 -XX:GCTimeRatio=19 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:+DisableExplicitGC -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -Xloggc:/var/log/tomcat_19090_jenkins/gc.log -Dhudson.model.ParametersAction.keepUndefinedParameters=true -DJENKINS_HOME='/mnt/jenkins_config' -Djava.awt.headless=true"
Tomcat常見內存溢出的幾種狀況:
OutOfMemoryError:Java heap space
OutOfMemoryError:GC overhead limit exceeded
OutOfMemoryError:Permgen space
OutOfMemoryError:Metaspace
1)OutOfMemoryError:Java heap space
每一個Java程序都只能使用必定量的內存, 這種限制是由JVM的啓動參數決定的。而更復雜的狀況在於, Java程序的內存分爲兩部分: 堆內存(Heap space)和 永久代(Permanent Generation, 簡稱 Permgen):
這兩個區域的最大內存大小, 由JVM啓動參數 -Xmx 和 -XX: MaxPermSize 指定. 若是沒有明確指定, 則根據平臺類型(好比OS版本+ JVM版本)和物理內存的大小來肯定。假如在建立新的對象時, 堆內存中的空間不足以存放新建立的對象, 就會引起java.lang.OutOfMemoryError: Java heap space 錯誤。無論機器上還有沒有空閒的物理內存, 只要堆內存使用量達到最大內存限制, 就會拋出 java.lang.OutOfMemoryError: Java heap space 錯誤。
緣由分析
產生 java.lang.OutOfMemoryError: Java heap space 錯誤的緣由, 不少時候, 就相似於將 XXL 號的對象,往 S 號的 Java heap space 裏面塞。其實清楚了緣由, 就很容易解決。 只要增長堆內存的大小, 程序就能正常運行. 另外還有一些比較複雜的狀況, 主要是由代碼問題致使的:
a)超出預期的訪問量/數據量。 應用系統設計時,通常是有 「容量」 定義的, 部署這麼多機器, 用來處理必定量的數據/業務。 若是訪問量忽然飆升, 超過預期的閾值, 相似於時間座標系中針尖形狀的圖譜, 那麼在峯值所在的時間段, 程序極可能就會卡死、並觸發 java.lang.OutOfMemoryError: Java heap space 錯誤。
b)內存泄露(Memory leak). 這也是一種常常出現的情形。因爲代碼中的某些錯誤, 致使系統佔用的內存愈來愈多. 若是某個方法/某段代碼存在內存泄漏的, 每執行一次, 就會(有更多的垃圾對象)佔用更多的內存. 隨着運行時間的推移, 泄漏的對象耗光了堆中的全部內存, 那麼 java.lang.OutOfMemoryError: Java heap space 錯誤就爆發了。
解決方案
若是設置的最大內存不知足程序的正常運行, 只須要增大堆內存便可。但不少狀況下, 增長堆內存空間並不能解決問題。好比存在內存泄漏, 增長堆內存只會推遲 java.lang.OutOfMemoryError: Java heap space 錯誤的觸發時間。固然, 增大堆內存, 可能會增長 GC pauses 的時間, 從而影響程序的 吞吐量或延遲。
2)OutOfMemoryErro: GC overhead limit exceeded
Java運行時環境內置了垃圾收集(GC) 模塊. 上一代的不少編程語言中並無自動內存回收機制, 須要程序員手工編寫代碼來進行內存分配和釋放, 以重複利用堆內存。在Java程序中, 只須要關心內存分配就行。若是某塊內存再也不使用, 垃圾收集(Garbage Collection) 模塊會自動執行清理。通常來講, JVM內置的垃圾收集算法就可以應對絕大多數的業務場景。java.lang.OutOfMemoryError: GC overhead limit exceeded 這種狀況發生的緣由是, 程序基本上耗盡了全部的可用內存, GC也清理不了。暫時解決這個問題de 方法: 1)增長參數,-XX:-UseGCOverheadLimit,關閉這個特性;2)同時增長heap大小:-Xmx1024m。
緣由分析
JVM拋出 java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤就是發出了這樣的信號: 執行垃圾收集的時間比例太大, 有效的運算量過小. 默認狀況下, 若是GC花費的時間超過 98%, 而且GC回收的內存少於 2%, JVM就會拋出這個錯誤。
須要注意:java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤只在連續屢次 GC 都只回收了不到2%的極端狀況下才會拋出。假如不拋出 GC overhead limit 錯誤會發生什麼狀況呢? 那就是GC清理的這麼點內存很快會再次填滿, 迫使GC再次執行. 這樣就造成惡性循環, CPU使用率一直是100%, 而GC卻沒有任何成果. 系統用戶就會看到系統卡死 - 之前只須要幾毫秒的操做, 如今須要好幾分鐘才能完成。
解決方案
有一種應付了事的解決方案, 就是不想拋出 「java.lang.OutOfMemoryError: GC overhead limit exceeded」 錯誤信息, 則添加下面啓動參數:
#不推薦 -XX:-UseGCOverheadLimit
強烈建議不要指定該選項: 由於這不能真正地解決問題,只能推遲一點 out of memory 錯誤發生的時間,到最後還得進行其餘處理。指定這個選項, 會將原來的 java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤掩蓋,變成更常見的 java.lang.OutOfMemoryError: Java heap space 錯誤消息。
須要注意: 有時候觸發 GC overhead limit 錯誤的緣由, 是由於分配給JVM的堆內存不足。這種狀況下只須要增長堆內存大小便可。在大多數狀況下, 增長堆內存並不能解決問題。例如程序中存在內存泄漏, 增長堆內存只能推遲產生 java.lang.OutOfMemoryError: Java heap space 錯誤的時間。固然, 增大堆內存, 還有可能會增長 GC pauses 的時間, 從而影響程序的 吞吐量或延遲。若是想從根本上解決問題, 則須要排查內存分配相關的代碼. 簡單來講, 須要回答如下問題:
能夠根據內存分析的結果, 以及Plumbr生成的報告(下面會提到), 若是發現對象佔用的內存很合理, 也不須要修改源代碼的話, 那就增大堆內存吧。在這種狀況下,修改JVM啓動參數, (按比例)增長下面的值:
-Xmx1024m
這裏配置了最大堆內存爲 1024MB。能夠根據實際狀況修改這個值. 若是JVM仍是會拋出 OutOfMemoryError, 那麼可能還須要查詢手冊, 或者藉助工具再次進行分析和診斷。也可使用 g/G 表示 GB, m/M 表明 MB, k/K 表示 KB.下面的這些形式都是等價的, 設置Java堆的最大空間爲 1GB:
# 等價形式: 最大1GB內存 java -Xmx1073741824 com.mycompany.MyClass java -Xmx1048576k com.mycompany.MyClass java -Xmx1024m com.mycompany.MyClass java -Xmx1g com.mycompany.MyClass
3)OutOfMemoryError: Permgen space
PermGen space是指內存的永久保存區域。OutOfMemoryError: PermGen space從表面上看就是內存益出,解決方法也必定是加大內存。說說爲何會內存益出:這一部分用於存放Class和Meta的信息,Class在被 Load的時候被放入PermGen space區域,它和和存放Instance的Heap區域不一樣,GC(Garbage Collection)不會在主程序運行期對PermGen space進行清理,因此若是你的APP會LOAD不少CLASS的話,就極可能出現PermGen space錯誤。這種錯誤常見在web服務器對JSP進行pre compile的時候。
JVM限制了Java程序的最大內存使用量, 能夠經過啓動參數來配置。而Java的堆內存被劃分爲多個區域, 以下圖所示:
這些區域的最大值, 由JVM啓動參數 -Xmx 和 -XX:MaxPermSize 指定. 若是沒有明確指定, 則根據操做系統平臺和物理內存的大小來肯定。java.lang.OutOfMemoryError: PermGen space 錯誤信息所表達的意思是: 永久代(Permanent Generation) 內存區域已滿。
緣由分析:咱們先看看 PermGen 是用來幹什麼的?
在JDK1.7及以前的版本, 永久代(permanent generation) 主要用於存儲加載/緩存到內存中的 class 定義, 包括 class 的 名稱(name), 字段(fields), 方法(methods)和字節碼(method bytecode); 以及常量池(constant pool information); 對象數組(object arrays)/類型數組(type arrays)所關聯的 class, 還有 JIT 編譯器優化後的class信息等。很容易看出, PermGen 的使用量和JVM加載到內存中的 class 數量/大小有關。能夠說 java.lang.OutOfMemoryError: PermGen space 的主要緣由, 是加載到內存中的 class 數量太多或體積太大。
解決方案
a)解決程序啓動時產生的 OutOfMemoryError
在程序啓動時, 若是 PermGen 耗盡而產生 OutOfMemoryError 錯誤, 那很容易解決. 增長 PermGen 的大小, 讓程序擁有更多的內存來加載 class 便可. 修改 -XX:MaxPermSize 啓動參數, 相似下面這樣:
java -XX:MaxPermSize=512m com.yourcompany.YourClass
以上配置容許JVM使用的最大 PermGen 空間爲 512MB
, 若是還不夠, 就會拋出 OutOfMemoryError。
b)解決運行時產生的 OutOfMemoryError
若是在運行的過程當中發生 OutOfMemoryError, 首先須要確認 GC是否能從PermGen中卸載class。 官方的JVM在這方面是至關的保守(在加載class以後,就一直讓其駐留在內存中,即便這個類再也不被使用). 可是, 現代的應用程序在運行過程當中, 會動態建立大量的class, 而這些class的生命週期基本上都很短暫, 舊版本的JVM 不能很好地處理這些問題。那麼咱們就須要容許JVM卸載class。使用下面的啓動參數:
-XX:+CMSClassUnloadingEnabled
默認狀況下 CMSClassUnloadingEnabled 的值爲false, 因此須要明確指定。 啓用之後, GC 將會清理 PermGen, 卸載無用的 class. 固然, 這個選項只有在設置 UseConcMarkSweepGC 時生效。 若是使用了 ParallelGC, 或者 Serial GC 時, 那麼須要切換爲CMS:
-XX:+UseConcMarkSweepGC
經過來講,java.lang.OutOfMemoryError: PermGen space有效解決方法:手動設置MaxPermSize大小修改TOMCAT_HOME/bin/catalina.sh
在 catalina.sh 文件 echo "Using CATALINA_BASE: $CATALINA_BASE"上面或者第一行加入如下行: set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=256M -XX:MaxPermSize=512m
4)OutOfMemoryError: Metaspace
Metaspace能夠簡單理解爲爲存放的類元信息和方法信息的一個JVM 塊,Metaspace是JDK1.8的新特性,能夠把它理解爲JDK1.6的PermGen區域(可是二者也有很大區別的)。JVM限制了Java程序的最大內存, 修改/指定啓動參數能夠改變這種限制。Java將堆內存劃分爲多個部分, 以下圖所示:
[Java8及以上版本]這些內存池的最大值, 由 -Xmx 和 -XX:MaxMetaspaceSize 等JVM啓動參數指定. 若是沒有明確指定, 則根據平臺類型(OS版本+JVM版本)和物理內存的大小來肯定。java.lang.OutOfMemoryError: Metaspace 錯誤所表達的信息是: 元數據區(Metaspace) 已被用滿
緣由分析
若是你是Java老司機, 應該對 PermGen 比較熟悉. 但從Java 8開始,內存結構發生重大改變, 再也不使用Permgen, 而是引入一個新的空間: Metaspace. 這種改變基於多方面的考慮, 部分緣由列舉以下:Permgen空間的具體多大很難預測。指定小了會形成 java.lang.OutOfMemoryError: Permgen size 錯誤, 設置多了又形成浪費。
a)爲了 GC 性能 的提高, 使得垃圾收集過程當中的併發階段再也不 停頓, 另外對 metadata 進行特定的遍歷(specific iterators)。
b)對 G1垃圾收集器 的併發 class unloading 進行深度優化。
c)在Java8中,將以前 PermGen 中的全部內容, 都移到了 Metaspace 空間。例如: class 名稱, 字段, 方法, 字節碼, 常量池, JIT優化代碼, 等等。
Metaspace 使用量與JVM加載到內存中的 class 數量/大小有關。能夠說java.lang.OutOfMemoryError: Metaspace 錯誤的主要緣由,是加載到內存中的 class 數量太多或者體積太大。
解決方案
若是拋出與 Metaspace 有關的 OutOfMemoryError , 第一解決方案是增長 Metaspace 的大小. 使用下面這樣的啓動參數:
-XX:MaxMetaspaceSize=512m
這裏將 Metaspace 的最大值設置爲 512MB, 若是沒有用完, 就不會拋出 OutOfMemoryError。
有一種看起來很簡單的方案:直接去掉 Metaspace 的大小限制。 但須要注意:不限制Metaspace內存的大小, 倘若物理內存不足, 有可能會引發內存交換(swapping), 嚴重拖累系統性能。 此外,還可能形成native內存分配失敗等問題。在現代應用集羣中,寧肯讓應用節點掛掉, 也不但願其響應緩慢。若是不想收到報警, 能夠像鴕鳥同樣, 把 java.lang.OutOfMemoryError: Metaspace 錯誤信息隱藏起來。 但這不能真正解決問題, 只會推遲問題爆發的時間。 若是確實存在內存泄露, 請參考前面的文章, 認真尋找解決方案。
要從根本上解決上面說到的這四種Tomcat 內存溢出 "OutOfMemoryError" 問題, 則需排查分配內存的代碼. 簡單來講, 須要解決這些問題:
a)哪類對象佔用了最多內存?
b)這些對象是在哪部分代碼中分配的。
要搞清出這一點, 可能須要好幾天時間。下面是大體的流程:
a)得到在生產服務器上執行堆轉儲(heap dump)的權限。"轉儲"(Dump)是堆內存的快照, 稍後能夠用於內存分析. 這些快照中可能含有機密信息, 例如密碼、信用卡帳號等, 因此有時候, 因爲企業的安全限制, 要得到生產環境的堆轉儲並不容易。
b)在適當的時間執行堆轉儲。通常來講,內存分析須要比對多個堆轉儲文件, 假如獲取的時機不對, 那就多是一個"廢"的快照. 另外, 每次執行堆轉儲, 都會對JVM進行"凍結", 因此生產環境中,也不能執行太多的Dump操做,不然系統緩慢或者卡死,你的麻煩就大了。
c)用另外一臺機器來加載Dump文件。通常來講, 若是出問題的JVM內存是8GB, 那麼分析 Heap Dump 的機器內存須要大於 8GB. 打開轉儲分析軟件(咱們推薦Eclipse MAT , 固然你也可使用其餘工具)。
d)檢測快照中佔用內存最大的 GC roots。
接下來, 找出可能會分配大量對象的代碼. 若是對整個系統很是熟悉, 可能很快就能定位了。這裏推薦一款檢查內存泄露工具 plumbr 。 Plumbr 能捕獲全部java.lang.OutOfMemoryError , 並找出其餘的性能問題, 例如最消耗內存的數據結構等等。Plumbr 在後臺負責收集數據 —— 包括堆內存使用狀況(只統計對象分佈圖, 不涉及實際數據),以及在堆轉儲中不容易發現的各類問題。 若是發生 java.lang.OutOfMemoryError , 還能在不停機的狀況下, 作必要的數據處理. 下面是Plumbr 對一個 java.lang.OutOfMemoryError 的提醒:
以上能直接看到:
a) 哪類對象佔用了最多的內存(此處是 271 個 com.example.map.impl.PartitionContainer 實例, 消耗了 173MB 內存, 而堆內存只有 248MB)
b)這些對象在何處建立(大部分是在 MetricManagerImpl 類中,第304行處)
c)當前是誰在引用這些對象(從 GC root 開始的完整引用鏈)
得知這些信息, 就能夠定位到問題的根源, 例如是當地精簡數據結構/模型, 只佔用必要的內存便可。
來看一個案例:公司的jenkins在使用過程當中出現報錯 "java.lang.OutOfMemoryError: Metaspace"
解決辦法:
[root@kevin ~]# ps -ef|grep tomcat_jenkins root 59446 4761 12 May27 ? 5-12:10:08 /root/base/jdk8/bin/java -Djava.util.logging.config.file=/root/tomcat_jenkins/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Xms5120M -Xmx5120M -Xmn1500M -Xss1024K -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=7 -XX:GCTimeRatio=19 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:+DisableExplicitGC -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -Xloggc:/var/log/tomcat_19090_jenkins/gc.log -Dhudson.model.ParametersAction.keepUndefinedParameters=true -DJENKINS_HOME=/mnt/jenkins_config -Djava.awt.headless=true -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -classpath /root/tomcat_jenkins/bin/bootstrap.jar:/root/tomcat_jenkins/bin/tomcat-juli.jar -Dcatalina.base=/root/tomcat_jenkins -Dcatalina.home=/root/tomcat_jenkins -Djava.io.tmpdir=/root/tomcat_jenkins/temp org.apache.catalina.startup.Bootstrap start 發現當前Metaspace元數據區設置爲512M,查看配置: [root@kevin ~]# cat /root/tomcat_jenkins/bin/setenv.sh JAVA_HOME=/root/base/jdk8 ....... ....... -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m 將setenv.sh文件中的Metaspace相關的jvm值大點 [root@kevin ~]# cat /root/tomcat_jenkins/bin/setenv.sh ....... ....... -XX:MetaspaceSize=1024m -XX:MaxMetaspaceSize=1024m 而後重啓這個tomcat_jenkins服務便可
Java內存溢出(java.lang.OutOfMemoryError)常見問題運維總結
在解決java內存溢出問題以前,須要對jvm(java虛擬機)的內存管理有必定的認識。jvm管理的內存大體包括三種不一樣類型的內存區域:Permanent Generation space(永久保存區域)、Heap space(堆區域)、Java Stacks(Java棧)。其中:
- Permanent Generation space主要存放Class(類)和Meta信息,Class第一次被Load時被放入PermGen space區域,Class須要存儲的內容主要包括方法和靜態屬性。
- Heap space用來存放Class實例(即對象), 對象需存儲的內容主要是非靜態屬性。每次用new建立一個對象實例後,對象實例存儲在堆區域中,這部分空間被jvm垃圾回收機制管理。
- Java StacksJava棧跟大多數編程語言包括彙編語言的棧功能類似,主要基本類型變量以及方法的輸入輸出參數。Java程序的每一個線程中都有一個獨立的堆棧。
注意:容易發生內存溢出問題的內存空間包括:Permanent Generation space和Heap space。
1)第一種 OutOfMemoryError: PermGen space (1.8後更新爲Metaspace,不存在)
PermSpace主要是存放靜態的類信息和方法信息,靜態的方法和變量,final標註的常量信息等。發生這種問題的原意是程序中使用了大量的jar或class,使java虛擬機裝載類的空間不夠,與Permanent Generation space有關。解決這類問題有如下兩種辦法:
a)增長java虛擬機中的XX:PermSize和XX:MaxPermSize參數的大小,其中XX:PermSize是初始永久保存區域大小,XX:MaxPermSize是最大永久保存區域大小。如針對tomcat6.0,在catalina.sh 或catalina.bat文件中一系列環境變量名說明結束處(大約在70行左右) 增長一行:
JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"
b)清理應用程序中web-inf/lib下的jar,若是tomcat部署了多個應用,不少應用都使用了相同的jar,能夠將共同的jar移到tomcat共同的lib下,減小類的重複加載。
第二種 OutOfMemoryError: Java heap space
發生這種問題的緣由是java虛擬機建立的對象太多,在進行垃圾回收之間,虛擬機分配的到堆內存空間已經用滿了,與Heap space有關。解決這類問題有兩種思路:
a)檢查代碼中是否有死循環或遞歸調用;檢查是否有大循環重複產生新對象實體;檢查對數據庫查詢中是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前數據庫中數據較少,不容易出問題,上線後數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢;檢查List、MAP等集合對象是否有使用完後未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。
b)增長Java虛擬機中Xms(初始堆大小)和Xmx(最大堆大小)參數的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m
第三種 OutOfMemoryError:unable to create new native thread
在java應用中有時候會出現這樣的錯誤:OutOfMemoryError: unable to create new native thread。這種怪事是由於JVM已經被系統分配了大量的內存(好比1.5G),而且它至少要佔用可用內存的一半。有人發現,在線程個數不少的狀況下,你分配給JVM的內存越多,那麼,上述錯誤發生的可能性就越大。對於這個異常咱們首先須要判斷下,發生內存溢出時進程中到底都有什麼樣的線程,這些線程是不是應該存在的,是否能夠經過優化來下降線程數; 另一方面默認狀況下java爲每一個線程分配的棧內存大小是1M,一般狀況下,這1M的棧內存空間是足足夠用了,由於在一般在棧上存放的只是基礎類型的數據或者對象的引用,這些東西都不會佔據太大的內存, 咱們能夠經過調整jvm參數,下降爲每一個線程分配的棧內存大小來解決問題,例如在jvm參數中添加-Xss128k將線程棧內存大小設置爲128k。