support classloader metaspace stat#3188
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69f587f89a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| event.getLong("classCount"), | ||
| event.getLong("chunkSize"), | ||
| event.getLong("blockSize"), | ||
| event.getLong("hiddenBlockSize")); |
There was a problem hiding this comment.
Support pre-17 JFR classloader statistics fields
StatsRow.from unconditionally reads hiddenBlockSize, but jdk.ClassLoaderStatistics on older supported runtimes (e.g. JDK 11) does not expose that field, so event.getLong("hiddenBlockSize") throws at runtime and the whole classloader-metaspace command fails. Because the command is registered whenever JFR is present, this turns into a hard failure on those JDKs instead of degraded output; the parser should tolerate version differences (for example, fallback to unsafeAnonymousBlockSize or 0).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR introduces a new classloader-metaspace command to report metaspace / class metadata memory per ClassLoader using JFR, along with terminal rendering, tests, and documentation.
Changes:
- Add
classloader-metaspacecommand implementation backed by JFRjdk.ClassLoaderStatistics - Add result model + view, register them in the command pack and view resolver
- Add English/Chinese docs and unit tests for parsing/sorting and table rendering
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| site/docs/en/doc/commands.md | Adds the new command to the English command index |
| site/docs/en/doc/classloader-metaspace.md | New English documentation page for classloader-metaspace |
| site/docs/doc/commands.md | Adds the new command to the Chinese command index |
| site/docs/doc/classloader-metaspace.md | New Chinese documentation page for classloader-metaspace |
| core/src/test/java/com/taobao/arthas/core/command/view/ClassLoaderMetaspaceViewTest.java | Tests rendering for default/verbose columns and narrow terminals |
| core/src/test/java/com/taobao/arthas/core/command/klass100/ClassLoaderMetaspaceCommandTest.java | Tests time parsing, display-name fallback, sorting/limit behavior |
| core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java | Registers the new view for rendering |
| core/src/main/java/com/taobao/arthas/core/command/view/ClassLoaderMetaspaceView.java | Implements terminal table rendering for the command output |
| core/src/main/java/com/taobao/arthas/core/command/model/ClassLoaderMetaspaceModel.java | Adds a structured model for classloader-metaspace output |
| core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderMetaspaceCommand.java | Implements the new command using JFR recording + mapping event |
| core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java | Registers the new command when JFR is available |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | Name | Specification | | ||
| | --------------------: | :-------------------------------------------------------------------------------------------------------------- | | ||
| | `[c:]` | ClassLoader hashcode, same format as `classloader -c` | | ||
| | `[classLoaderClass:]` | Filter by full ClassLoader class name | | ||
| | `[duration:]` | JFR sampling duration, `2500ms` by default; supports `ms`, `s`, `m`; a plain number means milliseconds | | ||
| | `[period:]` | `jdk.ClassLoaderStatistics` period, `500ms` by default; supports `ms`, `s`, `m` | | ||
| | `[limit:]` | Show only the first N rows after sorting by `chunkSize` descending | | ||
| | `[verbose:]` | Show full diagnostic columns, including `classLoaderData`, `hiddenBlockSize` and `type`; `-v` is also supported | |
| | 参数名称 | 参数说明 | | ||
| | --------------------: | :--------------------------------------------------------------------------------- | | ||
| | `[c:]` | ClassLoader 的 hashcode,格式和 `classloader -c` 一致 | | ||
| | `[classLoaderClass:]` | 按 ClassLoader 完整类名过滤 | | ||
| | `[duration:]` | JFR 采样时长,默认 `2500ms`,支持 `ms`、`s`、`m`,裸数字按毫秒处理 | | ||
| | `[period:]` | `jdk.ClassLoaderStatistics` 采样周期,默认 `500ms`,支持 `ms`、`s`、`m` | | ||
| | `[limit:]` | 只输出按 `chunkSize` 降序排序后的前 N 行 | | ||
| | `[verbose:]` | 输出完整诊断列,包括 `classLoaderData`、`hiddenBlockSize` 和 `type`;也可使用 `-v` | |
| long millis = Long.parseLong(text) * multiplier; | ||
| if (millis <= 0) { | ||
| throw new IllegalArgumentException(optionName + " must be greater than 0."); | ||
| } | ||
| return millis; | ||
| } catch (NumberFormatException e) { | ||
| throw new IllegalArgumentException(optionName + " is invalid: " + value); |
| private static StatsRow from(RecordedEvent event) { | ||
| RecordedClassLoader loader = event.getValue("classLoader"); | ||
| long loaderId = loader == null ? 0 : loader.getId(); | ||
| String name = loader == null ? "BootstrapClassLoader" : loader.getName(); | ||
| return new StatsRow( | ||
| event.getStartTime(), | ||
| loaderId, | ||
| loader == null ? "null" : null, | ||
| name, | ||
| typeName(loader), | ||
| event.getLong("classLoaderData"), | ||
| event.getLong("classCount"), | ||
| event.getLong("chunkSize"), | ||
| event.getLong("blockSize"), | ||
| event.getLong("hiddenBlockSize")); | ||
| } |
| private RecordingData readRecording(Path recording) throws IOException { | ||
| Map<Long, LoaderMapping> mappingByLoaderId = new HashMap<Long, LoaderMapping>(); | ||
| Map<Long, StatsRow> latestStatsByLoaderId = new HashMap<Long, StatsRow>(); | ||
| RecordingFile file = new RecordingFile(recording); | ||
| try { | ||
| while (file.hasMoreEvents()) { | ||
| RecordedEvent event = file.readEvent(); | ||
| String eventName = event.getEventType().getName(); | ||
| if (MAPPING_EVENT_NAME.equals(eventName)) { | ||
| RecordedClass anchorClass = event.getClass("anchorClass"); | ||
| RecordedClassLoader loader = anchorClass == null ? null : anchorClass.getClassLoader(); | ||
| if (loader != null) { | ||
| mappingByLoaderId.put(loader.getId(), LoaderMapping.from(event)); | ||
| } | ||
| } else if (STATS_EVENT_NAME.equals(eventName)) { | ||
| StatsRow row = StatsRow.from(event); | ||
| StatsRow previous = latestStatsByLoaderId.get(row.loaderId); | ||
| if (previous == null || row.startTime.isAfter(previous.startTime)) { | ||
| latestStatsByLoaderId.put(row.loaderId, row); | ||
| } | ||
| } | ||
| } | ||
| } finally { | ||
| file.close(); | ||
| } | ||
| return new RecordingData(mappingByLoaderId, latestStatsByLoaderId.values()); | ||
| } |
5daa20e to
0d100c2
Compare
No description provided.