Возврат памяти из неосновных арен glibc malloc в операционную системуJAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Возврат памяти из неосновных арен glibc malloc в операционную систему

Сообщение Anonymous »

Я смоделировал сценарий на Java, который приводит к серьезной фрагментации памяти glibc.
  • Шаг первый: смоделировать многопоточную среду с 600 потоками.
  • Шаг второй: каждую секунду запускать два новых потока, которые читают большой файл через java.nio.channels.FileChannel.
  • Шаг третий: через 10 секунд автоматически завершиться и позвольте GC выполнить очистку.
Эта программа будет продолжать использовать память из неосновных областей арены до тех пор, пока не будет достигнуто максимальное количество арен, после чего использование памяти стабилизируется. Если вы затем остановите все потоки и оставите в спящем только основной поток, вы обнаружите, что метрика RSS процесса не уменьшается и остается высокой.
  • Согласно отчету NMT, вы обнаружите, что учет JVM не показывает использование такого большого количества памяти.
  • Из информации Linux pmap и smaps вы увидите, что каждый блок размером 64 МБ в неосновных регионах арены в физической памяти задето около 8 МБ. Если вы попытаетесь прочитать двоичные данные из памяти, вы обнаружите, что все это содержимое вашего файла.
Самая интересная часть — поведение параметров glibc. Когда я пытаюсь использовать переменную среды MALLOC_TRIM_THRESHOLD_, независимо от установленного мной значения, начинает происходить восстановление RSS. Поскольку DirectBuffer, используемый для чтения файла, имеет размер 8 МБ, я попробовал 52 428 800 (50 МБ) и 8 388 608 (8 МБ). Каждый раз RSS процесса освобождал около 16 МБ. Однажды я использовал GDB для проверки параметра Trim_threshold распределителя ptmalloc2 процесса, значение по умолчанию существует и составляет около 48 МБ. После настройки переменной MALLOC_TRIM_THRESHOLD_ поведение освобождения полностью меняется, особенно для памяти в неосновных регионах арены.
Кстати, версия JDK, которую я использовал, — 21-openjdk, а версия glibc — 2.39.
При каждом чтении файла процесс вызывает malloc для запроса памяти вне кучи, и после поток умирает, поток GC инициирует свободную операцию. Таким образом, в дампе JVM вы можете наблюдать, что все объекты, включая DirectBuffer вне кучи, уничтожаются по порядку.
Мои основные вопросы двоякие: почему этот параметр влияет на падение использования памяти? Кроме того, когда для него установлено значение 50 МБ, даже до теоретического достижения порога в 50 МБ, glibc также возвращает память из неосновной арены в ОС. Похоже, что после установки этого параметра свободное поведение glibc изменилось?
Добавить: это параметр времени выполнения JVM, и он простой.
-Xms6g -Xmx6g -XX:MaxDirectMemorySize=5g -XX:+AlwaysPreTouch -XX:+UseG1GC -Xlog:gc:file=gc.log:time,uptime,level,tags:filecount=5,filesize=100M*
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

public class FileRead1 {

private static volatile boolean stop = false;

public static void main(String[] args) throws InterruptedException {

liveThreadLoopWithoutMemory();

int count = 0;
while (true) {
if (count > (Integer.MAX_VALUE -1) ) {
break;
}
count++;
try {
for (int i = 0; i < 2; i++) {
newThread();
}

for (int i = 0; i < 5; i++) {
Thread.sleep(1000L);
}
} catch (InterruptedException e) {
}
}

stop = true;

while (true) {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000L);
}
}
}

private static void liveThreadLoopWithoutMemory() {
for (int i = 0; i < 600; i++) {
new Thread(() -> {
while (true) {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (stop) {
break;
}
}
}).start();
}
}

private static void newThread() {
Thread thread = new Thread(new Runnable() {

private final List strings = new ArrayList();

@Override
public void run() {

test(strings, "test.txt", "UTF-8", false);

strings.clear();
}
});
thread.setDaemon(true);
thread.start();
}

private static void test(List strings, String src, String encoding, boolean hasTitle) {
test1(strings, src, encoding, hasTitle);
}

private static void test1(List strings, String src, String encoding, boolean hasTitle) {
test2(strings, src, encoding, hasTitle);

}

private static void test2(List strings, String src, String encoding, boolean hasTitle) {
test3(strings, src, encoding, hasTitle);
}

private static void test3(List strings, String src, String encoding, boolean hasTitle) {
test4(strings, src, encoding, hasTitle);

}

private static void test4(List strings, String src, String encoding, boolean hasTitle) {
test5(strings, src, encoding, hasTitle);
}

private static void test5(List strings, String src, String encoding, boolean hasTitle) {
test6(strings, src, encoding, hasTitle);

}

private static void test6(List strings, String src, String encoding, boolean hasTitle) {
test7(strings, src, encoding, hasTitle);
}

private static void test7(List strings, String src, String encoding, boolean hasTitle) {
test8(strings, src, encoding, hasTitle);
}

private static void test8(List strings, String src, String encoding, boolean hasTitle) {
test9(strings, src, encoding, hasTitle);
}

private static void test9(List strings, String src, String encoding, boolean hasTitle) {
test10(strings, src, encoding, hasTitle);
}

private static void test10(List strings, String src, String encoding, boolean hasTitle) {
new TaskReadFile() {
@Override
public void process(String str) {
super.process(str + "swk");
}
}.readFile(strings, src, encoding, hasTitle);
}

static class TaskReadFile implements ReadFileInterface {

private final List stringList = new ArrayList();

@Override
public void process(String str) {
stringList.add(str + "123");
}

@Override
public void clear() {
stringList.clear();
}
}

interface ReadFileInterface {

void process(String str);

void clear();

default void readFile(List strings, String src, String encoding, boolean hasTitle) {
int readLimit = 8 * 1024 * 1024;
int startIndex = 0;

try (RandomAccessFile raf = new RandomAccessFile(src, "r");) {
FileChannel channel = raf.getChannel();
ByteBuffer rbuf = ByteBuffer.allocate(readLimit);

synchronized (rbuf) {
channel.position(startIndex);

byte[] temp = new byte[0];
int LF = 10;
long lineCount = 0;

while (channel.read(rbuf) != -1) {
int position = rbuf.position();
byte[] rbyte = new byte[position];
rbuf.flip();
rbuf.get(rbyte);
int startnum = 0;

for (int i = 0; i < rbyte.length; i++) {
if (rbyte == LF) {
if (channel.position() == startIndex) {
startnum = i + 1;
} else {
if (hasTitle && 0 == lineCount) {
startnum = i + 1;
lineCount++;
continue;
}
int lineLen = i - startnum + 1;
byte[] line = new byte[temp.length + lineLen];
System.arraycopy(temp, 0, line, 0, temp.length);
System.arraycopy(rbyte, startnum, line, temp.length, lineLen);
startnum = i + 1;
temp = new byte[0];
String str = trimEndingCRLF(line, encoding);
strings.add(str);
process(str);
}
}
}

if (startnum < rbyte.length) {
byte[] temp2 = new byte[temp.length + rbyte.length - startnum];
System.arraycopy(temp, 0, temp2, 0, temp.length);
System.arraycopy(rbyte, startnum, temp2, temp.length, rbyte.length - startnum);
temp = temp2;
}
rbuf.clear();
}
}

rbuf.clear();

try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

clear();

} catch (Exception e) {
throw new RuntimeException();
}
}

default String trimEndingCRLF(byte[] line, String encoding) throws UnsupportedEncodingException {
if (line.length != 0 && line[0] != 10) {
int lastIdx = line.length - 1;
if (line[lastIdx] != 10) {
++lastIdx;
} else if (line[lastIdx - 1] == 13) {
--lastIdx;
}

return new String(line, 0, lastIdx, encoding);
} else {
return "";
}
}
}
}

```java


Подробнее здесь: https://stackoverflow.com/questions/798 ... ing-system
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»