Java虛擬機器效能監測工具Visual VM與OQL
1.Visual VM多合一工具
Visual VM是一個功能強大的多合一故障診斷和效能監控的視覺化工具,它集成了多種效能統計工具的功能,使用 Visual VM 可以代替jstat、jmap、jhat、jstack甚至是JConsole。在JDK 6 Update 7以後,Visual Vm便作為JDK的一部分發布,它完全免費。
Visual VM外掛的安裝非常容易,既可以通過離線下載外掛*.nbm。然後在 PLugin 對話方塊的Downloaded頁面下,新增已下載的外掛。也可以在Availble Plugin頁面下,線上安裝外掛,如圖所示。
若是啟動VisualVm.exe報錯:Can'nt find java1.8 or or higher ,只需要編輯\etc\visualvm.conf檔案,找到下面這行並重新指向本地Java路徑即可。
visualvm_jdkhome="D:\Java\jdk1.8.0"
外掛地址彙總:https://visualvm.github.io/pluginscenters.html
1.1 Visual VM連線應用程式
1)Visual VM支援多種連線應用程式,最常見的就是本地連線。只要本地計算機內有Java應用程式正在執行,就可以監測到。如圖所示。
2)除了本地連線外,Visual VM也支援遠端JMX連線。Java應用程式可以通過以下引數啟動程式開啟JMX埠:
-Djava.rmi.server.hostname=127.0.0.1 #遠端伺服器的ip地址 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8888 #指定jmx監聽的埠 -Dcom.sun.management.jmxremote.authenticate=false #是否開啟認證 -Dcom.sun.management.jmxremote.ssl=false #是否開啟ssl
新增JMX連線
新增成功後
3)新增遠端主機。遠端主機可以通過jstatd工具建立,如使用以下命令開啟
jstatd -J-Djava.security.policy=c:\jstatd.all.policy
文字檔案jstatd.all.policy的內容為:
grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
接著在Visual VM中新增遠端主機,如圖。正確填寫計算機IP地址。
1.2 監控應用程式概況
通過Visual VM,可以檢視應用程式的基本情況。比如,程序ID、Main Class、啟動引數等。
單機Tab頁面的Monitor頁面,即可監控應用程式的CPU、堆、永久區、類載入和執行緒數的總體情況。通過頁面上的Perform GC 和 Heap Dump按鈕還可以手工執行Full GC和生成堆快照。
1.3 Thread Dump和分析
1.4 效能分析
Visual VM有CPU和記憶體兩個取樣器。
編寫測試程式:
public class HProfTest {
public void slowMethod(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void slowerMethod(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void fastMethod(){
try {
Thread.yield();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
while (true) {
HProfTest hProfTest = new HProfTest();
hProfTest.fastMethod();
hProfTest.slowMethod();
hProfTest.slowerMethod();
}
}
}
通過Visual VM的取樣功能,可以找到佔用CPU時間最長的方法。如圖slowerMethod()方法佔用時間最長。
在Visual VM的預設統計資訊中,不包含JDK內的函式呼叫統計,需要單擊右上角的設定,手工配置。如圖。
1.5 快照
選中java應用,單擊應用程式,即可檢視堆Dump,執行緒Dump等。
右擊dump快照,可另存為。
1.6 記憶體快照分析
通過選中右鍵的堆Dump命令,可以立即獲得當前應用程式的記憶體快照,如圖。
在類頁面中,還可以對兩個不同的記憶體快照檔案進行比較。這個功能可以幫助開發者快速分析同一應用程式執行的不同時刻,記憶體資料產生的變化。
在這個類展示的頁面中,如果需要獲取類的更多資訊,可以單擊右鍵,進入該類的例項頁面;或者直接雙擊。
在例項頁面中,將顯示類的所有例項。
1.7 MBean管理
Visual VM可以通過外掛,整合JConsole的MBean管理功能。
1.8 TDA使用
TDA 是Thread Dump Analyzer 的縮寫,是一款執行緒快照分析工具。當使用jstack或者Visual VM等工具取得執行緒快照檔案後,使用TDA可以幫助開發者分析匯出的執行緒快照。TDA即是一款單獨的軟體,又是Visual VM的外掛。當作為外掛時,匯出快照後,TDA會自啟動。
1.9 BTrace介紹
BTrace 通過位元組碼注入,動態監控系統的執行情況。它可以跟蹤指定的方法呼叫、建構函式呼叫和系統記憶體等資訊。在Visual VM中安裝外掛BTrace後,右擊Java程式開啟Trece application。如圖。
BTrace指令碼示範:
@BTrace
public class TimeLogger {
@TLS
private static long startTime = 0;
@OnMethod(clazz="/.+/", //監控任意類
method="/slowMethod/") //監控slowMethod方法
public static void startMethod(){
startTime = timeMillis();
}
@OnMethod(clazz="/.+/",method="/slowMethod/",[email protected](Kind.RETURN))//方法返回時觸發
public static void endMethod(){
long time = timeMillis() - startTime;
println(strcat("execute time(nanos): ", str(time)));
}
}
以上指令碼使用@OnMethod註釋指定要監控的類和方法名稱。@Location註釋,可以指定程式執行到某一行程式碼時,觸發某一行為。
@OnMethod(clazz="/.+/", [email protected](Kind.LINE, line=26))
定時觸發(ms)
@OnMethod 更換為 @OnTimer(3000)
監控引數
public static void endMethod(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args){
//pcn 類名稱
//pmn 方法名稱
//args 引數
}
監控檔案
method="<init>" //監控建構函式
2.Visual VM對OQL的支援
上面我們學會了如何檢視堆記憶體快照,但是,堆記憶體快照十分龐大,快照中的類數量也很多。Visual VM提供了對OQL(物件查詢語言)的支援,以便於開發人員在龐大的堆記憶體資料中,快速定位所需的資源。
2.1 Visual VM的OQL基本語法
OQL 語言是一種類似SQL的查詢語言。基本語法如下:
select <JavaScript expression to select>
[ from [instanceof] <class name> <identifier>
[ where <JavaScript boolean expression to filter> ] ]
OQL由3個部分組成:select 子句、from 子句和where 子句。select 子句指定查詢結果要顯示的內容;from 子句指定查詢範圍,可指定類名,如java.lang.String、char[]、[Ljava.io.File(File陣列);where 子句用於指定查詢條件。
select 子句和where 子句支援使用Javascript 語法處理較為複雜的查詢邏輯;select 子句可以使用類似json的語法輸出多個列;from子句中可以使用instanceof關鍵字,將給定類的子類也包括到輸出列表中。
在Visual VM的OQL中,可以直接訪問物件的屬性和部分方法。如下例中,直接使用了String物件的count屬性,篩選出長度大於等於100的字串:
select s from java.lang.String s where s.count >= 100
選取長度大於等於256的 int 陣列:
select a from int[] a where a.length >= 256
篩選出表示兩位數整數的字串:
select {instance: s, content: s.toString()} from java.lang.String s where /^\d{2}$/(s.toString())
上例中,select 子句使用了json語法,指定輸出兩列為String物件以及String.toString() 的輸出。where 子句使用正則表示式,指定了符合/^\d{2}$/條件的字串。
下例使用 instance 關鍵字選取所有的ClassLoader,包括子類:
select cl from instanceof java.lang.ClassLoader cl;
由於在Java程式中,一個類可能會被多個ClassLoader同時載入,因此,這種情況下,可能需要使用Class的ID來指定Class。如下例,選出了所有ID為0x37A014D8的Class物件例項。
select s from 0x37A014D8 s;
Visual VM 的 OQL 語言支援Javascript作為子表示式。
2.2 內建heap物件
heap物件是 Visual VM OQL 的內建物件。通過 heap 物件可以實現一些強大的OQL功能。heap 物件的主要方法如下:
- forEachClass():對每一個Class物件執行一個回撥操作。它的使用方法類似於 heap.forEachClass(callback),其中 callback 為 Javascript 函式。
- findClass():查詢給定名稱的類物件,返回類的方法和屬性參考表6.3.它的呼叫方法類似 heap.findClass(className)。
- classes():返回堆快照中所有的類集合。使用方法如 heap.classes()。
- objects():返回堆快照中所有的物件集合。使用方法如 heap.objects(clazz,[includeSubtypes],[filter]),其中clazz指定類名稱,includeSubtypes指定是否選出子類,filter 為過濾器,指定篩選規則。includeSubtypes 和 filter 可以省略。
- livepaths():返回指定物件的存活路徑。即,顯示哪些物件直接或者間接引用了給定物件。它的使用方法如heap.livepaths(obj)。
- roots():返回這個堆的根物件。使用方法如heap.roots()。
使用findClass()返回的Class物件擁有的屬性和方法 :
屬性 | 方法 |
---|---|
name:類名稱 | isSubclassOf():是否是指定類的子類 |
superclass:父類 | isSuperclassOf():是否是指定類的父類 |
statics:類的靜態變數的名稱和值 | subclasses():返回所有子類 |
fields:類的域資訊 | superclasses():返回所有父類 |
下例查詢java.util.Vector類:
select heap.findClass("java.util.Vector")
查詢java.util.Vector的所有父類:
select heap.findClass("java.util.Vector").superclasses()
輸出結果如下:
java.util.AbstractList
java.util.AbstractCollection
java.lang.Object
查詢所有在java.io包下的物件:
select filter(heap.classes(), "/java.io./(it.name)")
查詢字串“56”的引用鏈:
select heap.livepaths(s) from java.lang.String s where s.toString()=='56'
如下是一種可能的輸出結果,其中java.lang.String#1600即字串“56”。它顯示了該字串被一個WebPage物件持有。
java.lang.String#1600->geym.zbase.ch7.heap.WebPage#57->java.lang.Object[]#341->java.util.Vector#11->geym.zbase.ch7.heap.Student#3
查詢這個堆的根物件:
select heap.roots()
下例查詢當前堆中所有java.io.File物件例項,引數true表示java.io.File的子類也需要被顯示:
select heap.objects("java.io.File",true)
下例訪問了TraceStudent類的靜態成員webpages物件:
select heap.findClass("geym.zbase.ch7.heap.TraceStudent").webpages
2.3 物件函式
在Visual VM中,為OQL語言還提供了一組以物件為操作目標的內建函式。通過這些函式,可以獲取目標物件的更多資訊。本節主要介紹一些常用的物件函式。
1.classof()函式
返回給定Java物件的類。呼叫方法形如classof(objname)。返回的類物件有以下屬性。
name:類名稱。
superclass:父類。
statics:類的靜態變數的名稱和值。
fields:類的域資訊。
Class物件擁有以下方法。
isSubclassOf():是否是指定類的子類。
isSuperclassOf():是否是指定類的父類。
subclasses():返回所有子類。
superclasses():返回所有父類。
下例將返回所有Vector類以及子類的型別:
select classof(v) from instanceof java.util.Vector v
一種可能的輸出如下:
java.util.Vector
java.util.Vector
java.util.Stack
2.objectid()函式
objectid()函式返回物件的ID。使用方法如objectid(objname)。
返回所有Vector物件(不包含子類)的ID:
select objectid(v) from java.util.Vector v
3.reachables()函式
reachables()函式返回給定物件的可達物件集合。使用方法如reachables(obj,[exclude])。obj為給定物件,exclude指定忽略給定物件中的某一欄位的可達引用。
下例返回'56'這個String物件的所有可達物件:
select reachables(s) from java.lang.String s where s.toString()=='56'
它的部分輸出如下:
char[]#264
這裡的返回結果是 java.lang.String.value 域的引用物件。即,給定的 String 型別的 value 域指向物件 char[]#264。如果使用過濾,要求輸出結果中不包含 java.lang.String.value 域的引用物件,程式碼如下:
select reachables(s, "java.lang.String.value") from java.lang.String s where s.toString()=='56'
以上查詢輸出結果為空,因為String物件只有value包含對其它物件的引用。
4.referrers()函式
返回引用給定物件的物件集合。使用方法如:referrers(obj)。
下例返回了引用“56”String物件的物件集合:
select referrers(s) from java.lang.String s where s.toString()=='56'
它的輸出可能如下:
java.lang.Object[]#1077
java.lang.Object[]#1055
這說明一個Object陣列引用了“56”這個字串物件。在查詢結果中單擊 java.lang.Object[]#1077,可進一步找到引用 java.lang.Object[]#1077 物件的是一個ArrayList物件。如圖所示。
下例找出長度為2,並且至少被2個物件引用的字串:
select s.toString() from java.lang.String s where (s.count==2 && count(referrers(s)) >=2)
注意:where子句中使用的邏輯運算子是&&。這是JavaScript語法,不能像SQL一樣使用AND操作符。
5.referees()函式
referees()函式返回給定物件的直接引用物件集合,用法形如:referees(obj)。
下例返回了File物件的靜態成員引用:
select referees(heap.findClass("java.io.File"))
下例返回長度為2,並且至少被2個物件引用的字串的直接引用:
select referees(s) from java.lang.String s where (s.count==2 && count(referrers(s)) >=2 )
6.sizeof()函式
sizeof()函式返回指定物件的大小(不包括它的引用物件),即淺堆(Shallow Size)。
注意:sizeof()函式返回物件的大小不包括物件的引用物件。因此,sizeof()的返回值由物件的型別決定,和物件的具體內容無關。
下例返回所有int陣列的大小以及物件:
select {size:sizeof(o),Object:o} from int[] o
下例返回所有Vector的大小以及物件:
select {size:sizeof(o),Object:o} from java.util.Vector o
它的輸出可能為如下形式:
{
size = 36,
Object = java.util.Vector#5
}
{
size = 36,
Object = java.util.Vector#6
}
可以看到,不論Vector集合包含多少物件。Vector物件所佔用的記憶體大小始終為36位元組。這是由Vector本身的結構決定的,與其內容無關。sizeof()函式就是返回物件的固有大小。
7.rsizeof()函式
rsizeof()函式返回物件以及其引用物件的大小總和,即深堆(Retained Size)。這個數值不僅與類本身的結構有關,還與物件的當前資料內容有關。
下例顯示了所有Vector物件的Shallow Size以及Retained Size:
select {size:sizeof(o),rsize:rsizeof(o)} from java.util.Vector o
部分輸出可能如下所示:
{
size = 36,
rsize = 572
}
{
size = 36,
rsize = 76
}
注意:resizeof()取得物件以及其引用物件的大小總和。因此,它的返回值與物件的當前資料內容有關。
8.toHtml()函式
toHtml()函式將物件轉為HTML顯示。
下例將Vector物件的輸出使用HTML進行加粗和斜體顯示:
select "<b><em>"+toHtml(o)+"</em></b>" from java.util.Vector o
輸出部分結果如圖7.44所示。直接點選輸出物件,可以展示例項頁面中的對應物件。
2.4 集合/統計函式
Visual VM中還有一組用於集合操作和統計的函式。可以方便地對結果集進行後處理或者統計操作。集合/統計函式主要有contains()、count()、filter()、length()、map()、max()、min()、sort()、top()等。
1.contains()函式
contains()函式判斷給定集合是否包含滿足給定表示式的物件。它的使用方法形如contains(set, boolexpression)。其中set為給定集合,boolexpression為表示式。在boolexpression中,可以使用如下contains()函式的內建物件。
it:當前訪問物件。
index:當前物件索引。
array:當前迭代的陣列/集合。
下例返回被 File 物件引用的 String 物件集合。首先通過 referrers(s) 得到所有引用String 物件的物件集合。使用 contains() 函式及其引數布林等式表示式classof(it).name == 'java.io.File'),將 contains() 的篩選條件設定為類名是java.io.File 的物件。
select s.toString() from java.lang.String s where contains(referrers(s), "classof(it).name == 'java.io.File'")
以上查詢的部分輸出結果如下:
D:\Java\jdk1.8.0\jre\lib\ext\sunpkcs11.jar
D:\Java\jdk1.8.0\jre\lib\ext\sunec.jar
D:\Java\jdk1.8.0\jre\lib\ext\nashorn.jar
D:\Java\jdk1.8.0\jre\lib\ext\localedata.jar
D:\Java\jdk1.8.0\jre\lib\ext\zipfs.jar
D:\Java\jdk1.8.0\jre\lib\ext\jfxrt.jar
D:\Java\jdk1.8.0\jre\lib\ext\dnsns.jar
通過該OQL,得到了當前堆中所有的File物件的檔名稱。可以理解為當前Java程式通過java.io.File獲得已開啟或持有的所有檔案。
2.count()函式
count()函式返回指定集合內滿足給定布林表示式的物件數量。它的基本使用方法如:count(set, [boolexpression])。引數set指定要統計總數的集合,boolexpression為布林條件表示式,可以省略,但如果指定,count()函式只計算滿足表示式的物件個數。在boolexpression表示式中,可以使用以下內建物件。
- it:當前訪問物件。
- index:當前物件索引。
- array:當前迭代的陣列/集合。
下例返回堆中所有java.io包中的類的數量,布林表示式使用正則表示式表示。
select count(heap.classes(), "/java.io./(it.name)")
下列返回堆中所有類的數量。
select count(heap.classes())
3.filter()函式
filter()函式返回給定集合中,滿足某一個布林表示式的物件子集合。使用方法形如filter(set, boolexpression)。在boolexpression中,可以使用以下內建物件。
it:當前訪問物件。
index:當前物件索引。
array:當前迭代的陣列/集合。
下例返回所有java.io包中的類。
select filter(heap.classes(), "/java.io./(it.name)")
下例返回了當前堆中,引用了java.io.File物件並且不在java.io包中的所有物件例項。首先使用referrers()函式得到所有引用java.io.File物件的例項,接著使用filter()函式進行過濾,只選取不在java.io包中的物件。
select filter(referrers(f), "! /java.io./(classof(it).name)") from java.io.File f
4.length()函式
length()函式返回給定集合的數量,使用方法形如length(set)。
下例返回當前堆中所有類的數量。
select length(heap.classes())
5.map()函式
map()函式將結果集中的每一個元素按照特定的規則進行轉換,以方便輸出顯示。使用方法形如:map(set, transferCode)。set為目標集合,transferCode為轉換程式碼。在transferCode中可以使用以下內建物件。
it:當前訪問物件。
index:當前物件索引。
array:當前迭代的陣列/集合。
下例將當前堆中的所有File物件進行格式化輸出:
select map(heap.objects("java.io.File"), "index + '=' + it.path.toString()")
輸出結果為:
0=D:\tools\jdk1.7_40\jre\bin\zip.dll
1=D:\tools\jdk1.7_40\jre\bin\zip.dll
2=D:\tools\jdk1.7_40\jre\lib\ext
3=C:\Windows\Sun\Java\lib\ext
4=D:\tools\jdk1.7_40\jre\lib\ext\meta-index
5=D:\tools\jdk1.7_40\jre\lib\ext
注意:map()函式可以用於輸出結果的資料格式化。它可以將集合中每一個物件轉成特定的輸出格式。
6.max()函式
max()函式計算並得到給定集合的最大元素。使用方法為:max(set, [express])。其中set為給定集合,express為比較表示式,指定元素間的比較邏輯。引數express可以省略,若省略,則執行數值比較。引數express可以使用以下內建物件。
lhs:用於比較的左側元素。
rhs:用於比較的右側元素。
下例顯示了當前堆中最長的String長度。對於JDK 1.6得到的堆,首先使用heap.objects()函式得到所有String物件,接著,使用map()函式將String物件集合轉為String物件的長度集合,最後,使用max()函式得到集合中的最大元素。對於JDK 1.7得到的堆,由於String結構發生變化,故通過String.value得到字串長度。
JDK 1.6匯出的堆
select max(map(heap.objects('java.lang.String', false), 'it.count'))
JDK 1.7匯出的堆
select max(map(filter(heap.objects('java.lang.String', false),'it.value!=null'), 'it.value.length'))
以上OQL的輸出為最大字串長度,輸出如下:
734.0
下例取得當前堆的最長字串。它在max()函式中設定了比較表示式,指定了集合中物件的比較邏輯。
JDK 1.6匯出的堆
select max(heap.objects('java.lang.String'), 'lhs.count > rhs.count')
JDK 1.7匯出的堆
select max(filter(heap.objects('java.lang.String'),'it.value!=null'), 'lhs. value.length > rhs.value.length')
與上例相比,它得到的是最大字串物件,而非物件的長度:
java.lang.String#908
7.min()函式
min()函式計算並得到給定集合的最小元素。使用方法為:min(set, [expression])。其中set為給定集合,expression為比較表示式,指定元素間的比較邏輯。引數expression可以省略,若省略,則執行數值比較。引數expression可以使用以下內建物件:
lhs:用於比較的左側元素
rhs:用於比較的右側元素
下例返回當前堆中陣列長度最小的Vector物件的長度:
select min(map(heap.objects('java.util.Vector', false), 'it.elementData. length'))
下例得到陣列元素長度最長的一個Vector物件:
select min(heap.objects('java.util.Vector'), 'lhs.elementData.length > rhs.elementData.length')
8.sort()函式
sort()函式對指定的集合進行排序。它的一般使用方法為:sort(set, expression)。其中,set為給定集合,expression為集合中物件的排序邏輯。在expression中可以使用以下內建物件:
lhs:用於比較的左側元素
rhs:用於比較的右側元素
下例將當前堆中的所有Vector按照內部陣列的大小進行排序:
select sort(heap.objects('java.util.Vector'),
'lhs.elementData.length - rhs.elementData.length')
下例將當前堆中的所有Vector類(包括子類),按照內部資料長度大小,從小到大排序,並輸出Vector物件的實際大小以及物件本身。
select map( sort(
heap.objects('java.util.Vector'),
'lhs.elementData.length - rhs.elementData.length' ),
'{ size: rsizeof(it), obj: it }' )
上述查詢中,首先通過heap.objects()方法得到所有Vector及其子類的例項,接著,使用sort()函式,通過Vector內部陣列長度進行排序,最後使用map()函式對排序後的集合進行格式化輸出。
9.top()函式
top()函式返回在給定集合中,按照特定順序排序的前幾個物件。一般使用方法為:top(set, expression,num)。其中set為給定集合,expression為排序邏輯,num指定輸出前幾個物件。在expression中,可以使用以下內建物件。
lhs:用於比較的左側元素。
rhs:用於比較的右側元素。
下例顯示了長度最長的前5個字串:
JDK 1.6的堆
select top(heap.objects('java.lang.String'), 'rhs.count - lhs.count', 5)
JDK 1.7的堆
select top(filter(heap.objects('java.lang.String'),'it.value!=null'), 'rhs. value.length - lhs.value.length', 5)
下例顯示長度最長的5個字串,輸出它們的長度與物件:
JDK 1.6的堆
select map(top(heap.objects('java.lang.String'), 'rhs.count - lhs.count', 5), '{ length: it.count, obj: it }')
JDK 1.7的堆
select map(top(filter(heap.objects('java.lang.String'),'it.value!=null'), 'rhs.value.length - lhs.value.length', 5), '{ length: it.value.length, obj: it }')
上述查詢的部分輸出可能如下所示:
{
length = 734.0,
obj = java.lang.String#908 }
{
length = 293.0,
obj = java.lang.String#914
}
10.sum()函式
sum()函式用於計算集合的累計值。它的一般使用方法為:sum(set,[expression])。其中第一個引數set為給定集合,引數expression用於將當前物件對映到一個整數,以便用於求和。引數expression可以省略,如果省略,則可以使用map()函式作為替代。
下例計算所有 java.util.Properties 物件的可達物件的總大小:
select sum(map(reachables(p), 'sizeof(it)')) from java.util.Properties p
將使用 sum() 函式的第2個引數 expression 代替 map() 函式,實現相同的功能:
select sum(reachables(p), 'sizeof(it)') from java.util.Properties p
11.unique()函式
unique()函式將除去指定集合中的重複元素,返回不包含重複元素的集合。它的一般使用方法形如unique(set)。
下例返回當前堆中,有多個不同的字串:
select count(unique(map(heap.objects('java.lang.String'), 'it.value')))
2.5 程式化OQL
Visual VM不僅支援在OQL控制檯上執行OQL查詢語言,也可以通過其OQL相關的JAR包,將OQL查詢程式化,從而獲得更加靈活的物件查詢功能,實現堆快照分析的自動化。
在進行OQL開發前,工程需要引用Visual VM安裝目錄下JAR包,如圖7.45所示。
這裡以分析Tomcat堆溢位檔案為例,展示程式化OQL帶來的便利。 對於給定的Tomcat堆溢位Dump檔案,這裡將展示如何通過程式,計算Tomcat平均每秒產生的session數量,程式碼如下:
public class AveLoadTomcatOOM {
public static final String dumpFilePath = "d:/tmp/tomcat_oom/tomcat.hprof";
public static void main(String args[]) throws Exception {
OQLEngine engine;
final List<Long> creationTimes = new ArrayList<Long>(000);
engine = new OQLEngine(HeapFactory.createHeap(new File(dumpFilePath)));
String query = "select s.creationTime from org.apache.catalina. session.StandardSession s"; //第8行
engine.executeQuery(query, new OQLEngine.ObjectVisitor() {
public boolean visit(Object obj) {
creationTimes.add((Long) obj);
return false;
}
});
Collections.sort(creationTimes);
long min = creationTimes.get(0) / 1000;//第18行
long max = creationTimes.get(creationTimes.size() - 1) / 1000;
System.out.println("平均壓力:" + creationTimes.size() * 1.0 / (max - min) + "次/秒");
}
}
上述程式碼第8行,通過OQL語句得到所有session的建立時間,在第18、19行獲得所有session中最早建立和最晚建立的session時間,在第21行計算整個時間段內的平均session建立速度。
執行上述程式碼,得到輸出如下:
平均壓力:311.34375次/秒
使用這種方式可以做到堆轉存檔案的全自動化分析,並將結果匯出到給定檔案,當有多個堆轉存檔案需要分析時,有著重要的作用。
除了使用以上方式外,Visual VM的OQL控制檯也支援直接使用JavaScript程式碼進行程式設計,如下程式碼實現了相同功能:
var sessions=toArray(heap.objects("org.apache.catalina.session.StandardSession"));
var count=sessions.length;
var createtimes=new Array();
for(var i=0;i<count;i++){ createtimes[i]=sessions[i].creationTime;
}
createtimes.sort();
var min=createtimes[0]/1000;
var max=createtimes[count-1]/1000;
count/(max-min)+"次/秒"
圖7.47顯示了在OQL控制檯中,執行上述指令碼以及輸出結果。
細心的讀者可能會發現,這個結果和使用Java訪問Dump檔案時的結果有所差異,這是因為JavaScript是弱型別語言,在處理整數除法時和Java有所不同,讀者可以自行研究,在此不予展開討論。
Visual VM的OQL是非常靈活的,除了上述使用JavaScript風格外,也可以使用如下函數語言程式設計風格計算:
count(heap.objects('org.apache.catalina.session.StandardSession'))/ (
max(map(heap.objects('org.apache.catalina.session.StandardSession'),'it.creationTime'))/1000-
min(map(heap.objects('org.apache.catalina.session.StandardSession'),'it.creationTime'))/1000 )
上述程式碼使用了count()、min()、max()、map()等函式,共同完成了平均值的計算。執行上述程式碼,輸出如下:
312.1240594043491