Правда о выделении памяти в java

Zusk

Правду предстоит выяснить.
Возьмем простейшую программу, создающую длинный однонаправленный список:

public class NumberCounter {

public static void main (String[] args) {
int n = 10*1000*1000;
long dm, m = 0;

for (int i = 1; i <= n; i++) {
Number p = new Number;
p.number = i;
p.next = null;

if (e != null) {
e.next = p;
}
e = p;

if (s == null) {
s = p;
}

if (i % 100000 == 0) {
dm = free;
System.out.println("free="+(dm/1024)+"Kb"+" i="+i);
m += dm;
}
}

m += free;
System.out.println("total created="+counter+" total free="+(m/1024)+"Kb");

pause(1000*1000);
}

public static long free {
long m0 = Runtime.getRuntime.freeMemory;
Runtime.getRuntime.gc;
return Runtime.getRuntime.freeMemory-m0;
}

public static void pause (int msec) {
try {Thread.sleep(msec);} catch (java.lang.InterruptedException e) {}
}

static class Number {
Number {
counter++;
}
// public int data1;
// public int data2;
public int number;
public Number next;
}

public static int counter = 0;
private static Number s = null, e = null;
}


Концовка запуска:
free=3006Kb i=9900000
free=2903Kb i=10000000
total created=10000000 total free=190085Kb
Смотрим в Process Viewer:
MemUsage=182Mb VMSize=227Mb
Это означает:
1) В ходе создания списка из 10 млн. элементов java выделила
как минимум 190Mb где-то на свои внутренние нужды, и эту память
потом успешно освободил gc.
2) Один экземпляр класса Number должен занимать 8байт,
все объекты – 80Mb, на деле имеем в разы больше.
Допустим яве надо хранить что-то еще и Number занимает больше 8байт.
тогда добавляем в него еще два поля типа int

static class Number {
Number {
counter++;
}
public int data1;
public int data2;
public int number;
public Number next;
}

в расчете что размер используемой памяти увеличится ровно на 80Mb.
На это раз получаем:
MemUsage=281Mb VMSize=442Mb
Видим, что разница на десятки Mb больше чем надо.
Ваши комментарии?

Hastya

Твоя ошибка в этом:
2) Один экземпляр класса Number должен занимать 8байт,

Zusk

Дальше я вроде пишу: "допустим не 8байт ..." Опять приходим к противоречию.
Проблема в том, что это только вершина айсберга.
В реальных приложениях все горазда хуже, что заставило рассмотреть
простейший пример и даже на нем обнаружить большую несостыковку.

klyv

тут где-то был , в котором обсуждалось, что память свободную и занятую трудно считать. в Java это тоже трудно.

Dasar

Ваши комментарии?
во-первых, Number занимает больше чем 8байт.
во-вторых, при добавлении двух int-ов на самом деле может добавиться или больше, или меньше, из-за выравнивания.

danilov

Жава выделяет несколько больше памяти, чем требуется, но не больше, чем задано в Xmx.
Это обусловлено в том числе тем, что некоторые gc гораздо лучше работают при наличии свободной памяти.
Например, в этом случае коллектору young/old generations совсем не нужно вызывать full gc, который занимает ну очень много времени.
Уверен, если ты выделишь ровно столько памяти, сколько требуется (ну, плюс ещё мегабайт 50 то со скрипом твоя программа пройдёт, но выполняться будет медленней. Вообще политика брать столько памяти, сколько дают, она в целом верная....
Ps +1 к проблеме оценке памяти в яве. Да и вызов System.gc вовсе не гарантирует вызова коллектора.

Hastya

Дальше я вроде пишу: "допустим не 8байт ..." Опять приходим к противоречию.
Так откуда ты знаешь, какими блоками выделяется память? Вполне возможен вариант, когда куча увеличивается скачками, например, по 64М.

Zusk

Да, плавное увеличение размера объекта Number показало,
что память увеличивается скачками по ~89Mb, что объясняет
основную массу забираемой памяти…
Но это к сожалению не объясняет изначальной проблемы,
присутствующей в реальной аппликации, где размер ява-машины
в десятки раз превышает все разумные пределы, профайлер памяти
в этом случае оказывается бесполезным, т.к. показывает скажем 100Mb
в качестве shallow size объектов, в то время как MemUsage=1600Mb

Dasar

значит утечка где-то в нативной либе, или на стыке java и натив, а не в самой джаве

katrin2201

профайлер памяти
в этом случае оказывается бесполезным, т.к. показывает скажем 100Mb
в качестве shallow size объектов, в то время как MemUsage=1600Mb
У тебя что-то не так. Shallow size - это какой shallow size?

Hastya

В чем цель исследования? Узнать sizeof объекта или понять систему управления памятью в JVM?

Zusk

Утечки нет. докзательство - берем весь процесс повторяем тысячи раз в цикле. память стабилизируется к одному числу сразу после первого повторения, например к 500Mb,
а должна быть 100Mb не болше...

Zusk

Shallow size - это какой shallow size?
См. jProfiler. Размер всех полей объекта - самих ссылок, а не того на что они ссылаются...

Zusk

Цель – например удостовериться, что java не подходит для задач требовательных к памяти,
(что должен подтвердить опыт других) и с чистой душой начать все переписывать на C++

zya369

:shocked: :shocked: :shocked:

SPARTAK3959

А ты на С++ запусти и выполни параллельно рандомное освобождение и выделение больших блоков и убедись, что С++ еще более для таких задач не подходит. Если у тебя большое количество маленьких объектов, то их нужно заменять на семейство массивов (или коллекций, умеющих эффективно хранить примитивные типы) - и тогда памяти будет тратится столько, сколько нужно.
Оставить комментарий
Имя или ник:
Комментарий: