1. 程式人生 > >Java虛擬機器效能監測工具Visual VM與OQL

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