探索堆中的祕密之OQL

前言

坦白的說,有點標題黨,一股走進科學的感受,其實就是與你們分享一下關於OQL的一些內容。java

OQL是什麼?json

它是基於java堆上快照的對象查詢語言,語法呢,和咱們日常的SQL,HQL等等是近似的,畢竟QL一家親。數組

根據它堆上查詢的特性,因此天然是能夠進行內存診斷,或者出一個你想要的報表等等用途。bash

筆者本人也比較喜歡使用OQL,畢竟能本身寫代碼,仍是頗爲靈活,靈活天然是惹人親睞的因素。jvm

第二個緣由天然是: 工具

今天從介紹,到實際例子,帶給還不清楚「OQL」的盆友,多一種調試的思路。測試

正如《深刻理解java虛擬機》所說:「沒有什麼工具是'祕密武器',擁有了就能‘包治百病’」。 因此多一項技能,也只是多一種調試的思路。優化

正文

若是你堅持看到了正文,相信你是瞭解heap dump是何物,ui

因此內存結構等概念在本文不過多介紹。url

heap dump分析工具衆多,從JConsole到Jhat,以及廣爲應用的MAT,它們自帶的功能已經很強大了,還用本身寫OQL嗎?

這個好理解,看Report和寫SQL,天然不是一個場景,前者是專一特定領域的結果,後者是靈活的開發語言。

先從簡單例子開始,後面筆者會用《Effective Java》中那段經典的「可能存在內存泄漏」示例代碼,展現怎樣寫出「有趣的OQL」。

簡單的例子

第一步確定是搞一個heap dump,既然是先舉簡單的例子,能夠隨便找一個heap dump入手。

Tip: 每一個人的工具習慣不同,筆者今天的操做,好比heap dump,讀取hprof文件,以及執行OQL,都是在jVisualVm中進行的。

若是是有經驗的盆友,固然能夠跳過簡單的例子,繼續看下面的內容~

首先,先從左側列表中的幸運進程中抽取一位觀衆,進行heap dump,看名字應該是筆者正在跑的IDEA中內部進程。

不出意外,會跳到heapDump的分析頁面,其它功能暫且掠過,將左上角的下拉框選到今天的主題「OQL Console」。

查詢全部「字符串實例」,能夠算是OQL中的「hello world」了。 本文也不例外:

select 
  s
from 
  java.lang.String s
複製代碼

將以上OQL寫到下面的文本框中

從上圖看出,已經輸出了全部String的實例,是否是和普通的DB可視化工具沒什麼區別?

輸入完OQL運行,上面就顯示了查詢結果,順着查詢結果,能夠找到該對象的值,引用等等信息。

這裏還有一個小細節,其實OQL也是有方言一說,只不過是受限於運行工具不一樣,

好比在有些書籍上是以select * from java.lang.String做爲第一個入門語句,

可是在筆者當前的visualVM2.0中運行則會報錯,MAT下會正常輸出。

雖然大致語法是一致的,可是細節上,還要以你習慣使用工具的官網介紹的語法爲準。

Effective Java中的例子

若是你閱讀過Effective Java,那麼你必定記的有一段經典代碼,用於演示存在內存泄漏風險的場景。

若是沒有讀過也不要緊,這段代碼很簡單,筆者也粘貼了過來:

class Stack {
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;

        public Stack() {
            elements = new Object[DEFAULT_INITIAL_CAPACITY];
        }

        public void push(Object e) {
            ensureCapacity();
            elements[size++] = e;
        }

        public Object pop() {
            if (size == 0) {
                throw new EmptyStackException();
            }
            return elements[--size];
        }

        /**
         * Ensure space for at least one more element, roughly
         * doubling the capacity each time the array needs to grow.
         */
        private void ensureCapacity() {
            if (elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }
    }
複製代碼

代碼很簡單,一個本身實現的棧,出棧操做pop忘記了消除elements數組中的引用,存在內存泄漏的風險。

那麼筆者就用這段代碼作演示,看看OQL都能作些什麼。

筆者測試代碼

public static void main(String[] args) throws Exception {
        // stack1
        Stack stack1 = new Stack();
        // stack1入棧30個元素
        addItem(stack1);
        // stack2
        Stack stack2 = new Stack();
        // stack2入棧30個元素
        addItem(stack2);
        // stack2出棧20元素
        for (int i = 0; i < 20; i++) {
            stack2.pop();
        }
        System.out.println("----Over----");
        // 通知full gc, 若是jvm心情不錯,能夠拿到dump
        System.gc();
        Thread.sleep(5000);
}
複製代碼

筆者的測試很簡單,實例化了兩個上文的棧,一個沒有出棧操做,一個有出棧操做,有出棧操做的實例,天然保存了過時的引用。

爲了拿到dump文件,我將啓動參數增長了下面兩條:

  • -XX:+HeapDumpBeforeFullGC
  • -XX:HeapDumpPath=/Users/vt/logs/jvm

即FullGC以前dump一下。

繼續,一塊兒看看能夠用OQL作一些什麼事情? 下面的內容爲了省略篇幅,OQL和輸出結果,用文字Input和Output表示。

Input:

select 
  s.elements.length
from instanceof 
  com.vt.example.LeakTest$Stack s
複製代碼

Output:

33
33
複製代碼

能夠看出打印出來兩個Stack的實例結果,與測試內容一致。 而且其中elements的長度都是被擴容到了33。

由於結果輸出,支持json的格式,咱們不妨再多查詢一些內容。

Input:

select 
  {
    "elements's length" :s.elements.length, 
    "instance": s, 
    "size attr" : s.size, 
    "count" : (actualCount = count(filter(s.elements, "it != null")))
  }
from instanceof 
  com.vt.example.LeakTest$Stack s
複製代碼

Output:

{
instance = com.vt.example.LeakTest$Stack#1,
count = 30,
size attr = 30,
elements's length = 33 } { instance = com.vt.example.LeakTest$Stack#2, count = 30, size attr = 10, elements's length = 33
}
複製代碼

上面筆者的OQL語句分別查詢了,實例地址,實例size屬性,實例中數組容器的長度,以及真實的size大小

真實的size大小是統計了數組容器中不爲null的元素。

也就是說,上文提到的stack問題是:出棧時沒有把過時的元算置於null,致使了內存泄漏風險。

那麼實際看看上面的OQL結果,是否是知道了什麼?

沒錯,那就是第二個實例實際保存的元素,和size屬性大小不符,天然是已經發生問題的實例。

可是看的還不是那麼清晰,咱們再優化一個版本看看如何:

Input:

select 
  {
    "實例中數組容器當前大小" :s.elements.length, 
    "實例地址": s, 
    "當前size屬性" : s.size, 
    "實際保存的引用數量" : (actualCount = count(filter(s.elements, "it != null"))),
    "疑似泄漏" : (actualCount > s.size) ? "<font color='red'>有</font>" : "無" 
  }
from instanceof 
  com.vt.example.LeakTest$Stack s

複製代碼

Output:

最後這版本用了中文描述,而且還有文字高亮,是否是更清晰了一些?

用OQL直接查出咱們想要的一個報表,依靠基本功能可不必定能作獲得哦。

最後

看到這,你們應該對OQL有一個初步的瞭解和如何簡單「玩轉」了,本文也就結束了。

實際中,可不是區區上面的OQL就能查出來內存泄漏,內容是演示功能爲主,帶給不瞭解OQL的讀者一套新的思路,可不是什麼解決方案啊~


相關文章
相關標籤/搜索