Tomcat 內存溢出 "OutOfMemoryError" 問題總結 (JVM參數說明)


 

問題說明:
公司內網環境中部署的jenkins代碼發版平台突然不能訪問了,查看tomcat的catalina.out日志發現報錯如下:

[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,在這一行的前面加入參數,具體如下:

[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毫秒。

其中和最大連接數相關的參數為maxProcessors和acceptCount如果要加大並發連接數,應同時加大這兩個參數。
web server允許的最大連接數還受制於操作系統的內核參數設置,通常Windows是2000個左右,Linux是1000個左右。Unix中如何設置這些參數,請參閱Unix常用監控和管理命令

例如:

<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:設置每個線程可使用的內存大小,即棧的大小。在相同物理內存下,減小這個值能生成更多的線程,當然操作系統對一個進程內的線程數還是有限制的,不能無限生成。線程棧的大小是個雙刃劍,如果設置過小,可能會出現棧溢出,特別是在該線程內有遞歸、大的循環時出現溢出的可能性更大,如果該值設置過大,就有影響到創建棧的數量,如果是多線程的應用,就會出現內存溢出的錯誤。

除了這些配置,JVM還有非常多的配置,常用的梳理如下:
一、堆設置
-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以后)

二、收集器設置
-XX:+UseSerialGC:  設置串行收集器
-XX:+UseParallelGC:  設置並行收集器
-XX:+UseParalledlOldGC:  設置並行老年代收集器
-XX:+UseConcMarkSweepGC:  設置並發收集器

三、垃圾回收統計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename

四、並行收集器設置
-XX:ParallelGCThreads=n:  設置並行收集器收集時使用的CPU數。並行收集線程數。
-XX:MaxGCPauseMillis=n:  設置並行收集最大暫停時間
-XX:GCTimeRatio=n:  設置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)

五、並發收集器設置
-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的3、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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM