-
Notifications
You must be signed in to change notification settings - Fork 11
Prevent unbounded memory growth in long-running profilers #327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 28 commits
fd5f798
f008c7d
7dfb743
6281c46
c31e8b5
693d3bc
4f2c30d
8506bac
ae5cc44
0824cb4
46fac98
6e6f12a
cd0c279
2ab1d26
5b881a7
ebf7f17
89acf7e
fc0e2af
6e7729f
bee9080
caf6ed1
5ea19ad
f48420f
3ce7672
257d982
032d9a1
05d870c
33f6c5c
4bbfcfe
00da241
7ed1e7e
d2c7dee
937745c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
| * Copyright The async-profiler authors | ||||
| * Copyright 2025, Datadog, Inc. | ||||
| * Copyright 2025, 2026 Datadog, Inc. | ||||
| * SPDX-License-Identifier: Apache-2.0 | ||||
| */ | ||||
|
|
||||
|
|
@@ -43,10 +43,27 @@ | |||
| static const char *const SETTING_RING[] = {NULL, "kernel", "user", "any"}; | ||||
| static const char *const SETTING_CSTACK[] = {NULL, "no", "fp", "dwarf", "lbr"}; | ||||
|
|
||||
| static void deallocateLineNumberTable(void *ptr) {} | ||||
|
|
||||
| SharedLineNumberTable::~SharedLineNumberTable() { | ||||
| VM::jvmti()->Deallocate((unsigned char *)_ptr); | ||||
| // Always attempt to deallocate if we have a valid pointer | ||||
| // JVMTI spec requires that memory allocated by GetLineNumberTable | ||||
| // must be freed with Deallocate | ||||
| if (_ptr != nullptr) { | ||||
| jvmtiEnv *jvmti = VM::jvmti(); | ||||
| if (jvmti != nullptr) { | ||||
| jvmtiError err = jvmti->Deallocate((unsigned char *)_ptr); | ||||
| // If Deallocate fails, log it for debugging (this could indicate a JVM bug) | ||||
| // JVMTI_ERROR_ILLEGAL_ARGUMENT means the memory wasn't allocated by JVMTI | ||||
| // which would be a serious bug in GetLineNumberTable | ||||
| if (err != JVMTI_ERROR_NONE) { | ||||
| TEST_LOG("Unexpected error while deallocating linenumber table: %d", err); | ||||
| } else { | ||||
| // Successfully deallocated - decrement counter | ||||
| Counters::decrement(LINE_NUMBER_TABLES); | ||||
| } | ||||
| } else { | ||||
| TEST_LOG("WARNING: Cannot deallocate line number table - JVMTI is null"); | ||||
| } | ||||
|
jbachorik marked this conversation as resolved.
Outdated
|
||||
| } | ||||
| } | ||||
|
|
||||
| void Lookup::fillNativeMethodInfo(MethodInfo *mi, const char *name, | ||||
|
|
@@ -151,24 +168,44 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, | |||
| jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == 0) { | ||||
|
|
||||
| if (first_time) { | ||||
| jvmti->GetLineNumberTable(method, &line_number_table_size, | ||||
| jvmtiError line_table_error = jvmti->GetLineNumberTable(method, &line_number_table_size, | ||||
| &line_number_table); | ||||
| // Defensive: if GetLineNumberTable failed, clean up any potentially allocated memory | ||||
| // Some buggy JVMTI implementations might allocate despite returning an error | ||||
| if (line_table_error != JVMTI_ERROR_NONE) { | ||||
| if (line_number_table != nullptr) { | ||||
| // Try to deallocate to prevent leak from buggy JVM | ||||
| jvmti->Deallocate((unsigned char *)line_number_table); | ||||
| } | ||||
| line_number_table = nullptr; | ||||
| line_number_table_size = 0; | ||||
| } | ||||
| } | ||||
|
|
||||
| // Check if the frame is Thread.run or inherits from it | ||||
| if (strncmp(method_name, "run", 4) == 0 && | ||||
| strncmp(method_sig, "()V", 3) == 0) { | ||||
| jclass Thread_class = jni->FindClass("java/lang/Thread"); | ||||
| jmethodID equals = jni->GetMethodID(jni->FindClass("java/lang/Class"), | ||||
| "equals", "(Ljava/lang/Object;)Z"); | ||||
| jclass klass = method_class; | ||||
| do { | ||||
| entry = jni->CallBooleanMethod(Thread_class, equals, klass); | ||||
| jniExceptionCheck(jni); | ||||
| if (entry) { | ||||
| break; | ||||
| jclass Class_class = jni->FindClass("java/lang/Class"); | ||||
| if (Thread_class != nullptr && Class_class != nullptr) { | ||||
| jmethodID equals = jni->GetMethodID(Class_class, | ||||
| "equals", "(Ljava/lang/Object;)Z"); | ||||
| if (equals != nullptr) { | ||||
| jclass klass = method_class; | ||||
| do { | ||||
| entry = jni->CallBooleanMethod(Thread_class, equals, klass); | ||||
| if (jniExceptionCheck(jni)) { | ||||
| entry = false; | ||||
| break; | ||||
| } | ||||
| if (entry) { | ||||
| break; | ||||
| } | ||||
| } while ((klass = jni->GetSuperclass(klass)) != NULL); | ||||
| } | ||||
| } while ((klass = jni->GetSuperclass(klass)) != NULL); | ||||
| } | ||||
| // Clear any exceptions from the reflection calls above | ||||
| jniExceptionCheck(jni); | ||||
| } else if (strncmp(method_name, "main", 5) == 0 && | ||||
| strncmp(method_sig, "(Ljava/lang/String;)V", 21)) { | ||||
| // public static void main(String[] args) - 'public static' translates | ||||
|
|
@@ -250,6 +287,8 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, | |||
| if (line_number_table != nullptr) { | ||||
| mi->_line_number_table = std::make_shared<SharedLineNumberTable>( | ||||
| line_number_table_size, line_number_table); | ||||
| // Increment counter for tracking live line number tables | ||||
| Counters::increment(LINE_NUMBER_TABLES); | ||||
| } | ||||
|
|
||||
| // strings are null or came from JVMTI | ||||
|
|
@@ -483,7 +522,15 @@ off_t Recording::finishChunk(bool end_recording) { | |||
| } | ||||
|
|
||||
| void Recording::switchChunk(int fd) { | ||||
| size_t methods_before = _method_map.size(); | ||||
|
|
||||
|
||||
| size_t methods_before = _method_map.size(); |
Copilot
AI
Jan 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable aged_count is calculated but never used. It's incremented when a method's age increases, but this count isn't logged or used for any purpose. Consider either removing this unused variable or including it in the TEST_LOG output to provide diagnostic information about how many methods aged during this cleanup cycle.
Uh oh!
There was an error while loading. Please reload this page.