3 jmx監控tomcat
http://blog.csdn.net/l1028386804/article/details/51547408
import java.lang.management.MemoryUsage;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
/**
* @author liuyazhuang
* @date 2016-05-31
*/
public class JMXTest {
/**
* main方法
* @param args
*/
public static void main(String[] args) {
try {
String jmxURL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:8999/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(jmxURL);
Map map = new HashMap();
String[] credentials = new String[] { "monitorRole", "tomcat" };
map.put("jmx.remote.credentials", credentials);
JMXConnector connector = JMXConnectorFactory.connect(serviceURL,
map);
MBeanServerConnection mbsc = connector.getMBeanServerConnection();
// 端口最好是動態取得
ObjectName threadObjName = new ObjectName(
"Catalina:type=ThreadPool,name=http-8080");
MBeanInfo mbInfo = mbsc.getMBeanInfo(threadObjName);
String attrName = "currentThreadCount";// tomcat的線程數對應的屬性值
MBeanAttributeInfo[] mbAttributes = mbInfo.getAttributes();
System.out.println("currentThreadCount:"
+ mbsc.getAttribute(threadObjName, attrName));
// heap
for (int j = 0; j < mbsc.getDomains().length; j++) {
System.out.println("###########" + mbsc.getDomains()[j]);
}
Set MBeanset = mbsc.queryMBeans(null, null);
System.out.println("MBeanset.size() : " + MBeanset.size());
Iterator MBeansetIterator = MBeanset.iterator();
while (MBeansetIterator.hasNext()) {
ObjectInstance objectInstance = (ObjectInstance) MBeansetIterator
.next();
ObjectName objectName = objectInstance.getObjectName();
String canonicalName = objectName.getCanonicalName();
System.out.println("canonicalName : " + canonicalName);
if (canonicalName
.equals("Catalina:host=localhost,type=Cluster")) {
// Get details of cluster MBeans
System.out.println("Cluster MBeans Details:");
System.out
.println("=========================================");
// getMBeansDetails(canonicalName);
String canonicalKeyPropList = objectName
.getCanonicalKeyPropertyListString();
}
}
// ------------------------- system ----------------------
ObjectName runtimeObjName = new ObjectName("java.lang:type=Runtime");
System.out.println("廠商:"
+ (String) mbsc.getAttribute(runtimeObjName, "VmVendor"));
System.out.println("程序:"
+ (String) mbsc.getAttribute(runtimeObjName, "VmName"));
System.out.println("版本:"
+ (String) mbsc.getAttribute(runtimeObjName, "VmVersion"));
Date starttime = new Date((Long) mbsc.getAttribute(runtimeObjName,
"StartTime"));
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("啓動時間:" + df.format(starttime));
Long timespan = (Long) mbsc.getAttribute(runtimeObjName, "Uptime");
System.out.println("連續工作時間:" + JMXTest.formatTimeSpan(timespan));
// ------------------------ JVM -------------------------
// 堆使用率
ObjectName heapObjName = new ObjectName("java.lang:type=Memory");
MemoryUsage heapMemoryUsage = MemoryUsage
.from((CompositeDataSupport) mbsc.getAttribute(heapObjName,
"HeapMemoryUsage"));
long maxMemory = heapMemoryUsage.getMax();// 堆最大
long commitMemory = heapMemoryUsage.getCommitted();// 堆當前分配
long usedMemory = heapMemoryUsage.getUsed();
System.out.println("heap:" + (double) usedMemory * 100
/ commitMemory + "%");// 堆使用率
MemoryUsage nonheapMemoryUsage = MemoryUsage
.from((CompositeDataSupport) mbsc.getAttribute(heapObjName,
"NonHeapMemoryUsage"));
long noncommitMemory = nonheapMemoryUsage.getCommitted();
long nonusedMemory = heapMemoryUsage.getUsed();
System.out.println("nonheap:" + (double) nonusedMemory * 100
/ noncommitMemory + "%");
ObjectName permObjName = new ObjectName(
"java.lang:type=MemoryPool,name=Perm Gen");
MemoryUsage permGenUsage = MemoryUsage
.from((CompositeDataSupport) mbsc.getAttribute(permObjName,
"Usage"));
long committed = permGenUsage.getCommitted();// 持久堆大小
long used = heapMemoryUsage.getUsed();//
System.out.println("perm gen:" + (double) used * 100 / committed
+ "%");// 持久堆使用率
// -------------------- Session ---------------
ObjectName managerObjName = new ObjectName(
"Catalina:type=Manager,*");
Set<ObjectName> s = mbsc.queryNames(managerObjName, null);
for (ObjectName obj : s) {
System.out.println("應用名:" + obj.getKeyProperty("path"));
ObjectName objname = new ObjectName(obj.getCanonicalName());
System.out.println("最大會話數:"
+ mbsc.getAttribute(objname, "maxActiveSessions"));
System.out.println("會話數:"
+ mbsc.getAttribute(objname, "activeSessions"));
System.out.println("活動會話數:"
+ mbsc.getAttribute(objname, "sessionCounter"));
}
// ----------------- Thread Pool ----------------
ObjectName threadpoolObjName = new ObjectName(
"Catalina:type=ThreadPool,*");
Set<ObjectName> s2 = mbsc.queryNames(threadpoolObjName, null);
for (ObjectName obj : s2) {
System.out.println("端口名:" + obj.getKeyProperty("name"));
ObjectName objname = new ObjectName(obj.getCanonicalName());
System.out.println("最大線程數:"
+ mbsc.getAttribute(objname, "maxThreads"));
System.out.println("當前線程數:"
+ mbsc.getAttribute(objname, "currentThreadCount"));
System.out.println("繁忙線程數:"
+ mbsc.getAttribute(objname, "currentThreadsBusy"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static String formatTimeSpan(long span) {
long minseconds = span % 1000;
span = span / 1000;
long seconds = span % 60;
span = span / 60;
long mins = span % 60;
span = span / 60;
long hours = span % 24;
span = span / 24;
long days = span;
return (new Formatter()).format("%1$d天 %2$02d:%3$02d:%4$02d.%5$03d",
days, hours, mins, seconds, minseconds).toString();
}
}
4 jvm性能調優監控工具jps/jstack/jmap/jhat/jstat
http://blog.csdn.net/wisgood/article/details/25343845
JDK本身提供了很多方便的JVM性能調優監控工具,除了集成式的VisualVM和jConsole外,還有jps、jstack、jmap、jhat、jstat等小巧的工具,本博客希望能起拋磚引玉之用,讓大家能開始對JVM性能調優的常用工具有所瞭解。
現實企業級Java開發中,有時候我們會碰到下面這些問題:
OutOfMemoryError,內存不足
內存泄露
線程死鎖
鎖爭用(Lock Contention)
Java進程消耗CPU過高
......
這些問題在日常開發中可能被很多人忽視(比如有的人遇到上面的問題只是重啓服務器或者調大內存,而不會深究問題根源),但能夠理解並解決這些問題是Java程序員進階的必備要求。本文將對一些常用的JVM性能調優監控工具進行介紹,希望能起拋磚引玉之用。本文參考了網上很多資料,難以一一列舉,在此對這些資料的作者表示感謝!關於JVM性能調優相關的資料,請參考文末。
jps(Java Virtual Machine Process Status Tool)
jps主要用來輸出JVM中運行的進程狀態信息。語法格式如下:
jps [options] [hostid]
如果不指定hostid就默認爲當前主機或服務器。
命令行參數選項說明如下:
-q 不輸出類名、Jar名和傳入main方法的參數
-m 輸出傳入main方法的參數
-l 輸出main類或Jar的全限名
-v 輸出傳入JVM的參數
比如下面:
root@ubuntu:/# jps -m -l
2458 org.artifactory.standalone.main.Main /usr/local/artifactory-2.2.5/etc/jetty.xml
29920 com.sun.tools.hat.Main -port 9998 /tmp/dump.dat
3149 org.apache.catalina.startup.Bootstrap start
30972 sun.tools.jps.Jps -m -l
8247 org.apache.catalina.startup.Bootstrap start
25687 com.sun.tools.hat.Main -port 9999 dump.dat
21711 mrf-center.jar
B、 jstack
jstack主要用來查看某個Java進程內的線程堆棧信息。語法格式如下:
jstack [option] pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip
命令行參數選項說明如下:
-l long listings,會打印出額外的鎖信息,在發生死鎖時可以用jstack -l pid來觀察鎖持有情況
-m mixed mode,不僅會輸出Java堆棧信息,還會輸出C/C++堆棧信息(比如Native方法)
jstack可以定位到線程堆棧,根據堆棧信息我們可以定位到具體代碼,所以它在JVM性能調優中使用得非常多。下面我們來一個實例找出某個Java進程中最耗費CPU的Java線程並定位堆棧信息,用到的命令有ps、top、printf、jstack、grep。
第一步先找出Java進程ID,我部署在服務器上的Java應用名稱爲mrf-center:
root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep
root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar
得到進程ID爲21711,第二步找出該進程內最耗費CPU的線程,可以使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我這裏用第三個,輸出如下:
JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解
TIME列就是各個Java線程耗費的CPU時間,CPU時間最長的是線程ID爲21742的線程,用
printf "%x\n" 21742
得到21742的十六進制值爲54ee,下面會用到。
OK,下一步終於輪到jstack上場了,它用來輸出進程21711的堆棧信息,然後根據線程ID的十六進制值grep,如下:
root@ubuntu:/# jstack 21711 | grep 54ee
"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
可以看到CPU消耗在PollIntervalRetrySchedulerThread這個類的Object.wait(),我找了下我的代碼,定位到下面的代碼:
// Idle wait
getLog().info("Thread [" + getName() + "] is idle waiting...");
schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;
long now = System.currentTimeMillis();
long waitTime = now + getIdleWaitTime();
long timeUntilContinue = waitTime - now;
synchronized(sigLock) {
try {
if(!halted.get()) {
sigLock.wait(timeUntilContinue);
}
}
catch (InterruptedException ignore) {
}
}
它是輪詢任務的空閒等待代碼,上面的sigLock.wait(timeUntilContinue)就對應了前面的Object.wait()。
jmap(Memory Map)和jhat(Java Heap Analysis Tool)
jmap用來查看堆內存使用狀況,一般結合jhat使用。
jmap語法格式如下:
jmap [option] pid
jmap [option] executable core
jmap [option] [server-id@]remote-hostname-or-ip
如果運行在64位JVM上,可能需要指定-J-d64命令選項參數。
jmap -permstat pid
打印進程的類加載器和類加載器加載的持久代對象信息,輸出:類加載器名稱、對象是否存活(不可靠)、對象地址、父類加載器、已加載的類大小等信息,
如下圖:
JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解
使用jmap -heap pid查看進程堆內存使用情況,包括使用的GC算法、堆配置參數和各代中堆內存使用情況。比如下面的例子:
root@ubuntu:/# jmap -heap 21711
Attaching to process ID 21711, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.10-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2067791872 (1972.0MB)
NewSize = 1310720 (1.25MB)
MaxNewSize = 17592186044415 MB
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 21757952 (20.75MB)
MaxPermSize = 85983232 (82.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 6422528 (6.125MB)
used = 5445552 (5.1932830810546875MB)
free = 976976 (0.9317169189453125MB)
84.78829520089286% used
From Space:
capacity = 131072 (0.125MB)
used = 98304 (0.09375MB)
free = 32768 (0.03125MB)
75.0% used
To Space:
capacity = 131072 (0.125MB)
used = 0 (0.0MB)
free = 131072 (0.125MB)
0.0% used
PS Old Generation
capacity = 35258368 (33.625MB)
used = 4119544 (3.9287033081054688MB)
free = 31138824 (29.69629669189453MB)
11.683876009235595% used
PS Perm Generation
capacity = 52428800 (50.0MB)
used = 26075168 (24.867218017578125MB)
free = 26353632 (25.132781982421875MB)
49.73443603515625% used
....
使用jmap -histo[:live]
pid查看堆內存中的對象數目、大小統計直方圖,如果帶上live則只統計活對象,如下:
root@ubuntu:/# jmap -histo:live 21711 | more
num #instances #bytes class name
----------------------------------------------
1: 38445 5597736 <constMethodKlass>
2: 38445 5237288 <methodKlass>
3: 3500 3749504 <constantPoolKlass>
4: 60858 3242600 <symbolKlass>
5: 3500 2715264 <instanceKlassKlass>
6: 2796 2131424 <constantPoolCacheKlass>
7: 5543 1317400 [I
8: 13714 1010768 [C
9: 4752 1003344 [B
10: 1225 639656 <methodDataKlass>
11: 14194 454208 java.lang.String
12: 3809 396136 java.lang.Class
13: 4979 311952 [S
14: 5598 287064 [[I
15: 3028 266464 java.lang.reflect.Method
16: 280 163520 <objArrayKlassKlass>
17: 4355 139360 java.util.HashMap$Entry
18: 1869 138568 [Ljava.util.HashMap$Entry;
19: 2443 97720 java.util.LinkedHashMap$Entry
20: 2072 82880 java.lang.ref.SoftReference
21: 1807 71528 [Ljava.lang.Object;
22: 2206 70592 java.lang.ref.WeakReference
23: 934 52304 java.util.LinkedHashMap
24: 871 48776 java.beans.MethodDescriptor
25: 1442 46144 java.util.concurrent.ConcurrentHashMap$HashEntry
26: 804 38592 java.util.HashMap
27: 948 37920 java.util.concurrent.ConcurrentHashMap$Segment
28: 1621 35696 [Ljava.lang.Class;
29: 1313 34880 [Ljava.lang.String;
30: 1396 33504 java.util.LinkedList$Entry
31: 462 33264 java.lang.reflect.Field
32: 1024 32768 java.util.Hashtable$Entry
33: 948 31440 [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;
class name是對象類型,說明如下:
B byte
C char
D double
F float
I int
J long
Z boolean
[ 數組,如[I表示int[]
[L+類名 其他對象
還有一個很常用的情況是:用jmap把進程內存使用情況dump到文件中,再用jhat分析查看。jmap進行dump命令格式如下:
jmap -dump:format=b,file=dumpFileName
我一樣地對上面進程ID爲21711進行Dump:
root@ubuntu:/# jmap -dump:format=b,file=/tmp/dump.dat 21711
Dumping heap to /tmp/dump.dat ...
Heap dump file created
dump出來的文件可以用MAT、VisualVM等工具查看,這裏用jhat查看:
root@ubuntu:/# jhat -port 9998 /tmp/dump.dat
Reading from /tmp/dump.dat...
Dump file created Tue Jan 28 17:46:14 CST 2014
Snapshot read, resolving...
Resolving 132207 objects...
Chasing references, expect 26 dots..........................
Eliminating duplicate references..........................
Snapshot resolved.
Started HTTP server on port 9998
Server is ready.
然後就可以在瀏覽器中輸入主機地址:9998查看了:
JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解
上面紅線框出來的部分大家可以自己去摸索下,最後一項支持OQL(對象查詢語言)。
D、jstat(JVM統計監測工具)
語法格式如下:
jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]
vmid是虛擬機ID,在Linux/Unix系統上一般就是進程ID。interval是採樣時間間隔。count是採樣數目。比如下面輸出的是GC信息,採樣時間間隔爲250ms,採樣數爲4:
root@ubuntu:/# jstat -gc 21711 250 4
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
192.0 192.0 64.0 0.0 6144.0 1854.9 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
192.0 192.0 64.0 0.0 6144.0 1972.2 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
192.0 192.0 64.0 0.0 6144.0 2109.7 32000.0 4111.6 55296.0 25472.7 702 0.431 3 0.218 0.649
要明白上面各列的意義,先看JVM堆內存佈局:
JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解
可以看出:
堆內存 = 年輕代 + 年老代 + 永久代
年輕代 = Eden區 + 兩個Survivor區(From和To)
現在來解釋各列含義:
S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used)
EC、EU:Eden區容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年輕代GC次數和GC耗時
FGC、FGCT:Full GC次數和Full GC耗時
GCT:GC總耗時
其他JVM性能調優參考資料:
《Java虛擬機規範》
《Java Performance》
《Trouble Shooting Guide for JavaSE 6 with HotSpot VM》: http://www.oracle.com/technetwork/java/javase/tsg-vm-149989.pdf
《Effective Java》
VisualVM: http://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/
jConsole: http://docs.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html
Monitoring and Managing JavaSE 6 Applications: http://www.oracle.com/technetwork/articles/javase/monitoring-141801.html
jstack(查看線程)、jmap(查看內存)和jstat(性能分析)命令
http://guafei.iteye.com/blog/1815222
1.Jstack
1.1 jstack能得到運行java程序的java stack和native stack的信息。可以輕鬆得知當前線程的運行情況。如下圖所示
注:這個和thread dump是同樣的結果。但是thread dump是用kill -3 pid命令,還是服務器上面少用kill爲妙
1.2 命名行格式
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
最常用的還是jstack pid
1.3 在thread dump中,要留意下面幾種狀態
死鎖,Deadlock(重點關注)
等待資源,Waiting on condition(重點關注)
• 等待獲取監視器,Waiting on monitor entry(重點關注)
阻塞,Blocked(重點關注)
• 執行中,Runnable
• 暫停,Suspended
• 對象等待中,Object.wait() 或 TIMED_WAITING
• 停止,Parked
下面有詳細的例子講這種分析,大家參考原著
http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html
1.4 在thread dump中,有幾種線程的定義如下
線程名稱 所屬 解釋說明
Attach Listener JVM Attach Listener 線程是負責接收到外部的命令,而對該命令進行執行的並且吧結果返回給發送者。通常我們會用一些命令去要求jvm給我們一些反饋信息,如:java -version、jmap、jstack等等。 如果該線程在jvm啓動的時候沒有初始化,那麼,則會在用戶第一次執行jvm命令時,得到啓動。
Signal Dispatcher JVM 前面我們提到第一個Attach Listener線程的職責是接收外部jvm命令,當命令接收成功後,會交給signal dispather 線程去進行分發到各個不同的模塊處理命令,並且返回處理結果。 signal dispather線程也是在第一次接收外部jvm命令時,進行初始化工作。
CompilerThread0 JVM 用來調用JITing,實時編譯裝卸class 。 通常,jvm會啓動多個線程來處理這部分工作,線程名稱後面的數字也會累加,例如:CompilerThread1
Concurrent Mark-Sweep GC Thread JVM 併發標記清除垃圾回收器(就是通常所說的CMS GC)線程, 該線程主要針對於老年代垃圾回收。ps:啓用該垃圾回收器,需要在jvm啓動參數中加上: -XX:+UseConcMarkSweepGC
DestroyJavaVM JVM 執行main()的線程在main執行完後調用JNI中的 jni_DestroyJavaVM() 方法喚起DestroyJavaVM 線程。 JVM在 Jboss 服務器啓動之後,就會喚起DestroyJavaVM線程,處於等待狀態,等待其它線程(java線程和native線程)退出時通知它卸載JVM。線程退出時,都會判斷自己當前是否是整個JVM中最後一個非deamon線程,如果是,則通知DestroyJavaVM 線程卸載JVM。
ps:
擴展一下:
1.如果線程退出時判斷自己不爲最後一個非deamon線程,那麼調用thread->exit(false) ,並在其中拋出thread_end事件,jvm不退出。
2.如果線程退出時判斷自己爲最後一個非deamon線程,那麼調用before_exit() 方法,拋出兩個事件: 事件1:thread_end 線程結束事件、事件2:VM的death事件。
然後調用thread->exit(true) 方法,接下來把線程從active list卸下,刪除線程等等一系列工作執行完成後,則通知正在等待的DestroyJavaVM 線程執行卸載JVM操作。
ContainerBackgroundProcessor 線程 JBOSS 它是一個守護線程, 在jboss服務器在啓動的時候就初始化了,主要工作是定期去檢查有沒有Session過期.過期則清除.
參考:http://liudeh-009.iteye.com/blog/1584876
Dispatcher-Thread-3 線程 Log4j Log4j具有異步打印日誌的功能,需要異步打印日誌的Appender都需要註冊到 AsyncAppender對象裏面去,由AsyncAppender進行監聽,決定何時觸發日誌打印操作。 AsyncAppender如果監聽到它管轄範圍內的Appender有打印日誌的操作,則給這個Appender生成一個相應的event,並將該event保存在一個buffuer區域內。 Dispatcher-Thread-3線程負責判斷這個event緩存區是否已經滿了,如果已經滿了,則將緩存區內的所有event分發到Appender容器裏面去,那些註冊上來的Appender收到自己的event後,則開始處理自己的日誌打印工作。 Dispatcher-Thread-3線程是一個守護線程。
Finalizer線程 JVM 這個線程也是在main線程之後創建的,其優先級爲10,主要用於在垃圾收集前,調用對象的finalize()方法;關於Finalizer線程的幾點:
1) 只有當開始一輪垃圾收集時,纔會開始調用finalize()方法;因此並不是所有對象的finalize()方法都會被執行;
2) 該線程也是daemon線程,因此如果虛擬機中沒有其他非daemon線程,不管該線程有沒有執行完finalize()方法,JVM也會退出;
3) JVM在垃圾收集時會將失去引用的對象包裝成Finalizer對象(Reference的實現),並放入ReferenceQueue,由Finalizer線程來處理;最後將該Finalizer對象的引用置爲null,由垃圾收集器來回收;
4) JVM爲什麼要單獨用一個線程來執行finalize()方法呢?如果JVM的垃圾收集線程自己來做,很有可能由於在finalize()方法中誤操作導致GC線程停止或不可控,這對GC線程來說是一種災難;
Gang worker#0 JVM JVM 用於做新生代垃圾回收(monir gc)的一個線程。#號後面是線程編號,例如:Gang worker#1
GC Daemon JVM GC Daemon 線程是JVM爲RMI提供遠程分佈式GC使用的,GC Daemon線程裏面會主動調用System.gc()方法,對服務器進行Full GC。 其初衷是當 RMI 服務器返回一個對象到其客戶機(遠程方法的調用方)時,其跟蹤遠程對象在客戶機中的使用。當再沒有更多的對客戶機上遠程對象的引用時,或者如果引用的“租借”過期並且沒有更新,服務器將垃圾回收遠程對象。
不過,我們現在jvm啓動參數都加上了-XX:+DisableExplicitGC配置,所以,這個線程只有打醬油的份了。
IdleRemover JBOSS Jboss連接池有一個最小值, 該線程每過一段時間都會被Jboss喚起,用於檢查和銷燬連接池中空閒和無效的連接,直到剩餘的連接數小於等於它的最小值。
Java2D Disposer JVM 這個線程主要服務於awt的各個組件。 說起該線程的主要工作職責前,需要先介紹一下Disposer類是幹嘛的。 Disposer提供一個addRecord方法。 如果你想在一個對象被銷燬前再做一些善後工作,那麼,你可以調用Disposer#addRecord方法,將這個對象和一個自定義的DisposerRecord接口實現類,一起傳入進去,進行註冊。
Disposer類會喚起“Java2D Disposer”線程,該線程會掃描已註冊的這些對象是否要被回收了,如果是,則調用該對象對應的DisposerRecord實現類裏面的dispose方法。
Disposer實際上不限於在awt應用場景,只是awt裏面的很多組件需要訪問很多操作系統資源,所以,這些組件在被回收時,需要先釋放這些資源。
InsttoolCacheScheduler_
QuartzSchedulerThread Quartz InsttoolCacheScheduler_QuartzSchedulerThread是Quartz的主線程,它主要負責實時的獲取下一個時間點要觸發的觸發器,然後執行觸發器相關聯的作業 。
原理大致如下:
Spring和Quartz結合使用的場景下,Spring IOC容器初始化時會創建並初始化Quartz線程池(TreadPool),並啓動它。剛啓動時線程池中每個線程都處於等待狀態,等待外界給他分配Runnable(持有作業對象的線程)。
繼而接着初始化並啓動Quartz的主線程(InsttoolCacheScheduler_QuartzSchedulerThread),該線程自啓動後就會處於等待狀態。等待外界給出工作信號之後,該主線程的run方法才實質上開始工作。run中會獲取JobStore中下一次要觸發的作業,拿到之後會一直等待到該作業的真正觸發時間,然後將該作業包裝成一個JobRunShell對象(該對象實現了Runnable接口,其實看是上面TreadPool中等待外界分配給他的Runnable),然後將剛創建的JobRunShell交給線程池,由線程池負責執行作業。
線程池收到Runnable後,從線程池一個線程啓動Runnable,反射調用JobRunShell中的run方法,run方法執行完成之後, TreadPool將該線程回收至空閒線程中。
InsttoolCacheScheduler_Worker-2 Quartz InsttoolCacheScheduler_Worker-2線程就是ThreadPool線程的一個簡單實現,它主要負責分配線程資源去執行
InsttoolCacheScheduler_QuartzSchedulerThread線程交給它的調度任務(也就是JobRunShell)。
JBossLifeThread Jboss Jboss主線程啓動成功,應用程序部署完畢之後將JBossLifeThread線程實例化並且start,JBossLifeThread線程啓動成功之後就處於等待狀態,以保持Jboss Java進程處於存活中。 所得比較通俗一點,就是Jboss啓動流程執行完畢之後,爲什麼沒有結束? 就是因爲有這個線程hold主了它。 牛b吧~~
JBoss System Threads(1)-1 Jboss 該線程是一個socket服務,默認端口號爲: 1099。 主要用於接收外部naming service(Jboss JNDI)請求。
JCA PoolFiller Jboss 該線程主要爲JBoss內部提供連接池的託管。 簡單介紹一下工作原理 :
Jboss內部凡是有遠程連接需求的類,都需要實現ManagedConnectionFactory接口,例如需要做JDBC連接的
XAManagedConnectionFactory對象,就實現了該接口。然後將XAManagedConnectionFactory對象,還有其它信息一起包裝到InternalManagedConnectionPool對象裏面,接着將InternalManagedConnectionPool交給PoolFiller對象裏面的列隊進行管理。 JCA PoolFiller線程會定期判斷列隊內是否有需要創建和管理的InternalManagedConnectionPool對象,如果有的話,則調用該對象的fillToMin方法, 觸發它去創建相應的遠程連接,並且將這個連接維護到它相應的連接池裏面去。
JDWP Event Helper Thread JVM
JDWP是通訊交互協議,它定義了調試器和被調試程序之間傳遞信息的格式。它詳細完整地定義了請求命令、迴應數據和錯誤代碼,保證了前端和後端的JVMTI和JDI的通信通暢。 該線程主要負責將JDI事件映射成JVMTI信號,以達到調試過程中操作JVM的目的。
JDWP Transport Listener: dt_socket JVM 該線程是一個Java Debugger的監聽器線程,負責受理客戶端的debug請求。 通常我們習慣將它的監聽端口設置爲8787。
Low Memory Detector JVM 這個線程是負責對可使用內存進行檢測,如果發現可用內存低,分配新的內存空間。
process reaper JVM 該線程負責去執行一個 OS 命令行的操作。
Reference Handler JVM JVM在創建main線程後就創建Reference Handler線程,其優先級最高,爲10,它主要用於處理引用對象本身(軟引用、弱引用、虛引用)的垃圾回收問題 。
Surrogate Locker Thread (CMS) JVM 這個線程主要用於配合CMS垃圾回收器使用,它是一個守護線程,其主要負責處理GC過程中,Java層的Reference(指軟引用、弱引用等等)與jvm 內部層面的對象狀態同步。 這裏對它們的實現稍微做一下介紹:這裏拿 WeakHashMap做例子,將一些關鍵點先列出來(我們後面會將這些關鍵點全部串起來):
- 我們知道HashMap用Entry[]數組來存儲數據的,WeakHashMap也不例外, 內部有一個Entry[]數組。
- WeakHashMap的Entry比較特殊,它的繼承體系結構爲Entry->WeakReference->Reference 。
- Reference 裏面有一個全局鎖對象:Lock,它也被稱爲pending_lock. 注意:它是靜態對象。
- Reference 裏面有一個靜態變量:pending。
- Reference 裏面有一個靜態內部類:ReferenceHandler的線程,它在static塊裏面被初始化並且啓動,啓動完成後處於wait狀態,它在一個Lock同步鎖模塊中等待。
- 另外,WeakHashMap裏面還實例化了一個ReferenceQueue列隊,這個列隊的作用,後面會提到。
- 上面關鍵點就介紹完畢了,下面我們把他們串起來。
假設,WeakHashMap對象裏面已經保存了很多對象的引用。 JVM 在進行CMS GC的時候,會創建一個ConcurrentMarkSweepThread(簡稱CMST)線程去進行GC,ConcurrentMarkSweepThread線程被創建的同時會創建一個SurrogateLockerThread(簡稱SLT)線程並且啓動它,SLT啓動之後,處於等待階段。CMST開始GC時,會發一個消息給SLT讓它去獲取Java層Reference對象的全局鎖:Lock。 直到CMS GC完畢之後,JVM 會將WeakHashMap中所有被回收的對象所屬的WeakReference容器對象放入到Reference 的pending屬性當中(每次GC完畢之後,pending屬性基本上都不會爲null了),然後通知SLT釋放並且notify全局鎖:Lock。此時激活了ReferenceHandler線程的run方法,使其脫離wait狀態,開始工作了。ReferenceHandler這個線程會將pending中的所有WeakReference對象都移動到它們各自的列隊當中,比如當前這個WeakReference屬於某個WeakHashMap對象,那麼它就會被放入相應的ReferenceQueue列隊裏面(該列隊是鏈表結構)。 當我們下次從WeakHashMap對象裏面get、put數據或者調用size方法的時候,WeakHashMap就會將ReferenceQueue列隊中的WeakReference依依poll出來去和Entry[]數據做比較,如果發現相同的,則說明這個Entry所保存的對象已經被GC掉了,那麼將Entry[]內的Entry對象剔除掉。
taskObjectTimerFactory JVM 顧名思義,該線程就是用來執行任務的。 當我們把一個認爲交給Timer對象,並且告訴它執行時間,週期時間後,Timer就會將該任務放入任務列隊,並且通知taskObjectTimerFactory線程去處理任務,taskObjectTimerFactory線程會將狀態爲取消的任務從任務列隊中移除,如果任務是非重複執行類型的,則在執行完該任務後,將它從任務列隊中移除,如果該任務是需要重複執行的,則計算出它下一次執行的時間點。
VM Periodic Task Thread JVM 該線程是JVM週期性任務調度的線程,它由WatcherThread創建,是一個單例對象。 該線程在JVM內使用得比較頻繁,比如:定期的內存監控、JVM運行狀況監控,還有我們經常需要去執行一些jstat 這類命令查看gc的情況,如下:
jstat -gcutil 23483 250 7 這個命令告訴jvm在控制檯打印PID爲:23483的gc情況,間隔250毫秒打印一次,一共打印7次。
VM Thread JVM 這個線程就比較牛b了,是jvm裏面的線程母體,根據hotspot源碼(vmThread.hpp)裏面的註釋,它是一個單例的對象(最原始的線程)會產生或觸發所有其他的線程,這個單個的VM線程是會被其他線程所使用來做一些VM操作(如,清掃垃圾等)。
在 VMThread 的結構體裏有一個VMOperationQueue列隊,所有的VM線程操作(vm_operation)都會被保存到這個列隊當中,VMThread 本身就是一個線程,它的線程負責執行一個自輪詢的loop函數(具體可以參考:VMThread.cpp裏面的void VMThread::loop()) ,該loop函數從VMOperationQueue列隊中按照優先級取出當前需要執行的操作對象(VM_Operation),並且調用VM_Operation->evaluate函數去執行該操作類型本身的業務邏輯。
ps:VM操作類型被定義在vm_operations.hpp文件內,列舉幾個:ThreadStop、ThreadDump、PrintThreads、GenCollectFull、GenCollectFullConcurrent、CMS_Initial_Mark、CMS_Final_Remark….. 有興趣的同學,可以自己去查看源文件。
(搬運自 http://blog.csdn.net/a43350860/article/details/8134234 感謝原著作者)
2.Jmap
2.1 得到運行java程序的內存分配的詳細情況。例如實例個數,大小等
2.2 命名行格式
jmap [ option ] pid
jmap [ option ] executable core
jmap [ option ] [server-id@]remote-hostname-or-IP
-dump:[live,]format=b,file=<filename> 使用hprof二進制形式,輸出jvm的heap內容到文件=. live子選項是可選的,假如指定live選項,那麼只輸出活的對象到文件.
-finalizerinfo 打印正等候回收的對象的信息.
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情況.
-histo[:live] 打印每個class的實例數目,內存佔用,類全名信息. VM的內部類名字開頭會加上前綴”*”. 如果live子參數加上後,只統計活的對象數量.
-permstat 打印classload和jvm heap長久層的信息. 包含每個classloader的名字,活潑性,地址,父classloader和加載的class數量. 另外,內部String的數量和佔用內存數也會打印出來.
-F 強迫.在pid沒有相應的時候使用-dump或者-histo參數. 在這個模式下,live子參數無效.
-h | -help 打印輔助信息
-J 傳遞參數給jmap啓動的jvm.
2.3 使用例子
jmap -histo pid(查看實例)
jmap -dump:format=b,file=heap.bin pid(導出內存,據說對性能有影響,小心使用)
(format=b是通過二進制的意思,但是能不能導出文本文件我沒找到,知道的告訴我)
把內存結構全部dump到二進制文件中,通過IBM的HeapAnalyzer和eclipse的MemoryAnalyzer都可以分析內存結構。
這個是我用HeapAnalyzer查看出的我們daily的內存結構,已經列出了可能存在的問題。(這個工具我不熟悉,只供大家參考)
下面是我用eclipse 的MemoryAnalyzer查看內存結構圖
上面的是eclipse分析內存泄漏分析出的。這個功能點非常多。可以慢慢學習
3.Jstat
3.1 這是一個比較實用的一個命令,可以觀察到classloader,compiler,gc相關信息。可以時時監控資源和性能
3.2 命令格式
-class:統計class loader行爲信息
-compile:統計編譯行爲信息
-gc:統計jdk gc時heap信息
-gccapacity:統計不同的generations(不知道怎麼翻譯好,包括新生區,老年區,permanent區)相應的heap容量情況
-gccause:統計gc的情況,(同-gcutil)和引起gc的事件
-gcnew:統計gc時,新生代的情況
-gcnewcapacity:統計gc時,新生代heap容量
-gcold:統計gc時,老年區的情況
-gcoldcapacity:統計gc時,老年區heap容量
-gcpermcapacity:統計gc時,permanent區heap容量
-gcutil:統計gc時,heap情況
3.3 輸出參數內容
S0 — Heap上的 Survivor space 0 區已使用空間的百分比
S0C:S0當前容量的大小
S0U:S0已經使用的大小
S1 — Heap上的 Survivor space 1 區已使用空間的百分比
S1C:S1當前容量的大小
S1U:S1已經使用的大小
E — Heap上的 Eden space 區已使用空間的百分比
EC:Eden space當前容量的大小
EU:Eden space已經使用的大小 1.Jstack
例子1
例子2(連續5次)
例子3(PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,PGC是當前新生成的perm內存佔用量,PC是但前perm內存佔用量)
這個工具的參數非常多,據說基本能覆蓋jprofile等收費工具的所有功能了。多用用對於系統調優還是很有幫助的
注1:我們在daily用這樣命令時,都要用-F參數的。因爲我們的用戶都不是啓動命令的用戶
注2:daily的這些命令好像都沒有配置到環境變量裏面,這個是我在自己應用機器裏面看到的。需要去jdk目錄底下執行。Sudo當然是必須的了
5 gvm gc 相關
http://www.cnblogs.com/Mandylover/p/5208055.html
JVM的GC機制及JVM的調優方法
內存管理和垃圾回收是JVM非常關鍵的點,對Java性能的剖析而言,瞭解內存管理和垃圾回收的基本策略非常重要。
1.在程序運行過程當中,會創建大量的對象,這些對象,大部分是短週期的對象,小部分是長週期的對象,對於短週期的對象,需要頻繁地進行垃圾回收以保證無用對 象儘早被釋放掉,對於長週期對象,則不需要頻率垃圾回收以確保無謂地垃圾掃描檢測。爲解決這種矛盾,Sun JVM的內存管理採用分代的策略。
1)年輕代(Young Gen):年輕代主要存放新創建的對象,內存大小相對會比較小,垃圾回收會比較頻繁。年輕代分成1個Eden Space和2個Suvivor Space(命名爲A和B)
當對象在堆創建時,將進入年輕代的Eden Space。
垃圾回收器進行垃圾回收時,掃描Eden Space和A Suvivor Space,如果對象仍然存活,則複製到B Suvivor Space,如果B Suvivor Space已經滿,則複製 Old Gen
掃描A Suvivor Space時,如果對象已經經過了幾次的掃描仍然存活,JVM認爲其爲一個Old對象,則將其移到Old Gen。
掃描完畢後,JVM將Eden Space和A Suvivor Space清空,然後交換A和B的角色(即下次垃圾回收時會掃描Eden Space和BSuvivor Space。
我們可以看到:Young Gen垃圾回收時,採用將存活對象複製到到空的Suvivor Space的方式來確保不存在內存碎片,採用空間換時間的方式來加速內存垃圾回收。
2)年老代(Tenured Gen):年老代主要存放JVM認爲比較old的對象(經過幾次的Young Gen的垃圾回收後仍然存在),內存大小相對會比較大,垃圾回收也相對沒有那麼頻繁(譬如可能幾個小時一次)。年老代主要採用壓縮的方式來避免內存碎片 (將存活對象移動到內存片的一邊),當然,有些垃圾回收器(譬如CMS垃圾回收器)出於效率的原因,可能會不進行壓縮。
3)持久代(Perm Gen):持久代主要存放類定義、字節碼和常量等很少會變更的信息
2.總結
1、對象優先在Eden分配,這裏大部分對象具有朝生夕滅的特徵,Minor GC主要清理該處
2、大對象(佔內存大)、老對象(使用頻繁)
3、Survivor無法容納的對象,將進入老年代,Full GC的主要清理該處
3.JVM有 2個GC線程
第一個線程負責回收Heap的Young區
第二個線程在Heap不足時,遍歷Heap,將Young 區升級爲Older區
JVM(採用分代回收的策略),用較高的頻率對年輕的對象(young generation)進行YGC,而對老對象( tenured generation)較少( tenured generation 滿了後才進行)進行Full GC。這樣就不需要每次GC都將內存中所有對象都檢查一遍。
GC不會在主程序運行期對PermGen Space進行清理,所以如果你的應用中有很多CLASS(特別是動態生成類,當然permgen space存放的內容不僅限於類)的話,就很可能出現PermGen Space錯誤。
4.jvm的調優
減少FullGC的次數,以爲FullGC會暫停程序比較長的時間,如果FullGC的次數比較多。程序就會經常性的假死。
Heap size 設置
JVM 堆的設置是指java程序運行過程中JVM可以調配使用的內存空間的設置.JVM在啓動的時候會自動設置Heap size的值,其初始空間(即-Xms)是物理內存的1/64,最大空間(-Xmx)是物理內存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。
如果Heap size 設置偏小,GC佔用了更多的時間,而應用分配到的執行時間較少。但Heap size 最大不要超過可用物理內存的80%。
具有2G或者更多的物理內存
http://www.xuebuyuan.com/1201516.html
JVM gc參數設置與分析
http://blog.csdn.net/yohoph/article/details/42041729
原文:
http://hi.baidu.com/i1see1you/item/295c1dc81f91ab55bdef69e5
gc日誌分析工具: http://qa.blog.163.com/blog/static/19014700220128199421589/
Java GC 日誌圖解: http://www.chinasb.org/archives/2012/09/4921.shtml
概述
java的最大好處是自動垃圾回收,這樣就無需我們手動的釋放對象空間了,但是也產生了相應的負效果,gc是需要時間和資源的,不好的gc會嚴重影響系統的系能,因此良好的gc是JVM的高性能的保證。JVM堆分爲新生代,舊生代和年老代,新生代可用的gc方式有:串行gc(Serial Copying),並行回收gc(Parellel Scavenge),並行gc(ParNew),舊生代和年老代可用的gc方式有串行gc(Serial MSC),並行gc(Parallel MSC),併發gc(CMS)。
回收方式的選擇
jvm有client和server兩種模式,這兩種模式的gc默認方式是不同的:
client模式下,新生代選擇的是串行gc,舊生代選擇的是串行gc
server模式下,新生代選擇的是並行回收gc,舊生代選擇的是並行gc
一般來說我們系統應用選擇有兩種方式:吞吐量優先和暫停時間優先,對於吞吐量優先的採用server默認的並行gc方式,對於暫停時間優先的選用併發gc(CMS)方式。
CMS gc
CMS,全稱Concurrent Low Pause Collector,是jdk1.4後期版本開始引入的新gc算法,在jdk5和jdk6中得到了進一步改進,它的主要適合場景是對響應時間的重要性需求大於對吞吐量的要求,能夠承受垃圾回收線程和應用線程共享處理器資源,並且應用中存在比較多的長生命週期的對象的應用。CMS是用於對tenured generation的回收,也就是年老代的回收,目標是儘量減少應用的暫停時間,減少full gc發生的機率,利用和應用程序線程併發的垃圾回收線程來標記清除年老代。在我們的應用中,因爲有緩存的存在,並且對於響應時間也有比較高的要求,因此希望能嘗試使用CMS來替代默認的server型JVM使用的並行收集器,以便獲得更短的垃圾回收的暫停時間,提高程序的響應性。
CMS並非沒有暫停,而是用兩次短暫停來替代串行標記整理算法的長暫停,它的收集週期是這樣:
初始標記(CMS-initial-mark) -> 併發標記(CMS-concurrent-mark) -> 重新標記(CMS-remark) -> 併發清除(CMS-concurrent-sweep) ->併發重設狀態等待下次CMS的觸發(CMS-concurrent-reset)。
其中的1,3兩個步驟需要暫停所有的應用程序線程的。第一次暫停從root對象開始標記存活的對象,這個階段稱爲初始標記;第二次暫停是在併發標記之後,暫停所有應用程序線程,重新標記併發標記階段遺漏的對象(在併發標記階段結束後對象狀態的更新導致)。第一次暫停會比較短,第二次暫停通常會比較長,並且 remark這個階段可以並行標記。
而併發標記、併發清除、併發重設階段的所謂併發,是指一個或者多個垃圾回收線程和應用程序線程併發地運行,垃圾回收線程不會暫停應用程序的執行,如果你有多於一個處理器,那麼併發收集線程將與應用線程在不同的處理器上運行,顯然,這樣的開銷就是會降低應用的吞吐量。Remark階段的並行,是指暫停了所有應用程序後,啓動一定數目的垃圾回收進程進行並行標記,此時的應用線程是暫停的。
full gc
full gc是對新生代,舊生代,以及持久代的統一回收,由於是對整個空間的回收,因此比較慢,系統中應當儘量減少full gc的次數。
如下幾種情況下會發生full gc:
- 舊生代空間不足
- 持久代空間不足
- CMS GC時出現了promotion failed和concurrent mode failure
- 統計得到新生代minor gc時晉升到舊生代的平均大小小於舊生代剩餘空間
- 直接調用System.gc,可以DisableExplicitGC來禁止
存在rmi調用時,默認會每分鐘執行一次System.gc,可以通過-Dsun.rmi.dgc.server.gcInterval=3600000來設置大點的間隔。
Gc日誌參數
通過在tomcat啓動腳本中添加相關參數生成gc日誌
-verbose.gc開關可顯示GC的操作內容。打開它,可以顯示最忙和最空閒收集行爲發生的時間、收集前後的內存大小、收集需要的時間等。
打開-xx:+printGCdetails開關,可以詳細瞭解GC中的變化。
打開-XX:+PrintGCTimeStamps開關,可以瞭解這些垃圾收集發生的時間,自JVM啓動以後以秒計量。
最後,通過-xx:+PrintHeapAtGC開關了解堆的更詳細的信息。
爲了瞭解新域的情況,可以通過-XX:+PrintTenuringDistribution開關了解獲得使用期的對象權。
-Xloggc:$CATALINA_BASE/logs/gc.log gc日誌產生的路徑
-XX:+PrintGCApplicationStoppedTime 輸出GC造成應用暫停的時間
-XX:+PrintGCDateStamps GC發生的時間信息
Opentsdb打開Gc參數
# tsdb.local
# http://opentsdb.net/docs/build/html/user_guide/cli/index.html
GCARGS="-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps\
-XX:+PrintTenuringDistribution -Xloggc:/tmp/tsd-gc-`date
+%s`.log"
if
test -t 0; then
# if stdin is a tty, don't turn on GC logging.
GCARGS=
fi
# The Sun JDK caches all name resolution results forever, which is stupid.
# This forces you to restart your application if any of the backends change
# IP. Instead tell it to cache names for only 10 minutes at most.
FIX_DNS='-Dsun.net.inetaddr.ttl=600'
JVMARGS="$JVMARGS $GCARGS $FIX_DNS"
常用JVM參數
分析gc日誌後,經常需要調整jvm內存相關參數,常用參數如下
-Xms:初始堆大小,默認爲物理內存的1/64(<1GB);默認(MinHeapFreeRatio參數可以調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制
-Xmx:最大堆大小,默認(MaxHeapFreeRatio參數可以調整)空餘堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制
-Xmn:新生代的內存空間大小,注意:此處的大小是(eden+ 2 survivor space)。與jmap -heap中顯示的New gen是不同的。整個堆大小=新生代大小 + 老生代大小 +
永久代大小。在保證堆大小不變的情況下,增大新生代後,將會減小老生代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。-XX:SurvivorRatio:新生代中Eden區域與Survivor區域的容量比值,默認值爲8。兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10。
-Xss:每個線程的堆棧大小。JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K。應根據應用的線程所需內存大小進行適當調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。一般小的應用,
如果棧不是很深,
應該是128k夠用的,大的應用建議使用256k。這個選項對性能影響比較大,需要嚴格的測試。和threadstacksize選項解釋很類似,官方文檔似乎沒有解釋,在論壇中有這樣一句話:"-Xss
is translated in a VM flag named ThreadStackSize”一般設置這個值就可以了。-XX:PermSize:設置永久代(perm gen)初始值。默認值爲物理內存的1/64。
-XX:MaxPermSize:設置持久代最大值。物理內存的1/4。
示例
下面對如下的參數進行分析:
JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4
-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log
-Djava.awt.headless=true
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"
-Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m
Xms,即爲jvm啓動時得JVM初始堆大小,Xmx爲jvm的最大堆大小,xmn爲新生代的大小,permsize爲永久代的初始大小,MaxPermSize爲永久代的最大空間。
-XX:SurvivorRatio=4
SurvivorRatio爲新生代空間中的Eden區和救助空間Survivor區的大小比值,默認是32,也就是說Eden區是
Survivor區的32倍大小,要注意Survivo是有兩個區的,因此Surivivor其實佔整個young
genertation的1/34。調小這個參數將增大survivor區,讓對象儘量在survitor區呆長一點,減少進入年老代的對象。去掉救助空間的想法是讓大部分不能馬上回收的數據儘快進入年老代,加快年老代的回收頻率,減少年老代暴漲的可能性,這個是通過將-XX:SurvivorRatio
設置成比較大的值(比如65536)來做到。-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log
將虛擬機每次垃圾回收的信息寫到日誌文件中,文件名由file指定,文件格式是平文件,內容和-verbose:gc輸出內容相同。
-Djava.awt.headless=true
Headless模式是系統的一種配置模式。在該模式下,系統缺少了顯示設備、鍵盤或鼠標。
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
設置gc日誌的格式
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
指定rmi調用時gc的時間間隔
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15
採用併發gc方式,經過15次minor gc 後進入年老代
Xms 是指設定程序啓動時佔用內存大小。一般來講,大點,程序會啓動的快一點,但是也可能會導致機器暫時間變慢。
Xmx 是指設定程序運行期間最大可佔用的內存大小。如果程序運行需要佔用更多的內存,超出了這個設置值,就會拋出OutOfMemory異常。
Xss 是指設定每個線程的堆棧大小。這個就要依據你的程序,看一個線程大約需要佔用多少內存,可能會有多少線程同時運行等。
以上三個參數的設置都是默認以Byte爲單位的,也可以在數字後面添加[k/K]或者[m/M]來表示KB或者MB。而且,超過機器本身的內存大小也是不可以的,否則就等着機器變慢而不是程序變慢了。
-Xmsn
Specify the initial size, in bytes, of the memory allocation pool.
This value must be a multiple of 1024 greater than 1MB. Append the
letter k or K to indicate kilobytes, or m or M to indicate megabytes.
The default value is chosen at runtime based on system configuration.
For more information, see HotSpot Ergonomics
Examples:
-Xms6291456
-Xms6144k
-Xms6m
-Xmxn
Specify the maximum size, in bytes, of the memory allocation pool. This value must a multiple of 1024 greater than 2MB. Append the letter k or K to indicate kilobytes, or m or M to indicate megabytes. The default value is chosen at runtime based on system configuration. For more information, see HotSpot Ergonomics
Examples:
-Xmx83886080
-Xmx81920k
-Xmx80m
-Xssn
Set thread stack size.
一些常見問題
爲了避免Perm區滿引起的full gc,建議開啓CMS回收Perm區選項:
+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled
默認CMS是在tenured generation沾滿68%的時候開始進行CMS收集,如果你的年老代增長不是那麼快,並且希望降低CMS次數的話,可以適當調高此值:
-XX:CMSInitiatingOccupancyFraction=80
遇到兩種fail引起full gc:Prommotion failed和Concurrent mode failed時:
Prommotion failed的日誌輸出大概是這樣:
[ParNew (promotion failed): 320138K->320138K(353920K), 0.2365970 secs]42576.951: [CMS: 1139969K->1120688K( 166784K), 9.2214860 secs] 1458785K->1120688K(2520704K), 9.4584090 secs]
這個問題的產生是由於救助空間不夠,從而向年老代轉移對象,年老代沒有足夠的空間來容納這些對象,導致一次full gc的產生。解決這個問題的辦法有兩種完全相反的傾向:增大救助空間、增大年老代或者去掉救助空間。
Concurrent mode failed的日誌大概是這樣的:
(concurrent mode failure): 1228795K->1228598K(1228800K), 7.6748280 secs] 1911483K->1681165K(1911488K), [CMS Perm : 225407K->225394K(262144K)], 7.6751800 secs]
問題的產生原因是由於CMS回收年老代的速度太慢,導致年老代在CMS完成前就被沾滿,引起full gc,避免這個現象的產生就是調小-XX:CMSInitiatingOccupancyFraction參數的值,讓CMS更早更頻繁的觸發,降低年老代被沾滿的可能。
Gc日誌分析工具
GCHisto
http://java.net/projects/gchisto
直接點擊gchisto.jar就可以運行,點add載入gc.log
統計了總共gc次數,youngGC次數,FullGC次數,次數的百分比,GC消耗的時間,百分比,平均消耗時間,消耗時間最小最大值等
統計的圖形化表示
YoungGC,FullGC不同消耗時間上次數的分佈圖,勾選可以顯示youngGC或fullGC單獨的分佈情況
整個時間過程詳細的gc情況,可以對整個過程進行剖析
GCLogViewer
http://code.google.com/p/gclogviewer/
點擊run.bat運行
整個過程gc情況的趨勢圖,還顯示了gc類型,吞吐量,平均gc頻率,內存變化趨勢等
Tools裏還能比較不同gc日誌:
HPjmeter
獲取地址 http://www.hp.com/go/java
參考文檔 http://www.javaperformancetuning.com/tools/hpjtune/index.shtml
工具很強大,但只能打開由以下參數生成的GC log, -verbose:gc -Xloggc:gc.log,添加其他參數生成的gc.log無法打開。
GCViewer
http://www.tagtraum.com/gcviewer.html
這個工具用的挺多的,但只能在JDK1.5以下的版本中運行,1.6以後沒有對應。
garbagecat
http://code.google.com/a/eclipselabs.org/p/garbagecat/wiki/Documentation
其它監控方法
Jvisualvm
Jvisualvm動態分析jvm內存情況和gc情況,插件:visualGC
jvisualvm還可以heapdump出對應hprof文件(默認存放路徑:監控的服務器 /tmp下),利用相關工具,比如HPjmeter可以對其進行分析
grep Full gc.log粗略觀察FullGC發生頻率
jstat –gcutil [pid] [intervel] [count]
jmap
jmap -histo pid可以觀測對象的個數和佔用空間
jmap -heap pid可以觀測jvm配置參數,堆內存各區使用情況
jprofiler,jmap dump出來用MAT分析
如果要分析的dump文件很大的話,就需要很多內存,很容易crash。
所以在啓動時,我們應該加上一些參數: Java –Xms512M –Xmx1024M –Xss8M
參考資料:
探祕Java虛擬機——內存管理與垃圾回收http://sunbean.blog.51cto.com/972509/768034
Tomcat中JVM內存溢出及合理配置
Tomcat本身不能直接在計算機上運行,需要依賴於硬件基礎之上的操作系統和一個Java虛擬機。Tomcat的內存溢出本質就是JVM內存溢出,所以在本文開始時,應該先對Java JVM有關內存方面的知識進行詳細介紹。
一、Java JVM內存介紹
JVM管理兩種類型的內存,堆和非堆。按照官方的說法:“Java 虛擬機具有一個堆,堆是運行時數據區域,所有類實例和數組的內存均從此處分配。堆是在 Java 虛擬機啓動時創建的。”“在JVM中堆之外的內存稱爲非堆內存(Non-heap memory)”。簡單來說堆就是Java代碼可及的內存,是留給開發人員使用的;非堆就是JVM留給自己用的,所以方法區、JVM內部處理或優化所需的內存(如JIT編譯後的代碼緩存)、每個類結構(如運行時常數池、字段和方法數據)以及方法和構造方法的代碼都在非堆內存中,它和堆不同,運行期內GC不會釋放其空間。
(1). 堆內存分配
JVM初始分配的內存由-Xms指定,默認是物理內存的1/64;JVM最大分配的內存由-Xmx指 定,默認是物理內存的1/4。默認空餘堆內存小於 40%時,JVM就會增大堆直到-Xmx的最大限制;空餘堆內存大於70%時,JVM會減少堆直到-Xms的最小限制。因此服務器一般設置-Xms、 -Xmx相等以避免在每次GC 後調整堆的大小。可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行堆內存設置,一般的要將-Xms和-Xmx選項設置爲相同,而-Xmn爲1/4的-Xmx值,建議堆的最大值設置爲可用內存的最大值的80%。
初始化堆的大小是JVM在啓動時向系統申請的內存的大小。一般而言,這個參數不重要。但是有的應用程序在大負載的情況下會急劇地佔用更多的內存,此時這個參數就是顯得非常重要,如果JVM啓動時設置使用的內存比較小而在這種情況下有許多對象進行初始化,JVM就必須重複地增加內存來滿足使用。由於這種原因,我們一般把-Xms和-Xmx設爲一樣大,而堆的最大值受限於系統使用的物理內存。一般使用數據量較大的應用程序會使用持久對象,內存使用有可能迅速地增長。當應用程序需要的內存超出堆的最大值時JVM就會提示內存溢出,並且導致應用服務崩潰。所以,如果Xms超過了Xmx值,或者堆最大值和非堆最大值的總和超過了物理內存或者操作系統的最大限制都會引起服務器啓動不起來。
(2). 非堆內存分配
也叫永久保存的區域,用於存放Class和Meta信息,Class在被Load的時候被放入該區域。它和存放類實例(Instance)的Heap區域不同,GC(Garbage Collection)不會在主程序運行期對PermGen space進行清理。JVM使用-XX:PermSize設置非堆內存初始值,默認是物理內存的1/64;由XX:MaxPermSize設置最大非堆內存的大小,默認是物理內存的1/4。 GC不會對PermGen space進行清理,所以如果你的APP會LOAD很多CLASS的話,就很可能出現PermGen space錯誤。
(3). JVM內存限制(最大值)
首先JVM內存限制於實際的最大物理內存(廢話!,呵呵),假設物理內存無限大的話,JVM內存的最大值跟操作系統有很大的關係。簡單的說就32位處理器雖然可控內存空間有4GB,但是具體的操作系統會給一個限制,這個限制一般是2GB-3GB(一般來說Windows系統下爲1.5G-2G,Linux系統 下爲2G-3G),而64bit以上的處理器就不會有限制了。
二、三種內存溢出異常介紹
1. OutOfMemoryError: Java heap space 堆溢出
內存溢出主要存在問題就是出現在這個情況中。當在JVM中如果98%的時間是用於GC且可用的 Heap size 不足2%的時候將拋出此異常信息。
2. OutOfMemoryError: PermGen space 非堆溢出(永久保存區域溢出)
這種錯誤常見在web服務器對JSP進行pre compile的時候。如果你的WEB APP下都用了大量的第三方jar, 其大小超過了jvm默認的大小(4M)那麼就會產生此錯誤信息了。如果web app用了大量的第三方jar或者應用有太多的class文件而恰好MaxPermSize設置較小,超出了也會導致這塊內存的佔用過多造成溢出,或者tomcat熱部署時侯不會清理前面加載的環境,只會將context更改爲新部署的,非堆存的內容就會越來越多。
3. OutOfMemoryError: unable to create new native thread. 無法創建新的線程
這種現象比較少見,也比較奇怪,主要是和jvm與系統內存的比例有關。這種怪事是因爲JVM已經被系統分配了大量的內存(比如1.5G),並且它至少要佔用可用內存的一半。
三、Java JVM內存配置
1. JVM內存分配設置的參數有四個
-Xmx Java Heap最大值,默認值爲物理內存的1/4;
-Xms Java Heap初始值,Server端JVM最好將-Xms和-Xmx設爲相同值,開發測試機JVM可以保留默認值;
-Xmn Java Heap Young區大小,不熟悉最好保留默認值;
-Xss 每個線程的Stack大小,不熟悉最好保留默認值;
-XX:PermSize:設定內存的永久保存區域;
-XX:MaxPermSize:設定最大內存的永久保存區域;
-XX:PermSize:設定內存的永久保存區域;
-XX:NewSize:設置JVM堆的‘新生代’的默認大小;
-XX:MaxNewSize:設置JVM堆的‘新生代’的最大大小;
2. 如何設置JVM的內存分配
(1)當在命令提示符下啓動並使用JVM時(只對當前運行的類Test生效):
java -Xmx128m -Xms64m -Xmn32m -Xss16m Test
(2)當在集成開發環境下(如eclipse)啓動並使用JVM時:
a. 在eclipse根目錄下打開eclipse.ini,默認內容爲(這裏設置的是運行當前開發工具的JVM內存分配): -vmargs -Xms40m -Xmx256m -vmargs表示以下爲虛擬機設置參數,可修改其中的參數值,也可添加-Xmn,-Xss,另外,eclipse.ini內還可以設置非 堆內存,如:-XX:PermSize=56m,-XX:MaxPermSize=128m。
b. 打開eclipse-窗口-首選項-Java-已安裝的JRE(對在當前開發環境中運行的java程序皆生效) 編輯當前使用的JRE,在缺省VM參數中輸入:-Xmx128m -Xms64m -Xmn32m –Xss16m。
c. 打開eclipse-運行-運行-Java應用程序(只對所設置的java類生效) 選定需設置內存分配的類-自變量,在VM自變量中輸入:-Xmx128m -Xms64m -Xmn32m -Xss16m 注:如果在同一開發環境中同時進行了b和c設置,則b設置生效,c設置無效,如: 開發環境的設置爲:-Xmx256m,而類Test的設置爲:-Xmx128m -Xms64m,則運行Test時生效的設置爲: -Xmx256m -Xms64m。
(3)當在服務器環境下(如Tomcat)啓動並使用JVM時(對當前服務器環境下所以Java程序生效):
a. 設置環境變量: 變量名:CATALINA_OPTS 變量值:-Xmx128m -Xms64m -Xmn32m -Xss16m。
b. 打開Tomcat根目錄下的bin文件夾,編輯catalina.bat,將其中的%CATALINA_OPTS%(共有四處)替換爲:-Xmx128m -Xms64m -Xmn32m -Xss16m。
c. 若沒有catalina.bat,只有tomcat.exe,tomcat6w.exe;則可以在啓動tomcat6w.exe 後 右鍵配置--Java--java option 下面輸入:
-Xmx256m –Xms64m
也可以找到註冊表HKEY_LOCAL_MACHINE\SOFTWARE\Apache Software Foundation\TomcatService Manager\Tomcat6\Parameters\JavaOptions原值爲 -Dcatalina.home="C:\ApacheGroup\Tomcat 6.0" -Djava.endorsed.dirs="C:\ApacheGroup\Tomcat 6.0\common\endorsed" -Xrs 加入 -Xms300m -Xmx350m (我的是加入-Xmx350m,tomcat才能啓動,加入-Xms300m -Xmx350m反而tomcat都不能啓動)重起tomcat服務,設置生效。
3. 查看JVM內存信息
Runtime.getRuntime().maxMemory(); //最大可用內存,對應-Xmx
Runtime.getRuntime().freeMemory(); //當前JVM空閒內存
Runtime.getRuntime().totalMemory(); //當前JVM佔用的內存總數,其值相當於當前JVM已使用的內存及freeMemory()的總和
關於maxMemory(),freeMemory()和totalMemory():maxMemory()爲JVM的最大可用內存,可通過-Xmx設置,默認值爲物理內存的1/4,設置不能高於計算機物理內存; totalMemory()爲當前JVM佔用的內存總數,其值相當於當前JVM已使用的內存及freeMemory()的總和,會隨着JVM使用內存的增加而增加; freeMemory()爲當前JVM空閒內存,因爲JVM只有在需要內存時才佔用物理內存使用,所以freeMemory()的值一般情況下都很小,而JVM實際可用內存並不等於freeMemory(),而應該等於maxMemory()-totalMemory()+freeMemory()。
4. 實例,以下給出1G內存環境下java jvm 的參數設置參考
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=64M -XX:MaxNewSize=256m -XX:MaxPermSize=128m -Djava.awt.headless=true "
大型的web工程,用tomcat默認分配的內存空間無法啓動,如果不是在myeclipse中啓動tomcat可以對tomcat這樣設置:
TOMCAT_HOME\bin\catalina.bat 中添加這樣一句話:
set JAVA_OPTS= -Xmx1024M -Xms512M -XX:MaxPermSize=256m
如果要在myeclipse中啓動,上述的修改就不起作用了,可如下設置:
Myeclipse->preferences->myeclipse->servers->tomcat->tomcat×.×->JDK面板中的
Optional Java VM arguments中添加:-Xmx1024M -Xms512M -XX:MaxPermSize=256m
對於單獨的.class,可以用下面的方法對Test運行時的jvm內存進行設置。 java -Xms64m -Xmx256m Test -Xms是設置內存初始化的大小 -Xmx是設置最大能夠使用內存的大小。
四、JVM內存配置與GC
需要考慮的是Java提供的垃圾回收機制。JVM的堆大小決定了JVM花費在收集垃圾上的時間和頻度。收集垃圾可以接受的速度與應用有關,應該通過分析實際的垃圾收集的時間和頻率來調整。如果堆的大小很大,那麼完全垃圾收集就會很慢,但是頻度會降低。如果你把堆的大小和內存的需要一致,完全收集就很快,但是會更加頻繁。調整堆大小的的目的是最小化垃圾收集的時間,以在特定的時間內最大化處理客戶的請求。在基準測試的時候,爲保證最好的性能,要把堆的大小設大,保證垃圾收集不在整個基準測試的過程中出現。如果系統花費很多的時間收集垃圾,請減小堆大小。一次完全的垃圾收集應該不超過 3-5 秒。如果垃圾收集成爲瓶頸,那麼需要指定堆的大小,檢查垃圾收集的詳細輸出,研究垃圾收集參數對性能的影響。一般說來,你應該使用物理內存的 80% 作爲堆大小。當增加處理器時,記得增加內存,因爲分配可以並行進行,而垃圾收集不是並行的。
Java Heap分爲3個區:
1.Young 2.Old 3.Permanent。Young保存剛實例化的對象。當該區被填滿時,GC會將對象移到Old區。Permanent區則負責保存反射對象,本文不討論該區。
JVM有2個GC線程:
第一個線程負責回收Heap的Young區;
第二個線程在Heap不足時,遍歷Heap,將Young 區升級爲Older區,Older區的大小等於-Xmx減去-Xmn,不能將-Xms的值設的過大,因爲第二個線程被迫運行會降低JVM的性能。
爲什麼一些程序頻繁發生GC?有如下原因:
- 程序內調用了System.gc()或Runtime.gc()。
- 一些中間件軟件調用自己的GC方法,此時需要設置參數禁止這些GC。
- Java的Heap太小,一般默認的Heap值都很小。
- 頻繁實例化對象,Release對象 此時儘量保存並重用對象,例如使用StringBuffer()和String()。
如果你發現每次GC後,Heap的剩餘空間會是總空間的50%,這表示你的Heap處於健康狀態許多Server端的Java程序每次GC後最好能有65%的剩餘空間。
經驗之談:
1.Server端JVM最好將-Xms和-Xmx設爲相同值。爲了優化GC,最好讓-Xmn值約等於-Xmx的1/3。
2.一個GUI程序最好是每10到20秒間運行一次GC,每次在半秒之內完成。
注意:
1.增加Heap的大小雖然會降低GC的頻率,但也增加了每次GC的時間。並且GC運行時,所有的用戶線程將暫停,也就是GC期間,Java應用程序不做任何工作。
2.Heap大小並不決定進程的內存使用量。進程的內存使用量要大於-Xmx定義的值,因爲Java爲其他任務分配內存,例如每個線程的Stack等。