Speicherverlust in JNI mit AttachCurrentThread() und DetachCurrentThread()Java

Java-Forum
Anonymous
 Speicherverlust in JNI mit AttachCurrentThread() und DetachCurrentThread()

Post by Anonymous »

Ich habe eine Java-Anwendung, die eine native Funktion aufruft. Diese native Funktion enthält eine Schleife und startet in jeder Iteration 10 Threads und wartet auf deren Abschluss. Jeder Thread führt eine Reihe zufälliger Speicherzuweisungen durch, die dann im selben Thread wieder freigegeben werden. Nach jeder Iteration verfolge ich Speicherberichte von /proc/PID/status (VmRSS) und Felder von /proc/$PID/statm.
Wenn ich AttachCurrentThread() nicht in den Threads aufrufe, bleibt der Speicherverbrauch wie erwartet flach. Wenn ich jedoch AttachCurrentThread() am Anfang jedes Threads und DetachCurrentThread() am Ende aufrufe, scheint es einen Speicherverlust zu geben: Die für VmRSS und die „residente“ Spalte in /proc/$PID/statm gemeldeten Zahlen nehmen ständig zu.
Muss ich irgendetwas tun, um den Thread ordnungsgemäß von der JVM zu trennen? Behält die JVM aus irgendeinem Grund Speicher? Meine tatsächliche Anwendung ist viel größer als der hier bereitgestellte Beispielcode und hat irgendwann nicht mehr genügend Speicher, offenbar aufgrund des in diesem Test beobachteten Lecks.
Meine JVM ist:

Code: Select all

$ java -version
openjdk version "21.0.9" 2025-10-21
OpenJDK Runtime [url=viewtopic.php?t=25360]Environment[/url] (build 21.0.9+10-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 21.0.9+10-Ubuntu-124.04, mixed mode, sharing)
Mit dem folgenden Code habe ich Folgendes ausgeführt:

Code: Select all

make run
make run ARGS=--no-attach
um diesen Bericht zu erstellen:
Image


Java-Code:

Code: Select all

public final class Leak {
static { System.loadLibrary("library"); }
public static native void randomAllocations(boolean attach);

public static void main(String[] args) {
boolean attach = true;
for (String arg : args) {
if (arg.equals("--no-attach"))
attach = false;
else
throw new RuntimeException("Unknown argument " + arg);
}
randomAllocations(attach);
}
}
Nativer Code:

Code: Select all

#include 
#include 
#include 
#include 
#include
#include 

#define threads  10    /* Number of threads to use. */
#define count    10000 /* Number of memory allocations in each thread. */
#define repeats  60    /* Number of repetitions of main loop. */
static JavaVM *jvm = NULL; /* Java virtual machine (set in native function). */

/* Dump statistics read from /proc to CSV file. */
static void
dumpStats(FILE *out)
{
long long const pid = getpid();
char filename[64];
char line[256];
char *endptr;
FILE *f;
long VmRSS, VmHWM;
long size, resident, shared, text, lib, data, dt;

snprintf(filename, sizeof(filename), "/proc/%lld/status", pid);
if ( (f = fopen(filename, "r")) == NULL ) {
perror("fopen");
abort();
}
while (fgets(line, sizeof(line), f)) {
endptr = NULL;
if ( strncmp(line, "VmRSS:", 6) == 0 )
VmRSS = strtol(line + 6, &endptr, 0);
else if ( strncmp(line, "VmHWM:", 6) == 0 )
VmHWM = strtol(line + 6, &endptr, 0);
if ( endptr && (endptr[0] != ' ' || endptr[1] != 'k' || endptr[2] != 'B') ) {
perror("strtol");
abort();
}
}
fclose(f);

snprintf(filename, sizeof(filename), "/proc/%lld/statm", pid);
if ( (f = fopen(filename, "r")) == NULL ) {
perror("fopen");
abort();
}
if ( !fgets(line, sizeof(line), f) ) {
perror("fgets");
abort();
}
if ( sscanf(line, "%ld %ld %ld %ld %ld %ld %ld", &size, &resident,
&shared, &text, &lib, &data, &dt) != 7 )
{
perror("sscanf");
abort();
}
fclose(f);

fprintf(out, "%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld,%ld\n",
VmHWM, VmRSS, size, resident, shared, text, lib, data, dt);
fflush(out);
}
/* Per thread data. */
typedef struct {
unsigned int seed;   /* Random seed for this thread. */
jboolean     attach; /* Whether thread should attach to JVM. */
pthread_t    thread; /* Thread handle. */
} ThreadData;

static int sizes[1024]; /* Random sizes for memory blocks to allocate.  */

static void *thread(void *data) {
ThreadData const *d = data;
JNIEnv *javaenv = NULL;
jint r;
int ret;

if ( d->attach &&
(ret = (*jvm)->AttachCurrentThread(jvm, (void **)&javaenv, NULL)) )
{
fprintf(stderr, "#### ATTACH FAILED: %d\n", ret);
abort();
}

{ /* Simulate some random memory allocations. */
void **pointers = NULL;
pointers = malloc(sizeof(*pointers) * count);
if ( !pointers ) {
fprintf(stderr, "#### MALLOC failed\n");
abort();
}
for (jint i = 0; i < count; ++i) {
size_t bytes = (d->seed + i) % (sizeof(sizes) / sizeof(sizes[0]));
pointers[i] = malloc(bytes);
}
for (jint i = 0; i < count; ++i) {
free(pointers[i]);
}
free(pointers);
}

if ( d->attach &&
(ret = (*jvm)->DetachCurrentThread(jvm)) != 0 )
{
fprintf(stderr, "#### DETACH FAILED: %d\n", ret);
abort();
}

return NULL;
}

JNIEXPORT void JNICALL Java_Leak_randomAllocations(JNIEnv *javaenv, jclass clazz, jboolean attach) {
int ret;
int jret = 0;
FILE *f;
char filename[256];
unsigned u;

(void)javaenv;
(void)clazz;

snprintf(filename, sizeof(filename), "stats_%s_%lld.csv",
attach ? "attach" : "no_attach",
(long long)getpid());
if ( (f = fopen(filename, "w")) == NULL ) {
perror("fopen");
abort();
}
fprintf(f, "VmHWM,VmRSS,size,resident,shared,text,lib,data,dt\n");
dumpStats(f);

ret = (*javaenv)->GetJavaVM(javaenv, &jvm);
if ( ret ) {
fprintf(stderr, "#### FAILED TO GET JVM: %d\n", ret);
abort();
}

srand(0);
for (u = 0; u < sizeof(sizes) / sizeof(sizes[0]); ++u)
sizes[u] = rand() % (1024 * 1024 * 4);

for (jint r = 0; r < repeats; ++r) {
ThreadData *data = malloc(threads * sizeof(*data));
jint t = 0;

if ( !data ) {
fprintf(stderr, "#### BASIC ALLOCATION FAILED: %d\n", ret);
abort();
}
for (t = 0; t < threads; ++t) {
data[t].seed = t;
data[t].attach = attach;
if ( pthread_create(&data[t].thread, NULL, thread, &data[t]) ) {
fprintf(stderr, "#### THREAD CREATE FAILED\n");
abort();
}
}
for (t = 0; t < threads; ++t) {
pthread_join(data[t].thread, NULL);
}
free(data);
dumpStats(f);
}

fclose(f);
}
Makefile:

Code: Select all

.PHONY: all clean run

all: liblibrary.so Leak.class

clean:
rm -f liblibrary.so Leak.class

run: liblibrary.so Leak.class
java -cp . -Djava.library.path=. Leak $(ARGS)

liblibrary.so: library.c
$(CC) -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -shared -fPIC -o $@ $<

Leak.class: Leak.java
javac Leak.java

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post