- 適用於 JDK 23 的 GraalVM (最新版)
- 適用於 JDK 24 的 GraalVM (搶先體驗版)
- 適用於 JDK 21 的 GraalVM
- 適用於 JDK 17 的 GraalVM
- 封存
- 開發組建
洞察手冊
GraalVM 洞察是一款多用途、靈活的工具,可讓您撰寫可靠的應用程式。此工具的動態特性可讓您選擇性地將追蹤切入點套用至現有的應用程式,而不會損失效能。
任何具備中等技能的駭客都可以輕鬆建立所謂的洞察程式碼片段,並將其動態套用至實際的應用程式。這可以提供對程式執行和行為的終極洞察,而不會影響其速度。
目錄 #
- 快速入門
- 熱點前 10 名範例
- 將洞察套用至任何 GraalVM 語言
- 使用 JavaScript 的洞察
- 使用 Python 的洞察
- 使用 Ruby 的洞察
- 使用 R 的洞察
- 深入 C 程式碼的洞察
- 檢查數值
- 修改本機變數
- 特定位置的洞察
- 延遲 Node.JS 中的洞察初始化
- 處理例外
- 攔截和變更執行
- 最小的負擔
- 存取本機時的最小負擔
- 存取執行堆疊
- 關於 GraalVM 洞察 API 的注意事項
- 堆積傾印
快速入門 #
從必備的 HelloWorld 範例開始。建立一個名為 source-tracing.js 的指令碼,其內容如下
insight.on('source', function(ev) {
if (ev.characters) {
print(`Loading ${ev.characters.length} characters from ${ev.name}`);
}
});
使用 GraalVM 的 node
啟動器,並加入 --insight
工具選項來執行它。觀察已載入和評估的指令碼
./bin/node --js.print --experimental-options --insight=source-tracing.js -e "print('The result: ' + 6 * 7)" | tail -n 10
Loading 29938 characters from url.js
Loading 345 characters from internal/idna.js
Loading 12642 characters from punycode.js
Loading 33678 characters from internal/modules/cjs/loader.js
Loading 13058 characters from vm.js
Loading 52408 characters from fs.js
Loading 15920 characters from internal/fs/utils.js
Loading 505 characters from [eval]-wrapper
Loading 29 characters from [eval]
The result: 42
剛剛發生了什麼事?GraalVM 洞察 source-tracing.js 指令碼已使用提供的 insight
物件,將來源接聽程式附加至執行階段。因此,每當 node
載入指令碼時,接聽程式都會收到通知,並且可以採取動作 (在此情況下,會列印已處理指令碼的長度和名稱)。
熱點前 10 名範例 #
收集洞察資訊不限於列印陳述式。您可以使用您的語言執行任何圖靈完備的計算。例如,一個程式會計算所有方法叫用,並在執行結束時傾印最常叫用的方法。
將下列程式碼儲存至 function-hotness-tracing.js
var map = new Map();
function dumpHotness() {
print("==== Hotness Top 10 ====");
var count = 10;
var digits = 3;
Array.from(map.entries()).sort((one, two) => two[1] - one[1]).forEach(function (entry) {
var number = entry[1].toString();
if (number.length >= digits) {
digits = number.length;
} else {
number = Array(digits - number.length + 1).join(' ') + number;
}
if (count-- > 0) print(`${number} calls to ${entry[0]}`);
});
print("========================");
}
insight.on('enter', function(ev) {
var cnt = map.get(ev.name);
if (cnt) {
cnt = cnt + 1;
} else {
cnt = 1;
}
map.set(ev.name, cnt);
}, {
roots: true
});
insight.on('close', dumpHotness);
map
是一個全域變數,在整個洞察指令碼中可見,它能讓程式碼在 insight.on('enter')
函式和 dumpHotness
函式之間共用資料。後者會在 node
處理序執行結束時執行 (透過 insight.on('close', dumpHotness)
註冊)。執行程式
./bin/node --js.print --experimental-options --insight=function-hotness-tracing.js -e "print('The result: ' + 6 * 7)"
The result: 42
==== Hotness Top 10 ====
516 calls to isPosixPathSeparator
311 calls to :=>
269 calls to E
263 calls to makeNodeErrorWithCode
159 calls to :anonymous
157 calls to :program
58 calls to getOptionValue
58 calls to getCLIOptionsFromBinding
48 calls to validateString
43 calls to hideStackFrames
========================
當 node
處理序結束時,會列印出包含函式叫用名稱和計數的表格。
將洞察套用至任何 GraalVM 語言 #
先前的範例是以 JavaScript 撰寫並使用 node
,但由於 GraalVM 的多語特性,您可以使用相同的工具並將其套用至 GraalVM 支援的任何語言。例如,使用 GraalVM 洞察測試 Ruby 語言。
首先,在 source-trace.js 檔案中建立工具
insight.on('source', function(ev) {
if (ev.uri.indexOf('gems') === -1) {
let n = ev.uri.substring(ev.uri.lastIndexOf('/') + 1);
print('JavaScript instrument observed load of ' + n);
}
});
在 helloworld.rb 檔案中準備您的 Ruby 程式
puts 'Hello from GraalVM Ruby!'
注意:請確定已啟用 Ruby 支援。請參閱多語程式設計指南。
將 JavaScript 工具套用至 Ruby 程式。您應該會看到以下內容
./bin/ruby --polyglot --insight=source-trace.js helloworld.rb
JavaScript instrument observed load of helloworld.rb
Hello from GraalVM Ruby!
必須使用 --polyglot
參數啟動 GraalVM 的 Ruby 啟動器,因為 source-tracing.js 指令碼仍然是以 JavaScript 撰寫。
使用 JavaScript 的洞察 #
如上一節所述,GraalVM 洞察不限於 Node.js。它在 GraalVM 提供的所有語言執行階段中都可用。試試看 GraalVM 隨附的 JavaScript 實作。
建立 function-tracing.js 指令碼
var count = 0;
var next = 8;
insight.on('enter', function(ev) {
if (count++ % next === 0) {
print(`Just called ${ev.name} as ${count} function invocation`);
next *= 2;
}
}, {
roots: true
});
在 sieve.js 上方執行它。這是一個範例指令碼,它使用埃拉托斯特尼篩法的變體來計算十萬個質數
./bin/js --insight=function-tracing.js sieve.js | grep -v Computed
Just called :program as 1 function invocation
Just called Natural.next as 17 function invocation
Just called Natural.next as 33 function invocation
Just called Natural.next as 65 function invocation
Just called Natural.next as 129 function invocation
Just called Filter as 257 function invocation
Just called Natural.next as 513 function invocation
Just called Natural.next as 1025 function invocation
Just called Natural.next as 2049 function invocation
Just called Natural.next as 4097 function invocation
使用 Python 的洞察 #
不僅可以檢測任何 GraalVM 語言,洞察指令碼也可以用該語言撰寫。在本節中,您將找到 Python 範例。
可以使用 Python 撰寫 GraalVM 洞察指令碼。這類洞察可以套用至以 Python 或任何其他語言撰寫的程式。
以下範例是一個指令碼,它會在呼叫函式 minusOne
時,列印變數 n
的值。將此程式碼儲存至 agent.py 檔案
def onEnter(ctx, frame):
print(f"minusOne {frame.n}")
class At:
sourcePath = ".*agent-fib.js"
class Roots:
roots = True
at = At()
rootNameFilter = "minusOne"
insight.on("enter", onEnter, Roots())
此程式碼使用 GraalVM 22.2 中導入的來源位置宣告規範。在較舊的 GraalVM 版本中使用動態 sourceFilter
def onEnter(ctx, frame):
print(f"minusOne {frame.n}")
class Roots:
roots = True
rootNameFilter = "minusOne"
def sourceFilter(self, src):
return src.name == "agent-fib.js"
insight.on("enter", onEnter, Roots())
使用以下命令將此指令碼套用至 agent-fib.js
`./bin/js --polyglot --insight=agent.py agent-fib.js`
注意:請確定已啟用 Python 支援。請參閱多語程式設計指南。
使用 Ruby 的洞察 #
可以使用 Ruby 撰寫 GraalVM 洞察指令碼。這類洞察可以套用至以 Ruby 或任何其他語言撰寫的程式。
注意:請確定已啟用 Ruby 支援。請參閱多語程式設計指南。
建立 source-tracing.rb 指令碼
puts("Ruby: Insight version #{insight.version} is launching")
insight.on("source", -> (env) {
puts "Ruby: observed loading of #{env.name}"
})
puts("Ruby: Hooks are ready!")
啟動 Node.js 程式,並使用 Ruby 指令碼對其進行檢測
./bin/node --js.print --experimental-options --polyglot --insight=source-tracing.rb agent-fib.js
Ruby: Initializing GraalVM Insight script
Ruby: Hooks are ready!
Ruby: observed loading of node:internal/errors
Ruby: observed loading of node:internal/util
Ruby: observed loading of node:events
....
Ruby: observed loading of node:internal/modules/run_main
Ruby: observed loading of <...>/agent-fib.js
Three is the result 3
若要追蹤變數值,請建立 agent.rb 指令碼
insight.on("enter", -> (ctx, frame) {
puts("minusOne #{frame.n}")
}, {
roots: true,
rootNameFilter: "minusOne",
at: {
sourcePath: ".*agent-fib.js"
}
})
此程式碼使用 GraalVM 22.2 中導入的來源位置宣告規範。在較舊的 GraalVM 版本中使用動態 sourceFilter
insight.on("enter", -> (ctx, frame) {
puts("minusOne #{frame.n}")
}, {
roots: true,
rootNameFilter: "minusOne",
sourceFilter: -> (src) {
return src.name == Dir.pwd+"/agent-fib.js"
}
})
上述 Ruby 指令碼範例會在呼叫 agent-fib.js 程式中的函式 minusOne
時,列印變數 n
的值
./bin/node --js.print --experimental-options --polyglot --insight=agent.rb agent-fib.js
minusOne 4
minusOne 3
minusOne 2
minusOne 2
Three is the result 3
使用 R 的洞察 #
可以使用 R 語言撰寫相同的工具。
建立 agent-r.R 指令碼
cat("R: Initializing GraalVM Insight script\n")
insight@on('source', function(env) {
cat("R: observed loading of ", env$name, "\n")
})
cat("R: Hooks are ready!\n")
使用它來追蹤 test.R 程式
./bin/Rscript --insight=agent-r.R test.R
R: Initializing GraalVM Insight script
R: Hooks are ready!
R: observed loading of test.R
唯一的變更是 R 語言。所有其他 GraalVM 洞察功能和 API 保持不變。
深入 C 程式碼的洞察 #
不僅可以解譯動態語言,藉助 GraalVM 的 LLI 實作,甚至可以混合使用以 C、C++、Fortran、Rust 等撰寫的靜態編譯程式。
例如,採用長時間執行的程式,例如 sieve.c,它的 main
方法中包含永無止盡的 for
迴圈。您可能想要給它一些執行配額。
首先,在 GraalVM 上執行程式
export TOOLCHAIN_PATH=`.../bin/lli --print-toolchain-path`
${TOOLCHAIN_PATH}/clang agent-sieve.c -lm -o sieve
./bin/lli sieve
GraalVM clang
包裝函式會新增特殊選項,指示一般 clang
將 LLVM 位元碼資訊保留在 sieve
可執行檔中,以及一般的原生程式碼。然後,GraalVM 的 lli
解譯器可以使用位元碼,以全速解譯程式。順帶一提,比較透過 ./sieve
直接原生執行和 ./bin/lli sieve
的解譯器速度結果。就解譯器而言,它應該會顯示相當不錯的結果。
現在著重於打破無盡迴圈。您可以使用這個 JavaScript agent-limit.js 洞察指令碼來執行此動作
var counter = 0;
insight.on('enter', function(ctx, frame) {
if (++counter === 1000) {
throw `GraalVM Insight: ${ctx.name} method called ${counter} times. enough!`;
}
}, {
roots: true,
rootNameFilter: 'nextNatural'
});
此指令碼會計算 C nextNatural
函式的叫用次數,當函式被叫用一千次時,它會發出錯誤以停止 sieve
執行。執行程式,如下所示
./bin/lli --polyglot --insight=agent-limit.js sieve
Computed 97 primes in 181 ms. Last one is 509
GraalVM Insight: nextNatural method called 1000 times. enough!
at <js> :anonymous(<eval>:7:117-185)
at <llvm> nextNatural(agent-sieve.c:14:186-221)
at <llvm> nextPrime(agent-sieve.c:74:1409)
at <llvm> measure(agent-sieve.c:104:1955)
at <llvm> main(agent-sieve.c:123:2452)
可以從原生程式碼存取基本本機變數。將上述洞察指令碼取代為
insight.on('enter', function(ctx, frame) {
print(`found new prime number ${frame.n}`);
}, {
roots: true,
rootNameFilter: (n) => n === 'newFilter'
});
每次將新的質數加入篩選器清單時,都會列印訊息
./bin/lli --polyglot --insight=agent-limit.js sieve | head -n 3
found new prime number 2
found new prime number 3
found new prime number 5
混合使用 lli
、多語和 GraalVM 洞察,在追蹤、控制和以互動或批次方式除錯原生程式方面開啟了巨大的可能性。
檢查數值 #
GraalVM 洞察不僅允許追蹤程式執行發生的位置,它還能在執行期間提供對本機變數和函式引數值的存取權。例如,您可以撰寫一個工具來顯示函式 fib
中引數 n
的值
insight.on('enter', function(ctx, frame) {
print('fib for ' + frame.n);
}, {
roots: true,
rootNameFilter: 'fib'
});
此工具使用第二個函式引數 frame
來存取每個檢測函式內的本機變數值。上述洞察指令碼也使用 rootNameFilter
,使其勾點僅套用至名為 fib
的函式
function fib(n) {
if (n < 1) return 0;
if (n < 2) return 1;
else return fib(n - 1) + fib(n - 2);
}
print("Two is the result " + fib(3));
當該工具儲存在 fib-trace.js 檔案中,而實際程式碼儲存在 fib.js 中時,則叫用下列命令會產生有關程式執行和函式叫用之間傳遞的參數的詳細資訊
./bin/node --js.print --experimental-options --insight=fib-trace.js fib.js
fib for 3
fib for 2
fib for 1
fib for 0
fib for 1
Two is the result 2
總結本節,GraalVM 洞察是一個對多語、語言不可知且面向層面的程式設計有用的工具。
修改本機變數 #
GraalVM 洞察不僅可以存取本機變數,還可以修改它們。例如,採用這個程式來加總陣列
function plus(a, b) {
return a + b;
}
var sum = 0;
[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((n) => sum = plus(sum, n));
print(sum);
它會列印出數字 45
。套用下列洞察指令碼來在加入非偶數之前「清除」它們
insight.on('enter', function zeroNonEvenNumbers(ctx, frame) {
if (frame.b % 2 === 1) {
frame.b = 0;
}
}, {
roots: true,
rootNameFilter: 'plus'
});
當使用 js --insight=erase.js sumarray.js
啟動時,只會列印出數值 20
。
GraalVM 洞察 enter
和 return
勾點只能修改現有的變數。它們無法引入新的變數。嘗試這樣做會產生例外狀況。
特定位置的洞察 #
若要取得特定程式碼位置的變數,at
物件可能不只有一個強制來源規範:具有符合來源檔案路徑的規則運算式的 sourcePath
屬性,或具有來源 URI 字串表示法的 sourceURI
屬性。也可以指定選用的 line
和/或 column
。讓我們建立一個 distance.js 來源檔案
(function(x, y) {
let x2 = x*x;
let y2 = y*y;
let d = Math.sqrt(x2 + y2);
for (let i = 0; i < d; i++) {
// ...
}
return d;
})(3, 4);
然後,我們可以套用下列 distance-trace.js 洞察指令碼來取得變數值
insight.on('enter', function(ctx, frame) {
print("Squares: " + frame.x2 + ", " + frame.y2);
}, {
statements: true,
at: {
sourcePath: ".*distance.js",
line: 4
}
});
insight.on('enter', function(ctx, frame) {
print("Loop var i = " + frame.i);
}, {
expressions: true,
at: {
sourcePath: ".*distance.js",
line: 5,
column: 21
}
});
這會給我們
./bin/js --insight=distance-trace.js distance.js
Squares: 9, 16
Loop var i = 0
Loop var i = 1
Loop var i = 2
Loop var i = 3
Loop var i = 4
Loop var i = 5
延遲 Node.JS 中的洞察初始化 #
GraalVM 洞察可以在任何 GraalVM 語言執行階段中使用,包括 node
實作。但是,當在 node
中時,您不想要撰寫純洞察指令碼。您可能想要使用 node
生態系統的全部功能,包括其模組。以下是一個執行此動作的範例 agent-require.js 指令碼
let initialize = function (require) {
let http = require("http");
print(`${typeof http.createServer} http.createServer is available to the agent`);
}
let waitForRequire = function (event) {
if (typeof process === 'object' && process.mainModule && process.mainModule.require) {
insight.off('source', waitForRequire);
initialize(process.mainModule.require.bind(process.mainModule));
}
};
insight.on('source', waitForRequire, { roots: true });
Insight 腳本會盡快初始化,但在那時 require
函式尚未準備就緒。因此,腳本首先會附加一個偵聽器來監聽已載入的腳本,當主要使用者腳本正在載入時,它會取得其 process.mainModule.require
函式。然後,它會使用 insight.off
移除探針,並調用實際的 initialize
函式來執行真正的初始化,同時可以存取所有 Node 模組。該腳本可以使用以下方式執行:
./bin/node --js.print --experimental-options --insight=agent-require.js yourScript.js
已知此初始化順序可在 GraalVM 的 node
版本 12.10.0 上運作,並使用主要的 yourScript.js
參數啟動。
處理例外狀況 #
GraalVM Insight 工具可能會拋出例外狀況,然後將其傳播到周圍的使用者腳本。假設您有一個程式 seq.js 記錄各種訊息
function log(msg) {
print(msg);
}
log('Hello GraalVM Insight!');
log('How');
log('are');
log('You?');
您可以註冊一個工具 term.js,並根據觀察到的記錄訊息來停止 seq.js 程式的執行
insight.on('enter', (ev, frame) => {
if (frame.msg === 'are') {
throw 'great you are!';
}
}, {
roots: true,
rootNameFilter: 'log'
});
term.js 工具會等待調用帶有訊息 are
的 log
函式,並在那時發出自己的例外狀況,有效地中斷使用者程式的執行。結果會得到
./bin/js --polyglot --insight=term.js seq.js
Hello GraalVM Insight!
How
great you are!
at <js> :=>(term.js:3:75-97)
at <js> log(seq.js:1-3:18-36)
at <js> :program(seq.js:7:74-83)
Insight 工具發出的例外狀況會被視為常規語言例外狀況。seq.js 程式可以使用常規的 try { ... } catch (e) { ... }
區塊來捕捉它們,並像處理常規使用者程式碼發出的例外狀況一樣處理它們。
攔截和修改執行 #
GraalVM Insight 能夠修改程式的執行。它可以跳過某些計算,並以自己的替代方案取代它們。以下面的 plus
函式為例
function plus(a, b) {
return a + b;
}
很容易更改 plus
方法的行為。以下 Insight 腳本透過使用 ctx.returnNow
功能,將 +
運算取代為乘法
insight.on('enter', function(ctx, frame) {
ctx.returnNow(frame.a * frame.b);
}, {
roots: true,
rootNameFilter: 'plus'
});
returnNow
方法會立即停止執行,並返回到 plus
函式的調用者。plus
方法的主體完全不會執行,因為 Insight 的 on('enter', ...)
是在執行函式主體之前套用的。將兩個數字相乘而不是相加可能聽起來不太吸引人,但相同的方法在為重複的函式調用提供附加的快取(例如,記憶化)時非常有用。
也可以讓原始函式程式碼執行,然後只修改其結果。例如,將 plus
函式的結果修改為始終為非負數
insight.on('return', function(ctx, frame) {
let result = ctx.returnValue(frame);
ctx.returnNow(Math.abs(result));
}, {
roots: true,
rootNameFilter: 'plus'
});
Insight 掛鉤會在 plus
函式返回時執行,並使用 returnValue
協助程式函式從目前的 frame
物件取得計算出的返回值。然後,它可以修改該值,而 returnNow
會返回一個新的結果。returnValue
函式始終可在提供的 ctx
物件上使用,但僅當在 on('return', ...)
掛鉤中使用時,它才會返回有意義的值。
最小的額外負荷 #
如果您問說,當應用腳本時,GraalVM Insight 是否會造成任何效能上的額外負荷,答案是「否」或「最小」。額外負荷取決於您的腳本所執行的操作。當它們在您的程式碼庫中添加和傳播複雜的計算時,您將會付出計算的代價。然而,那將是您的程式碼的額外負荷,而不是工具的額外負荷。使用一個簡單的 function-count.js 腳本來測量額外負荷。
var count = 0;
function dumpCount() {
print(`${count} functions have been executed`);
}
insight.on('enter', function(ev) {
count++;
}, {
roots: true
});
insight.on('close', dumpCount);
在 sieve.js 範例上使用該腳本進行五十次疊代,該範例使用埃拉托斯特尼篩法的變體來計算十萬個質數。重複計算五十次,讓執行階段有機會預熱並適當最佳化。以下是最佳的執行情況
./bin/js sieve.js | grep -v Computed
Hundred thousand prime numbers in 75 ms
Hundred thousand prime numbers in 73 ms
Hundred thousand prime numbers in 73 ms
現在,將其與啟用 GraalVM Insight 腳本執行時的執行時間進行比較
./bin/js --insight=function-count.js sieve.js | grep -v Computed
Hundred thousand prime numbers in 74 ms
Hundred thousand prime numbers in 74 ms
Hundred thousand prime numbers in 75 ms
72784921 functions have been executed
差異為 2 毫秒。GraalVM Insight 融合了程式碼和工具收集腳本之間的差異,使所有程式碼都能作為一個整體運作。count++
調用成為程式中所有表示程式函式 ROOT
的位置的自然組成部分。
存取區域變數時的最小額外負荷 #
GraalVM Insight 能夠幾乎「免費」地存取區域變數。存取區域變數的 GraalVM Insight 程式碼會與定義它們的實際函式程式碼融合,而且沒有明顯的速度減慢。
這可以使用此 sieve.js 演算法來演示,以計算十萬個質數。它會將找到的質數保留在一個透過以下函式建構的連結列表中
function Filter(number) {
this.number = number;
this.next = null;
this.last = this;
}
首先,透過調用計算五十次並測量完成最後一輪所花費的時間來測試行為
./bin/js -e "var count=50" --file sieve.js | grep Hundred | tail -n 1
Hundred thousand prime numbers in 73 ms
然後,透過觀察每次分配新的質數插槽來「誘使」系統,例如,呼叫 new Filter
建構函式
var sum = 0;
var max = 0;
insight.on('enter', (ctx, frame) => {
sum += frame.number;
if (frame.number > max) {
max = frame.number;
}
}, {
roots: true,
rootNameFilter: 'Filter'
});
insight.on('return', (ctx, frame) => {
log(`Hundred thousand prime numbers from 2 to ${max} has sum ${sum}`);
sum = 0;
max = 0;
}, {
roots: true,
rootNameFilter: 'measure'
});
每次分配 new Filter(number)
時,都會捕捉 number
的最大值(例如,找到的最高質數),以及到目前為止找到的所有質數的 sum
。當 measure
中的主迴圈結束時(表示有十萬個質數),結果會被印出。
現在嘗試以下操作
./bin/js -e "var count=50" --insight=sieve-filter1.js --file sieve.js | grep Hundred | tail -n 2
Hundred thousand prime numbers from 2 to 1299709 has sum 62260698721
Hundred thousand prime numbers in 74 ms
完全沒有速度減慢。當 GraalVM Insight 與 GraalVM 編譯器的內聯演算法結合使用時,可以實現出色的工具功能,而幾乎沒有效能損失。
存取執行堆疊 #
GraalVM Insight 有一種方法可以存取整個執行堆疊。以下程式碼片段顯示如何執行此操作
insight.on("return", function(ctx, frame) {
print("dumping locals");
ctx.iterateFrames((at, vars) => {
for (let p in vars) {
print(` at ${at.name} (${at.source.name}:${at.line}:${at.column}) ${p} has value ${vars[p]}`);
}
});
print("end of locals");
}, {
roots: true
});
每當觸發 Insight 掛鉤時,它會使用函式的 name
、source.name
、line
和 column
來印出目前的執行堆疊。此外,它也會印出每個框架的所有區域 vars
的值。也可以透過將新值指派給它們來修改現有變數的值:vars.n = 42
。存取整個堆疊很靈活,但與存取目前執行框架中的區域變數不同,它不是快速的操作,如果您希望程式繼續全速執行,請謹慎使用。
堆積傾印 #
GraalVM Insight 可以用來在執行期間對程式堆積的區域進行快照。將 --heap.dump=/path/to/output.hprof
選項與常規的 --insight
選項一起使用。Insight 腳本將可以使用具有 dump
函式的 heap
物件。將掛鉤放置在需要的位置,並在正確的時刻傾印堆積
insight.on('return', (ctx, frame) => {
heap.dump({
format: '1.0',
depth: 50, // set max depth for traversing object references
events: [
{
stack : [
{
at : ctx, // location of dump sieve.js:73
frame : {
// assemble frame content as you want
primes : frame.primes, // capture primes object
cnt : frame.cnt, // capture cnt value
},
depth : 10 // optionally override depth to ten references
}, // there can be more stack elements like this one
]
},
// there can be multiple events like the previous one
],
});
throw 'Heap dump written!';
}, {
roots: true,
rootNameFilter: 'measure'
});
將程式碼片段儲存為 dump.js 檔案。取得 sieve.js 檔案並依下列方式啟動
./bin/js --insight=dump.js --heap.dump=dump.hprof --file sieve.js
在 measure
函式結束時將會建立一個 dump.hprof 檔案,該檔案會擷取您程式記憶體的狀態。使用 VisualVM 或 NetBeans 等常規工具檢查產生的 .hprof 檔案。
上圖顯示在 sieve.js 腳本中 measure
函式結束時擷取的堆積傾印。該函式剛計算出十萬個(變數 cnt
中提供的計數)質數。該圖顯示一個連結列表 Filter
,其中包含從 2
到 17
的質數。連結列表的其餘部分隱藏在 unreachable
物件後面(僅請求深度最多為 10
的參考)。最後一個變數 x
顯示了計算所有質數所搜尋的自然數數量。
堆積傾印快取 #
為了加快堆積傾印過程並最佳化產生的傾印,可以啟用記憶體快取。在傾印到快取之間的屬性未變更的物件僅儲存一次,從而減少了產生的堆積傾印大小。新增(例如) --heap.cacheSize=1000
選項以將記憶體快取用於 1000 個事件。依預設,快取會在已滿時傾印到檔案並清除。可以使用 --heap.cacheReplacement=lru
選項來變更該原則,該選項會在快取大小達到限制時,將最新的傾印事件保留在快取中,並捨棄最舊的傾印事件。
若要將快取刷新到堆積傾印檔案,需要明確呼叫 heap.flush()
。
關於 GraalVM Insight API 的注意事項 #
透過 insight
物件公開的 GraalVM Insight API 的相容性是以相容的方式實作的。GraalVM Insight API 可以透過此連結找到。insight
物件屬性和函式是其javadoc 的一部分。
未來版本將新增新功能,但一旦公開的任何功能都將保持有效。如果您的腳本依賴於某些新功能,它可以檢查公開的 API 版本
print(`GraalVM Insight version is ${insight.version}`);
API 中的新元素帶有相關的 @since
標記,以描述相關功能可用的最低版本。