Uh oh!
There was an error while loading. Please reload this page.
fix(codecache): make memoryUsage() accurate and live - #677
Conversation
There was a problem hiding this comment.
Pull request overview
Improves native-symbol memory accounting by making CodeCache::memoryUsage() (and the aggregated CodeCacheArray::memoryUsage()) recompute live, accurate heap usage for native symbol tables, which feeds the CODECACHE_NATIVE_SIZE_BYTES counter.
Changes:
- Replaces the old approximation in
CodeCache::memoryUsage()with a live recompute that accounts for the full blob array, variable-length symbol-name allocations, DWARF unwind table, build-id string, and the cache’s own name. - Removes
CodeCacheArray’s cached_used_memoryand sums per-librarymemoryUsage()at call time to reflect post-registration growth. - Adds
codeCache_utcoverage validating that memory usage scales with symbol-name length and counts the fullCodeBlobarray size.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| ddprof-lib/src/test/cpp/codeCache_ut.cpp | Adds focused unit tests for memoryUsage() accuracy and growth behavior. |
| ddprof-lib/src/main/cpp/codeCache.h | Introduces NativeFunc::allocSize(), updates CodeCache::memoryUsage() API, and makes CodeCacheArray::memoryUsage() sum live values. |
| ddprof-lib/src/main/cpp/codeCache.cpp | Implements the new accurate, live CodeCache::memoryUsage() calculation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
CI Test ResultsRun:#29932410242 | Commit:
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Summary: Total: 32 | Passed: 32 | Failed: 0 Updated: 2026-07-22 16:00:22 UTC |
Benchmark Results (commit cf42bb1)Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/125921385 Commit:
|
| Benchmark | JDK | Latest | Dev | Δ (dev vs latest) | Issues L/D |
|---|---|---|---|---|---|
| akka-uct | 21 | ✅ 10280 ms (21 iters) | ✅ 10270 ms (21 iters) | ≈ -0.1% (±11.8%) | — / — |
| akka-uct | 25 | ✅ 8817 ms (24 iters) | ✅ 8918 ms (24 iters) | ≈ +1.1% (±10.1%) | — / — |
| finagle-chirper | 21 | ✅ 6040 ms (33 iters) | ✅ 5974 ms (33 iters) | ≈ -1.1% (±25.3%) | |
| finagle-chirper | 25 | ✅ 5505 ms (36 iters) | ✅ 5373 ms (36 iters) | ≈ -2.4% (±24.2%) | |
| fj-kmeans | 21 | ✅ 2643 ms (71 iters) | ✅ 2697 ms (70 iters) | ≈ +2% (±2.6%) | — / — |
| fj-kmeans | 25 | ✅ 2808 ms (66 iters) | ✅ 2823 ms (66 iters) | ≈ +0.5% (±2.6%) | — / — |
| future-genetic | 21 | ✅ 2042 ms (90 iters) | ✅ 2078 ms (89 iters) | ≈ +1.8% (±2.5%) | — / — |
| future-genetic | 25 | ✅ 2088 ms (89 iters) | ✅ 2006 ms (93 iters) | 🟢 -3.9% | — / — |
| naive-bayes | 21 | ✅ 1288 ms (133 iters) | ✅ 1253 ms (137 iters) | ≈ -2.7% (±31.8%) | — / — |
| naive-bayes | 25 | ✅ 1058 ms (162 iters) | ✅ 991 ms (172 iters) | ≈ -6.3% (±30.6%) | — / — |
| reactors | 21 | ✅ 16460 ms (15 iters) | ✅ 16646 ms (15 iters) | ≈ +1.1% (±7.5%) | — / — |
| reactors | 25 | ✅ 18515 ms (15 iters) | ✅ 18521 ms (15 iters) | ≈ +0% (±4.2%) | — / — |
Internal counter details (ddprof)
ddprof internal counters, latest / dev (✅ = 0, · = unavailable):
| Benchmark | JDK | Dropped rec | Dropped jvmti | Dropped trace | Skipped WC | AGCT fail | Unwind fail |
|---|---|---|---|---|---|---|---|
| akka-uct | 21 | ✅ / ✅ | ✅ / ✅ | 4 / 1 | 1950 / 1959 | ✅ / ✅ | ✅ / ✅ |
| akka-uct | 25 | ✅ / ✅ | ✅ / ✅ | ✅ / 2 | 2209 / 2042 | ✅ / ✅ | ✅ / ✅ |
| finagle-chirper | 21 | ✅ / ✅ | ✅ / ✅ | 2 / 1 | 8695 / 8520 | ✅ / ✅ | ✅ / ✅ |
| finagle-chirper | 25 | ✅ / ✅ | ✅ / ✅ | ✅ / 1 | 8170 / 8562 | ✅ / ✅ | ✅ / ✅ |
| fj-kmeans | 21 | ✅ / ✅ | ✅ / ✅ | ✅ / 1 | 1278 / 1297 | ✅ / ✅ | ✅ / ✅ |
| fj-kmeans | 25 | ✅ / ✅ | ✅ / ✅ | 1 / 1 | 1258 / 1270 | ✅ / ✅ | ✅ / ✅ |
| future-genetic | 21 | ✅ / ✅ | ✅ / ✅ | ✅ / 2 | 2890 / 2997 | ✅ / ✅ | ✅ / ✅ |
| future-genetic | 25 | ✅ / ✅ | ✅ / ✅ | 1 / ✅ | 2919 / 2906 | ✅ / ✅ | ✅ / ✅ |
| naive-bayes | 21 | ✅ / ✅ | ✅ / ✅ | 3 / 1 | 3530 / 3526 | ✅ / ✅ | ✅ / ✅ |
| naive-bayes | 25 | ✅ / ✅ | ✅ / ✅ | 2 / 7 | 3483 / 3505 | ✅ / ✅ | ✅ / ✅ |
| reactors | 21 | ✅ / ✅ | ✅ / ✅ | ✅ / ✅ | 1692 / 1796 | ✅ / ✅ | ✅ / ✅ |
| reactors | 25 | ✅ / ✅ | ✅ / ✅ | ✅ / 1 | 1904 / 1851 | ✅ / ✅ | ✅ / ✅ |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:8d1c750d9f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Benchmark Results (commit 8d1c750)Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/125932438 Commit:
|
| Benchmark | JDK | Latest | Dev | Δ (dev vs latest) | Issues L/D |
|---|---|---|---|---|---|
| akka-uct | 21 | ✅ 10391 ms (21 iters) | ✅ 10300 ms (21 iters) | ≈ -0.9% (±11.5%) | — / — |
| akka-uct | 25 | ✅ 8954 ms (24 iters) | ✅ 8757 ms (24 iters) | ≈ -2.2% (±10.1%) | — / — |
| finagle-chirper | 21 | ✅ 5979 ms (33 iters) | ✅ 5961 ms (33 iters) | ≈ -0.3% (±25.1%) | |
| finagle-chirper | 25 | ✅ 5511 ms (36 iters) | ✅ 5514 ms (36 iters) | ≈ +0.1% (±25%) | |
| fj-kmeans | 21 | ✅ 2701 ms (69 iters) | ✅ 2820 ms (66 iters) | 🔴 +4.4% | — / — |
| fj-kmeans | 25 | ✅ 2837 ms (66 iters) | ✅ 2830 ms (66 iters) | ≈ -0.2% (±2.6%) | — / — |
| future-genetic | 21 | ✅ 2099 ms (88 iters) | ✅ 2071 ms (90 iters) | ≈ -1.3% (±2.7%) | — / — |
| future-genetic | 25 | ✅ 2079 ms (89 iters) | ✅ 2070 ms (90 iters) | ≈ -0.4% (±2.7%) | — / — |
| naive-bayes | 21 | ✅ 1288 ms (133 iters) | ✅ 1305 ms (131 iters) | ≈ +1.3% (±32.9%) | — / — |
| naive-bayes | 25 | ✅ 1027 ms (166 iters) | ✅ 1027 ms (166 iters) | ≈ 0% (±31.8%) | — / — |
| reactors | 21 | ✅ 16180 ms (15 iters) | ✅ 17360 ms (15 iters) | 🔴 +7.3% | — / — |
| reactors | 25 | ✅ 18388 ms (15 iters) | ✅ 18296 ms (15 iters) | ≈ -0.5% (±4.7%) | — / — |
Internal counter details (ddprof)
ddprof internal counters, latest / dev (✅ = 0, · = unavailable):
| Benchmark | JDK | Dropped rec | Dropped jvmti | Dropped trace | Skipped WC | AGCT fail | Unwind fail |
|---|---|---|---|---|---|---|---|
| akka-uct | 21 | ✅ / ✅ | ✅ / ✅ | 3 / 1 | 2029 / 1937 | ✅ / ✅ | ✅ / ✅ |
| akka-uct | 25 | ✅ / ✅ | ✅ / ✅ | 3 / 3 | 2274 / 2377 | ✅ / ✅ | ✅ / ✅ |
| finagle-chirper | 21 | ✅ / ✅ | ✅ / ✅ | 2 / 5 | 8644 / 8323 | ✅ / ✅ | ✅ / ✅ |
| finagle-chirper | 25 | ✅ / ✅ | ✅ / ✅ | ✅ / 2 | 8369 / 8619 | ✅ / ✅ | ✅ / ✅ |
| fj-kmeans | 21 | ✅ / ✅ | ✅ / ✅ | ✅ / ✅ | ✅ / ✅ | ✅ / ✅ | ✅ / ✅ |
| fj-kmeans | 25 | ✅ / ✅ | ✅ / ✅ | 3 / 4 | 1282 / 1263 | ✅ / ✅ | ✅ / ✅ |
| future-genetic | 21 | ✅ / ✅ | ✅ / ✅ | 3 / ✅ | 2890 / 3007 | ✅ / ✅ | ✅ / ✅ |
| future-genetic | 25 | ✅ / ✅ | ✅ / ✅ | 1 / 1 | 2938 / 2923 | ✅ / ✅ | ✅ / ✅ |
| naive-bayes | 21 | ✅ / ✅ | ✅ / ✅ | 6 / 3 | 3490 / 3481 | ✅ / ✅ | ✅ / ✅ |
| naive-bayes | 25 | ✅ / ✅ | ✅ / ✅ | 6 / 4 | 3496 / 3474 | ✅ / ✅ | ✅ / ✅ |
| reactors | 21 | ✅ / ✅ | ✅ / ✅ | ✅ / 1 | 1697 / 1771 | ✅ / ✅ | ✅ / ✅ |
| reactors | 25 | ✅ / ✅ | ✅ / ✅ | 1 / 2 | 1900 / 1864 | ✅ / ✅ | ✅ / ✅ |
rkennke
commented
Jul 21, 2026
Thanks for the reviews — addressed in cf512dd: Build-id data race (codex P2 / Copilot) — Good catch.
Use Re: the benchmark outliers ( |
Uh oh!
There was an error while loading. Please reload this page.
Benchmark Results (commit cf512dd)Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/125969722 Commit: ✅ Within expected boundariesNo significant runtime deltas (all within run-to-run noise) and no internal-counter outliers. Runtime details (per benchmark × JDK)
Internal counter details (ddprof)ddprof internal counters, latest / dev (✅ = 0, · = unavailable):
|
rkennke
commented
Jul 22, 2026
Re: the PR-description / code mismatch on build-id (Copilot @ codeCache.cpp:169) — went with option (b): updated the PR description so it no longer lists build-id as counted, and added an explicit note that it's intentionally excluded to avoid the Also noting the benchmark run for cf512dd came back "within expected boundaries — all within run-to-run noise," which lines up with the earlier assessment that the outliers on prior commits were noise. |
This comment has been minimized.
This comment has been minimized.
zhengyu123
left a comment
There was a problem hiding this comment.
Looks good overall.
Unrelated to this PR:
Doubled cost at the call site — profiler.cpp:1739-1741
Counters::set(CODECACHE_NATIVE_SIZE_BYTES, native_libs.memoryUsage());
Counters::set(CODECACHE_RUNTIME_STUBS_SIZE_BYTES,
native_libs.memoryUsage());
rkennke
commented
Jul 22, 2026
Thanks for the review! On the unrelated note ( Separately, while there: |
rkennke
commented
Jul 22, 2026
Thanks for the thorough Sphinx pass — all seven addressed ( MEDIUM
LOW
Full |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
ddprof-lib/src/main/cpp/codeCache.cpp:179
- The build-id exclusion rationale is inaccurate:
Libraries::updateBuildIds()only callssetBuildId()once per library (see_build_id_processed+hasBuildId()), so it does not "free and replace" build-ids repeatedly. If the concern is unsynchronized publication to dump-time readers, clarify that instead (otherwise this comment suggests a different concurrency hazard than what the code actually does).
// library refresher (Libraries::updateBuildIds) frees and replaces _build_id
// on already-published caches under _build_id_lock, which dump does not hold,
// so dereferencing it here would race. It is negligible (~tens of bytes per
// library) next to the symbol tables, so excluding it costs no meaningful
// accuracy while keeping this read lock-free.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
rkennke
commented
Jul 22, 2026
Good catch — fixed in the latest commit. My comments named |
The comments on the lock-free memoryUsage() read named Libraries::_runtime_stubs (a vestigial, unused member) as the example of a continuously-mutated unpublished cache. The actual one is JitCodeCache::_runtime_stubs, mutated under JitCodeCache::_stubs_lock (add() under lock(), findRuntimeStub() under lockShared()). Fix the references. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9e64ead to
90523b9CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Addressing review feedback on visibility: _published was a plain bool read/written across threads (set by CodeCacheArray::add(), read by the add()/expand()/setDwarfTable() asserts). Make it std::atomic<bool> with a release store in markPublished() and acquire loads in the asserts. The store is sequenced before add()'s RELEASE publish of the pointer, so any thread that reaches a published cache observes _published == true — now without a formal data race and TSan-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rkennke
commented
Jul 22, 2026
Good catch — made |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
ddprof-lib/src/main/cpp/codeCache.cpp:25
- NativeFunc::allocSize() returns 0 for nullptr, but NativeFunc::create() now unconditionally calls allocSize(name) and then does aligned_alloc/strcpy. If a null name ever reaches create(), this becomes undefined behavior (size==0 allocation and strcpy(nullptr)). Add an assertion (or explicit null handling) so the contract is enforced locally.
char *NativeFunc::create(const char *name, short lib_index) {
size_t size = allocSize(name);
NativeFunc *f = (NativeFunc *)aligned_alloc(sizeof(NativeFunc*), size);
f->_lib_index = lib_index;
f->_mark = 0;
// cppcheck-suppress memleak
return strcpy(f->_name, name);
}
copyFrom() memcpy'd the CodeBlob array, which copied each blob's _name *pointer* — so a copied CodeCache shared name allocations with the original and both destructors freed them (double-free / use-after-free). Give the copy its own name strings via NativeFunc::create(), matching how build-id and the DWARF table are already deep-copied. (CodeCache is only ever used by pointer today, so the copy path is currently unexercised — this hardens it rather than fixing an observed crash.) Adds CopyIsDeepAndDoesNotDoubleFree, which copies a populated cache and destroys both, aborting on the old shallow-copy behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rkennke
commented
Jul 22, 2026
Fixed in the latest commit — |
…ootprint" CodeCache::memoryUsage() is a capacity/count-based estimate on main, so "live heap footprint" overstated it. Reword to "approximate heap usage" and note #677 makes memoryUsage() exact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Benchmark Results (commit 9800ad3)Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/126276045 Commit:
|
| Benchmark | JDK | Latest | Dev | Δ (dev vs latest) | Issues L/D |
|---|---|---|---|---|---|
| akka-uct | 21 | ✅ 10161 ms (21 iters) | ✅ 10263 ms (21 iters) | ≈ +1% (±12%) | — / — |
| akka-uct | 25 | ✅ 8973 ms (24 iters) | ✅ 8882 ms (24 iters) | ≈ -1% (±9.7%) | — / — |
| finagle-chirper | 21 | ✅ 6032 ms (33 iters) | ✅ 5973 ms (33 iters) | ≈ -1% (±26%) | |
| finagle-chirper | 25 | ✅ 5508 ms (36 iters) | ✅ 5482 ms (36 iters) | ≈ -0.5% (±23.8%) | |
| fj-kmeans | 21 | ✅ 2709 ms (70 iters) | ✅ 2763 ms (68 iters) | ≈ +2% (±2.7%) | — / — |
| fj-kmeans | 25 | ✅ 2860 ms (66 iters) | ✅ 2828 ms (66 iters) | ≈ -1.1% (±2.6%) | — / — |
| future-genetic | 21 | ✅ 2094 ms (88 iters) | ✅ 2098 ms (89 iters) | ≈ +0.2% (±2.6%) | — / — |
| future-genetic | 25 | ✅ 2013 ms (92 iters) | ✅ 2127 ms (87 iters) | 🔴 +5.7% | — / — |
| naive-bayes | 21 | ✅ 1259 ms (136 iters) | ✅ 1253 ms (137 iters) | ≈ -0.5% (±32.3%) | — / — |
| naive-bayes | 25 | ✅ 1020 ms (168 iters) | ✅ 1016 ms (169 iters) | ≈ -0.4% (±31.4%) | — / — |
| reactors | 21 | ✅ 16733 ms (15 iters) | ✅ 16874 ms (15 iters) | ≈ +0.8% (±6.1%) | — / — |
| reactors | 25 | ✅ 18576 ms (15 iters) | ✅ 18322 ms (15 iters) | ≈ -1.4% (±5.7%) | — / — |
Internal counter details (ddprof)
ddprof internal counters, latest / dev (✅ = 0, · = unavailable):
| Benchmark | JDK | Dropped rec | Dropped jvmti | Dropped trace | Skipped WC | AGCT fail | Unwind fail |
|---|---|---|---|---|---|---|---|
| akka-uct | 21 | ✅ / ✅ | ✅ / ✅ | ✅ / 2 | 2064 / 1929 | ✅ / ✅ | ✅ / ✅ |
| akka-uct | 25 | ✅ / ✅ | ✅ / ✅ | 2 / ✅ | 2232 / 2122 | ✅ / ✅ | ✅ / ✅ |
| finagle-chirper | 21 | ✅ / ✅ | ✅ / ✅ | 1 / 3 | 8913 / 8489 | ✅ / ✅ | ✅ / ✅ |
| finagle-chirper | 25 | ✅ / ✅ | ✅ / ✅ | 1 / 1 | 8849 / 8204 | ✅ / ✅ | ✅ / ✅ |
| fj-kmeans | 21 | ✅ / ✅ | ✅ / ✅ | 5 / ✅ | 1278 / 1253 | ✅ / ✅ | ✅ / ✅ |
| fj-kmeans | 25 | ✅ / ✅ | ✅ / ✅ | 1 / 3 | 1306 / 1253 | ✅ / ✅ | ✅ / ✅ |
| future-genetic | 21 | ✅ / ✅ | ✅ / ✅ | 1 / ✅ | 2939 / 3027 | ✅ / ✅ | ✅ / ✅ |
| future-genetic | 25 | ✅ / ✅ | ✅ / ✅ | 1 / ✅ | 2906 / 2868 | ✅ / ✅ | ✅ / ✅ |
| naive-bayes | 21 | ✅ / ✅ | ✅ / ✅ | 2 / 4 | 3536 / 3548 | ✅ / ✅ | ✅ / ✅ |
| naive-bayes | 25 | ✅ / ✅ | ✅ / ✅ | 3 / 3 | 3471 / 3479 | ✅ / ✅ | ✅ / ✅ |
| reactors | 21 | ✅ / ✅ | ✅ / ✅ | 1 / 2 | 1668 / 1709 | ✅ / ✅ | ✅ / ✅ |
| reactors | 25 | ✅ / ✅ | ✅ / ✅ | 1 / 1 | 1904 / 1846 | ✅ / ✅ | ✅ / ✅ |
Reliability & Chaos Results✅ All reliability & chaos checks passed Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/126276036 |
Uh oh!
There was an error while loading. Please reload this page.
…ootprint" CodeCache::memoryUsage() is a capacity/count-based estimate on main, so "live heap footprint" overstated it. Reword to "approximate heap usage" and note #677 makes memoryUsage() exact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ootprint" CodeCache::memoryUsage() is a capacity/count-based estimate on main, so "live heap footprint" overstated it. Reword to "approximate heap usage" and note #677 makes memoryUsage() exact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(codecache): report the real runtime-stubs cache size CODECACHE_RUNTIME_STUBS_SIZE_BYTES was set to native_libs.memoryUsage() — the same value as CODECACHE_NATIVE_SIZE_BYTES — a copy-paste since the counter was introduced (d0b0d1e). It never reflected the runtime-stubs cache. The runtime stubs live in JitCodeCache::_runtime_stubs (populated by DynamicCodeGenerated as the JVM emits stubs), a different cache from the native libraries. Add JitCodeCache::runtimeStubsMemoryUsage(), which reads that cache's size under its shared _stubs_lock (the cache is mutated concurrently by the JVMTI callback), and set the counter from it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(codecache): word runtime-stubs usage as approximate, not "live footprint" CodeCache::memoryUsage() is a capacity/count-based estimate on main, so "live heap footprint" overstated it. Reword to "approximate heap usage" and note #677 makes memoryUsage() exact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(codecache): drop stale "approximate/#677" note now that memoryUsage is exact With #677 merged, CodeCache::memoryUsage() is accurate on main, so the runtime-stubs accessor comment describing it as an approximation (pending #677) is no longer true. Describe current behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(codecache): route runtime-stubs usage through JVMSupport The runtime-stubs memory counter called the HotSpot-specific JitCodeCache::runtimeStubsMemoryUsage() directly from shared profiler.cpp, with no VM guard. On J9/Zing that reports a fixed near-zero baseline with nothing in the code marking the value as meaningless there. Route it through the same JVMSupport -> HotspotSupport abstraction the sibling isJitCode() accessor uses: JVMSupport::runtimeStubsMemoryUsage() dispatches to HotspotSupport (-> JitCodeCache) on HotSpot and returns 0 elsewhere. profiler.cpp no longer references JitCodeCache directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(codecache): make memoryUsage() accurate and live CodeCache::memoryUsage() drove the CODECACHE_NATIVE_SIZE_BYTES counter but both under-counted and went stale: - The blob array was counted as _capacity * sizeof(CodeBlob*) — a pointer's worth — while the array holds CodeBlob (three pointers), a ~3x undercount. - Symbol name strings were approximated as _count * sizeof(NativeFunc), a fixed 4 bytes each, ignoring the name length that dominates the actual allocation. - The DWARF unwind table and build-id string were not counted at all. - CodeCacheArray cached the sum at add() time, so it never reflected a library's later symbol growth. Recompute both accurately on demand instead. CodeCache::memoryUsage() now sums the full blob array, each symbol's real name-string allocation (via new NativeFunc::allocSize()), its own name, the DWARF table, and the build-id. CodeCacheArray::memoryUsage() sums the live per-library values (the array is append-only, so iterating the published prefix is safe alongside concurrent adds); the stale _used_memory cache is removed. memoryUsage() is only called at dump time, so the O(symbols) recompute is not on any hot path. Tests: new codeCache_ut asserts usage tracks per-symbol name length, that a longer name costs more than a shorter one, and that the blob array is counted at full CodeBlob size rather than a pointer's. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(codecache): describe current memoryUsage(), drop old-formula references Comments explained what the previous formula did wrong rather than what the code now counts. Reword to describe the current behavior only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(codecache): don't read build-id in memoryUsage() (data race); review nits Address PR review feedback: - Drop the build-id from memoryUsage(). The background library refresher (Libraries::updateBuildIds) calls setBuildId() — which frees and replaces _build_id — on already-published caches under _build_id_lock, which dump does not hold. The strlen(_build_id) added here could race that free/replace (use-after-free / torn read). Build-id is negligible (~tens of bytes per library) next to the symbol tables, so excluding it keeps the read lock-free at no meaningful accuracy cost. The blob-name loop and dwarf-length read are safe: blobs are fixed once a library is published (same read the symbolication fast path does) and the dwarf read touches only the length scalar. - Mark CodeCache::memoryUsage() const (it does not mutate state). - Test: use reinterpret_cast for the integer-to-pointer args and drop the unused <cstring> include. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * harden(codecache): enforce/scope the published-immutability invariant Address the Sphinx review on memoryUsage()'s lock-free read: - Make memoryUsage()'s lock-free safety an enforced precondition. Add a _published flag, set by CodeCacheArray::add() before the pointer is published, and assert !_published in add()/expand()/setDwarfTable() — the mutators that touch the _blobs array and _dwarf_table_length that memoryUsage() reads from the dump thread. Standalone caches that are never registered (e.g. Libraries::_runtime_stubs, which is continuously mutated under its own lock) stay unpublished and may be mutated freely. - Scope the safety comment accordingly: the lock-free read is valid only for array-registered caches, and must not be used on _runtime_stubs without that cache's lock. - NativeFunc::create() now calls allocSize() instead of recomputing the size formula, so memoryUsage() and the allocation can't drift. - Reword CodeCacheArray::memoryUsage() to drop the incorrect "reflects symbols added after registration" claim (adds happen pre-publish); it reflects each library's fully-populated state at dump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(codecache): cover array aggregation, DWARF term, exact blob-array size Address the Sphinx review's test-coverage gaps: - ArrayMemoryUsageSumsLibraries: exercises CodeCacheArray::memoryUsage() (the aggregation production consumes via Profiler::dump()), which no test covered — guards the loop and accumulation. - MemoryUsageCountsDwarfTable: populates a DWARF table via setDwarfTable() so the length * sizeof(FrameDesc) term is non-zero and a regression in it is detectable. - MemoryUsageCountsFullBlobArray: tightened from EXPECT_GT to an exact EXPECT_EQ (blob array + own name) so over-counting would fail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(codecache): reference JitCodeCache::_runtime_stubs, not Libraries:: The comments on the lock-free memoryUsage() read named Libraries::_runtime_stubs (a vestigial, unused member) as the example of a continuously-mutated unpublished cache. The actual one is JitCodeCache::_runtime_stubs, mutated under JitCodeCache::_stubs_lock (add() under lock(), findRuntimeStub() under lockShared()). Fix the references. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * harden(codecache): make _published atomic (release/acquire) Addressing review feedback on visibility: _published was a plain bool read/written across threads (set by CodeCacheArray::add(), read by the add()/expand()/setDwarfTable() asserts). Make it std::atomic<bool> with a release store in markPublished() and acquire loads in the asserts. The store is sequenced before add()'s RELEASE publish of the pointer, so any thread that reaches a published cache observes _published == true — now without a formal data race and TSan-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(codecache): deep-copy blob name strings in copyFrom() copyFrom() memcpy'd the CodeBlob array, which copied each blob's _name *pointer* — so a copied CodeCache shared name allocations with the original and both destructors freed them (double-free / use-after-free). Give the copy its own name strings via NativeFunc::create(), matching how build-id and the DWARF table are already deep-copied. (CodeCache is only ever used by pointer today, so the copy path is currently unexercised — this hardens it rather than fixing an observed crash.) Adds CopyIsDeepAndDoesNotDoubleFree, which copies a populated cache and destroys both, aborting on the old shallow-copy behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Makes
CodeCache::memoryUsage()— which drives theCODECACHE_NATIVE_SIZE_BYTEScounter — report the profiler's actual native-symbol memory, instead of a stale, heavily-undercounted approximation.(
CodeCachehere is the profiler's per-native-library symbol table used to symbolicate native frames — async-profiler's naming, not the JVM's JIT code cache.)Why
The old formula
_capacity * sizeof(CodeBlob*) + _count * sizeof(NativeFunc)was wrong in several ways:sizeof(CodeBlob*)(a pointer, 8 B) where the array actually holdsCodeBlob(three pointers, 24 B).sizeof(NativeFunc)(4 B) per symbol, ignoring the name-string length that dominates each allocation (aligned_alloc(sizeof(NativeFunc) + 1 + strlen(name)))._dwarf_table, can be large).CodeCacheArraycached the sum atadd()time via_used_memory, so it never reflected a library's later symbol growth.How
CodeCache::memoryUsage()recomputes accurately: full blob array (sizeof(CodeBlob)), each symbol's real name allocation (newNativeFunc::allocSize()), this cache's own name, and the DWARF table.Libraries::updateBuildIds) frees and replaces_build_idon already-published caches under_build_id_lock, which dump does not hold, so dereferencing it here (strlen) would race → use-after-free. It is negligible (~tens of bytes per library) next to the symbol tables, so excluding it keeps this read lock-free at no meaningful accuracy cost.CodeCacheArray::memoryUsage()sums the live per-library values instead of a cached total. The array is append-only, so iterating the published prefix is safe alongside concurrentadd()s. The now-dead_used_memoryfield and itsadd()-time accumulation are removed.CodeCacheArray(their_blobs/DWARF fields are fixed once published). This is now enforced: a_publishedflag is set byCodeCacheArray::add(), andadd()/expand()/setDwarfTable()assert!_published(debug builds). Standalone, continuously-mutated caches likeLibraries::_runtime_stubsare never published and are exempt.NativeFunc::create()callsNativeFunc::allocSize()so the allocation size andmemoryUsage()'s accounting share one formula and can't drift.memoryUsage()is only called at dump time, so theO(symbols)recompute is off any hot path.Testing
codeCache_ut(5 tests): per-symbol name length tracked exactly; longer name costs more; empty-cache size is exactly blob-array + own-name (EXPECT_EQ); DWARF table term counted; andCodeCacheArray::memoryUsage()sums the per-library values (the aggregationProfiler::dump()actually consumes).:ddprof-lib:gtestDebugsuite green — 354 tests, 0 failures (rebased on latestmain).Note
Independent of the native-memory accounting work in #669; that PR's
NM_NATIVE_SYMBOLSgauge mirrors thismemoryUsage(), so it benefits automatically once both land, but neither depends on the other to build.🤖 Generated with Claude Code