Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

作者:像風一樣i
來源:https://www.cnblogs.com/yueshutong/p/9818491.html
Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

1.Visual VM多合一工具

Visual VM是一個功能強大的多合一故障診斷和性能監控的可視化工具,它集成了多種性能統計工具的功能,使用 Visual VM 可以代替jstat、jmap、jhat、jstack甚至是JConsole。在JDK 6 Update 7以後,Visual Vm便作為JDK的一部分發布,它完全免費。

官方下載:https://visualvm.github.io/download.html

中文漢化版:https://download.csdn.net/download/yueshutong123/10729777

Visual VM插件的安裝非常容易,既可以通過離線下載插件*.nbm。然後在 PLugin 對話框的Downloaded頁面下,添加已下載的插件。也可以在Availble Plugin頁面下,在線安裝插件,如圖所示。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

若是啟動VisualVm.exe報錯:Can'nt find java1.8 or or higher ,只需要編輯\etc\visualvm.conf文件,找到下面這行並重新指向本地Java路徑即可。

visualvm_jdkhome="D:\Java\jdk1.8.0"

漢化版的插件地址已過時,點擊“插件”->“設置”->“編輯”,更換URL為https://visualvm.github.io/archive/uc/release138/updates.xml.gz

插件地址彙總:https://visualvm.github.io/pluginscenters.html

1.1 Visual VM連接應用程序

1)Visual VM支持多種連接應用程序,最常見的就是本地連接。只要本地計算機內有Java應用程序正在執行,就可以監測到。如圖所示。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

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連接

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

添加成功後

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

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地址。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

1.2 監控應用程序概況

通過Visual VM,可以查看應用程序的基本情況。比如,進程ID、Main Class、啟動參數等。

單機Tab頁面的Monitor頁面,即可監控應用程序的CPU、堆、永久區、類加載和線程數的總體情況。通過頁面上的Perform GC 和 Heap Dump按鈕還可以手工執行Full GC和生成堆快照。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

1.3 Thread Dump和分析

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

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()方法佔用時間最長。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

在Visual VM的默認統計信息中,不包含JDK內的函數調用統計,需要單擊右上角的設置,手工配置。如圖。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

1.5 快照

選中java應用,單擊應用程序,即可查看堆Dump,線程Dump等。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

右擊dump快照,可另存為。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

1.6 內存快照分析

通過選中右鍵的堆Dump命令,可以立即獲得當前應用程序的內存快照,如圖。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

在類頁面中,還可以對兩個不同的內存快照文件進行比較。這個功能可以幫助開發者快速分析同一應用程序運行的不同時刻,內存數據產生的變化。

在這個類展示的頁面中,如果需要獲取類的更多信息,可以單擊右鍵,進入該類的實例頁面;或者直接雙擊。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

在實例頁面中,將顯示類的所有實例。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

1.7 MBean管理

Visual VM可以通過插件,集成JConsole的MBean管理功能。

關於JConsole MBean的使用請參考另一篇博客:https://www.cnblogs.com/yueshutong/p/9812464.html

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。如圖。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

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/",location=@Location(Kind.RETURN))//方法返回時觸發
public static void endMethod(){
long time = timeMillis() - startTime;
println(strcat("execute time(nanos): ", str(time)));
}
}

以上腳本使用@OnMethod註釋指定要監控的類和方法名稱。@Location註釋,可以指定程序運行到某一行代碼時,觸發某一行為。

@OnMethod(clazz="/.+/", location=@Location(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對象擁有的屬性和方法 :

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

下例查找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對象。如圖所示。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

下例找出長度為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所示。直接點擊輸出對象,可以展示實例頁面中的對應對象。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

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所示。

Java虛擬機性能監測工具Visual VM與OQL對象查詢語言

這裡以分析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)+"次/秒"

下圖顯示了在OQL控制檯中,執行上述腳本以及輸出結果。

Java虛擬機性能監測工具Visual VM與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 

因JDK版本問題,無法保證上述操作的有效性,僅供學習與參考。

相關推薦

推薦中...