Если я не вызываю AttachCurrentThread() в потоках, потребление памяти останется неизменным, как и ожидалось. Однако если я вызываю AttachCurrentThread() в начале каждого потока и DetachCurrentThread() в конце, оказывается, что существует утечка памяти: числа, сообщаемые для VmRSS и "резидентного" столбца в /proc/$PID/statm, продолжают увеличиваться.
Нужно ли мне что-то делать, чтобы правильно отсоединить поток от JVM? Сохраняет ли JVM память по какой-то причине? Мое фактическое приложение намного больше, чем приведенный здесь пример кода, и в конечном итоге ему не хватает памяти, по-видимому, из-за утечки, наблюдаемой в этом тесте.
Моя JVM:
$ java -version
openjdk version "21.0.9" 2025-10-21
OpenJDK Runtime Environment (build 21.0.9+10-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 21.0.9+10-Ubuntu-124.04, mixed mode, sharing)
Используя приведенный ниже код, я выполнил:
make run
make run ARGS=--no-attach
для создания этого отчета:

Код Java:
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);
}
}
Собственный код:
#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 = malloc(bytes);
}
for (jint i = 0; i < count; ++i) {
free(pointers);
}
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 = 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:
.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
Подробнее здесь: https://stackoverflow.com/questions/798 ... rentthread
Мобильная версия