blackstrip писал(а): ↑Пт окт 17, 2025 6:06 pm Проблема оперативной памяти
Паинткад давно уже не идет на слабых телефонах с MIDP2. Запускаем вот эту последнюю версию с настройкой клавиш и т.п. на Siemens C65 эмуляторе и видим OutOfMemory:blackstrip писал(а): ↑Ср окт 08, 2025 10:10 pmДля сименса прописаны клавиши как для самых последних моделей Benq-Siemens, предпросмотр по физической "Взять трубку", тип линий по "Громкость-", фото по "Громкость+", а физическая "Положить трубку" не используется, т.к. она закрывает паинткад. На более старый сименс x65/75 потребуется дополнительная настройка вручную. Но там, на сименсах, и так сейчас от слишком толстых классов паинткада при запуске сразу исключение OutOfMemorу и вылет. Эту проблему надо будет попробовать решить позже
Т.е. ява распаковала JAR-архив, взяла главный класс PaintCAD, запустила его, там в самом начале создаются экземпляры класса стартового окна Loading и главного класса MainForm. Потом передается отображение в Loading. И где-то там не хватило памяти и всё, конец)
Когда-то давно такая ошибка стала вылетать - и тогда класс MainForm был поделен на два класса: MainForm и MainForm2. Часть процедур и переменных были перекинуты в MainForm2. И тогда все заработало и OutOfMemory перестал вылетать. Но потом программа росла-росла и вот уже лет пять-десять на старых сименсах она не запускается опять.
Попробуем разобраться из-за чего так происходит. Варианты:
1) если классы и ресурсы (картинки) помещаются в оперативную память и занимают там место, то этого места не хватило и привет. Можно было бы сделать для слабых телефонов версию с мелкой графикой, не занимающей много места
2) предел по размеру класса, как только класс вырастает до некоторого предельного для телефона размера (например, 64 килобайта) - ява больше не хочет грузить его и при загрузке такого большого класса выдает OutOfMemory (тогда понятно почему разделение класса MainForm на два класса помогло - было, например 80 кбайт, а стало 40 и 40, оба класса стали загружаться и запускаться без проблем). Тогда надо снова делить MainForm и делать MainForm3 =)
3) что-нибудь неведомое, что знают только те, кто знает как работает ява (это не я, ха-ха), как она грузит классы и запускает их, на что тратится Java-heap и т.д. Тут бы помог вывод с разблюдовкой по классам - какой класс сколько хочет занять, какой сколько занимает, какой спотыкается на выделении памяти и заваливает всю программу, и в какой момент все это происходит
У эмулятора, кстати, есть output window - выдает всякие сообщения. И он после ошибки OutOfMemory выдает вот что:
Провалена инициализация класса paintcad.ah. Интересное имя paintcad.ah - из-за обфускатора, который шифрует и переименовывает все классы чтоб никто не декомпилировал ява-проги. Выключим его, увидим реальные имена:
Не удается инициализировать класс MainForm. Уберем все создания экземпляров классов и переход на MainForm - эмулятор бесконечно показывает анимацию "Please wait...", видимо чтоб программа загрузила все что надо и наконец-то перешла на первый Displayable-класс:
Попробуем передадим отображение на новый класс сохранения 24-битного BMP:
О, чудо - он показывается:Код: Выделить всё
display = Display.getDisplay(this); display.setCurrent(new SBMP24());
Т.е. загрузить, распаковать куда-то и запустить 600-килобайтовый JAR полностью - у Siemens C65 сил хватает. И начать отображать один из классов-displayabl-ов - тоже хватает. Хорошо. Делать low-версию с урезанным количеством ресурсов не придется.
Попробуем создать только экземпляр Loading, а перейти на SBMP24:
И тоже видим OutOfMemory. Причем в Output window эмулятора - ничего не выводится. Будем отрезать от Loading-а кусочки пока он не станет таким худым что запустится =)Код: Выделить всё
displayabl = new Loading(); display = Display.getDisplay(this); display.setCurrent(new SBMP24());
Вырезав все из Loading (включая все обращения к статическим переменным MainForm) - зазапускался паинткад с отображением SBMP24 окошка. Хорошо.
Теперь передадим отображение прямо на Loading, как это обычно и происходит в необрезанном паинткаде:В процедуре отрисовки пропишем просто зарисовку экрана черным:Код: Выделить всё
displayabl = new Loading(); display = Display.getDisplay(this); display.setCurrent(displayabl);И оно рисуется:Код: Выделить всё
g.setColor(0,0,0); g.fillRect(0,0,getWidth(),getHeight());
Но стоит добавить в этот хорошо рисующий черным Loading обращение хоть к одной статической переменной класса MainForm - и снова OutOfMemory. Видимо, в этот момент класс MainForm подгружается - при первом обращении к его переменным.
Уберем обращение к большому классу MainForm. И проверим, действительно ли, класс не может загрузиться если он большого размера, или это неправда.
Сейчас Loading почти 4 килобайта:
А MainForm аж 119 килобайт:
Напихаем в Loading от души кода в какую-нибудь фиктивную процедуру так чтобы он разросся до подобного размера. Добавим статичную процедуру:Размер увеличился до 3,99 килобайта:Код: Выделить всё
static void Fictive() { JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); }
Повторим строчку приравнивания 10 раз вместо одного:Стало 4,09 килобайта:Код: Выделить всё
static void Fictive() { JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); JoySetup.KEY_FOTO = (int)System.currentTimeMillis(); }
Loading увеличился на 100 байт. А нам надо нарастить 115 килобайт. Хорошо, вставляем в процедуру 100 таких строчек вместо 10, смотрим линейно ли растет размер (мало ли какая оптимизация явы включится). Видим 5,05 килобайта:
Вырос на 1 килобайт примерно. Как раз в 10 раз больше чем 100 байт. Окей, теперь добавим 100 раз по 100 строчек +) Должен быть класс 100 с лишним килобайт.
Теперь Jbuilder помер, пишет что байт-код превышает 65536 байт (64 килобайта):
Может ему не нравится одна большая процедура. Хорошо, сделаем 10 маленьких.
10 маленьких preverifier-у понравились. Класс Loading теперь 111 килобайт:
Пробуем запустить его и отрисовать черный экран. И он рисуется:
Может быть порог размера класса выше 111 килобайт. Добавим фиктивных процедур и дотянем размер Loading-класса до 118 килобайт и даже выше:
122 килобайта и... снова все рисуется. Значит нет никакого "предельного" размера class-файла (по крайней мере при текущем размере проблемного класса MainForm в 118 килобайт) и это не поддельное OutOfMemory, а настоящее. Память явы переполняется при подгрузке и инициализации MainForm.
Добавим в отрисовку помимо черного экрана еще и доступное количество памяти и общее количество памяти в байтах:При чистом Loading-модуле без запросов к MainForm и другим классам:g.setColor(0,0,0);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(0,200,0);
g.drawString(String.valueOf(Runtime.getRuntime().freeMemory()),5,5,Graphics.LEFT|Graphics.TOP);
g.drawString(String.valueOf(Runtime.getRuntime().totalMemory()),5,25,Graphics.LEFT|Graphics.TOP);
В этих же условиях, но после обфускации:
Этот же JAR, собранный в APK и запущенный под андроидом 2.3.3 на эмуляторе - запустим дважды, цифры могут отличаться, т.к. в яве постоянно garbage collector чистит всякие ненужные вещи в памяти, но примерно количество свободной памяти должно быть одинаково в обоих запусках:
А теперь дернем одну из переменных толстого класса MainForm и посмотрим сколько теперь свободной памяти - тоже дважды посмотрим для точности:
Всего на 12 килобайт стало меньше. И это сваливает сименс в OutOfMemory... (проверил на всякий случай на этой же версии JAR - да, все еще сваливает +) Очень странно.
Может быть еще есть какой-то лимит. По количеству переменных, связей между классами, статичных переменных, расшаренных на весь мидлет.
Допустим, андроид неправильно все делает и при подключении большого класса MainForm толком не выделяет память (неизвестно как сделана ява андроида). Проверим тогда и на обычных J2ME-телефонах.
Nokia N90:
Просто Loading, без связи с большим MainForm - нокия говорит, что у нее всего 1 мегабайт памяти, и свободной всего около 70-100 килобайт (иногда даже 4 килобайта показывает), как будто она все классы паинткада сразу загрузила несмотря на то, есть ли с ними связи или нет:
Теперь Loading, дергающий переменную MainForm - у нокии открылось второе дыхание и теперь всего памяти не 1 миллион байт, а 2,5 миллиона, чудеса. И свободной опять около 100 килобайт. Как будто после связи с MainForm (и связью самого класса MainForm со всеми остальными связанными классами) вместо занимаемых 900 килобайт теперь нужно 2300 килобайт - если действительно это так, то здесь сименс и помирает на OutOfMemory со своими доступными 1500 килобайтами, 2300 в него не влезают:
Надоело болтающееся число свободных байт. Добавим в код вызов garbage collector через System.gc() - после его вызова лишнее и ненужное в памяти насильно будет удалено, может, число свободных байт станет более стабильным:
Проверим поедание памяти при подгрузке MainForm еще на одной яве. Это телефон LG GT540. Вообще это изначально был Android 1.6, потом выходили обновления и он стал Android 2.1. Но в нем есть эмулятор Java от LG. Этот эмулятор прикидывается LG-телефоном на яве и может устанавливать и запускать J2ME-мидлеты в андроиде. Мидлеты появляются в списке программ прямо в том же списке что и андроид-приложения, и их можно даже вытащить на рабочий стол:g.setColor(0,0,0);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(0,200,0);
System.gc();
g.drawString(String.valueOf(Runtime.getRuntime().freeMemory()),5,5,Graphics.LEFT|Graphics.TOP);
g.drawString(String.valueOf(Runtime.getRuntime().totalMemory()),5,25,Graphics.LEFT|Graphics.TOP);
Проверка Loading без связи с MainForm - говорит что паинткад занял около 3 мегабайт (3000 килобайт), а всего доступно аж 20 мегабайт, поэтому 17 мегабайт свободны. Остановка мидлета и повторный запуск - бесконечно показывает одно и то же число свободной памяти:
Проверка Loading со связь с MainForm. (на этом телефоне если его повернуть горизонтально-вертикально, то эмулятор перерисовывается и прекращает пытаться натянуть мидлет на экран. Шрифты становятся красивые, точка в точку, без масштабирования кривобокого). С подключенным классом MainForm числа заменялись от запуска к запуску, но совсем не сильно:
Разница с MainForm и без MainForm - около 10 килобайт. Но тут сразу заняты 3 мегабайта, поэтому, вероятно, он сразу подгрузил все классы, поэтому разницы и нет.
В общем:
- есть явы (Nokia N90), где подключение MainForm подгружает его и увеличивает лимит оперативной памяти чтобы все влезло. И становится нужно паинткаду 2,5 мегабайта памяти.
- есть явы (ява-эмулятор LG GT540, и просто запуск под Android APK), где подключение MainForm никак не влияет, вероятно, там память сразу выделяется на все классы и все их связи и переменные.
Есть еще эмуляторы Siemens в составе Benq-Siemens Mobility Toolkit - мощные эмуляторы для телефонов с большими (по меркам сименсов) экранами типа 240х320 пикселей. И там есть эмулятор слайдера Siemens SL75 - это телефон с экраном 132х176 пикселей как более старые сименсы. У них много оперативной памяти в яве.
Проверка Loading без связи с MainForm в эмуляторе Siemens SL75:
Занято сразу под 2 мегабайта, зато всего 3,7 мегабайта. Может быть здесь влезет в такую большую память Loading с MainForm.
От запуску к запуску количество свободной памяти здорово меняется - от 800 килобайт до 2,5 мегабайт)
Проверка Loading со связью с MainForm в эмуляторе Siemens SL75 - он не выдает OutOfMemory:
Стабильно остается по 800-850 килобайт. Значит паинткаду нужно 3670-850 = 2,8 мегабайта после подключения MainForm.
Посчитаем разницу памяти до подгрузки MainForm и после подгрузки MainForm на всех вышеописанных устройствах и эмуляторах. Ориентироваться будем на самую большую свободную память (меньшие числа - это просто не доработал garbage collector):
Android-эмулятор 2.3.3
До: 5644 - 2845 = 2799 Кб
После: 5644 - 2833 = 2811 Кб
Разница: 12 Кб
Nokia N90
До: 1000 - 114 = 886 Кб
После: 2441 - 151 = 2290 Кб
Разница: 1404 Кб
Эмулятор явы в LG GT540
До: 20971 - 17937 = 3034 Кб
После: 20971 - 17932 = 3039 Кб
Разница: 5 Кб
Siemens SL75 эмулятор
До: 3670 - 2465 = 1205 Кб
После: 3670 - 852 = 2818 Кб
Разница: 1613 Кб
Выводы:
1) на тех телефонах, где подключение MainForm сильно увеличивало занятую память (Nokia N90, Siemens SL75) - это подключение потребовало 1400-1600 килобайт памяти;
2) после подключения MainForm занимаемая паинткадом память стала равна:
- 2,3 Мбайт на Nokia N90,
- 2,8 Мбайт на Android и на Siemens SL75
- 3 Мбайт на эмуляторе явы в LG GT540
Поэтому текущие "системные требования" паинткада: минимум 2,5 - 3 мегабайта памяти в яве только для загрузки классов. Вот такие дела
Кроме того, при загрузке паинткада выделяется память под различные буферы, загружаются в память и масштабируются значки и графика. Это требует дополнительной памяти.
Проведем тест - запустим паинткад в 24-битном режиме с новым рисунком 32х32 пикселя. В меню "Помощь" есть пункт "Объем памяти", показывает свободную и полную память (эти же цифры что и наверху на скриншотах). Посмотрим сколько памяти занято при полной загрузке паинткада со всеми ресурсами, картинками и т.п. на этих же устройствах и эмуляторах:
Android-эмулятор 2.3.3
6365 - 3344 = 3021 Кб
Nokia N90
3051 - 1441 = 1610 Кб
Сначала она модулю Loading давала всего 1000 Кб памяти, потом давала для Loading+MainForm всего 2441 Кб, а сейчас - уже для полного паинткада всего 3051 Кб =) Как она это делает? Динамически растит явовскую память до больших значений +) Это телефон 2005 года, тогда еще были в ходу сименсы с экраном 101х80 точек и 4096-цветным экраном или 132х176. А эта с гигантским экраном и очень большой памятью по сравнению с ними.
Эмулятор явы в LG GT540
20971 - 17158 = 3813 Кб
Siemens SL75 эмулятор
Раньше паинткад и тут выдавал OutOfMemory. Но сейчас, почему-то работает) Интересно, тогда, посмотреть как тут выглядит настройка клавиш - она тут даже работает и текст влезает во все элементы на экране:
![]()
![]()
![]()
![]()
![]()
![]()
![]()
Что касается оперативной памяти:
3670 - 848 = 2822 Кб
Теперь можно высчитать сколько занимают буферы и ресурсы в памяти во всех этих устройствах:
Android-эмулятор 2.3.3
Loading: 2799 Кб
Loading+MainForm: 2811 Кб
Полностью PaintCAD: 3021 Кб
Чисто ресурсы: 3021 - 2811 = 210 Кб
Nokia N90
Loading: 886 Кб
Loading+MainForm: 2290 Кб
Полностью PaintCAD: 1610 Кб
Чисто ресурсы: 1610 - 2290 = минус 680 Кб (во бред)
Эмулятор явы в LG GT540
Loading: 3034 Кб
Loading+MainForm: 3039 Кб
Полностью PaintCAD: 3813 Кб
Чисто ресурсы: 3813 - 3039 = 774 Кб
Siemens SL75 эмулятор
Loading: 1205 Кб
Loading+MainForm: 2818 Кб
Полностью PaintCAD: 2822 Кб
Чисто ресурсы: 4 Кб
Ничего не понятно) Почему-то после прохождения полного цикла загрузки по MainForm, создания разных буферов и загрузки картинок в память:
- где-то памяти надо больше на 774 Кб (это нормально),
- где-то на 210 Кб (ну это андроид, он может чудить, ведь это портировщик с явы, что он там показывает хз),
- где-то минус 680 килобайт (Нокия N90 после загрузки ресурсов и создания буферов потребовала меньше памяти),
- где-то около нуля (эмулятор Сименса SL75 тоже, допустим, может чудить, хотя странно)
Допустим, у Нокии N90 где-то в пике загрузки классов потребовалось настолько много памяти, то она до загрузки ресурсов показала большее число, и garbage collector ничего этого лишнего очистить не мог, но уже после полной загрузки паинткада он смог и память освободилась, хз как там классы грузятся.
В общем, требуемая для сегодняшней версии память чтоб при запущенном паинткаде рисовать картинку 32х32 пикселя:
Android-эмулятор 2.3.3: 3021 Кб
Nokia N90: 1610 Кб
Эмулятор явы в LG GT540: 3813 Кб
Siemens SL75 эмулятор: 2822 Кб
И требуемая максимальная память (максимальное значение необходимой памяти из двух значений - до загрузки ресурсов и после загрузки ресурсов):
Android-эмулятор 2.3.3: 3021 Кб
Nokia N90: 2290 Кб
Эмулятор явы в LG GT540: 3813 Кб
Siemens SL75 эмулятор: 2822 Кб
1. PNG (Portable Network Graphics)
PNG-файл состоит из чанков — блоков данных с определённой структурой. Некоторые из них обязательны (например, IHDR, IDAT, IEND), а другие — опциональны и часто содержат метаданные:
tEXt, zTXt, iTXt — текстовые комментарии, автор, описание и т.п.
gAMA — гамма-коррекция.
cHRM, sRGB, iCCP — цветовые профили.
tIME — дата создания.
pHYs — физическое разрешение (dpi).
bKGD, hIST, tRNS — могут быть полезны, но не всегда нужны.
Оптимизация: Удаление всех необязательных чанков (особенно цветовых профилей и текстовых метаданных) может значительно уменьшить размер, особенно если изображение используется в вебе, где цветовые профили часто игнорируются.
Инструменты:
pngcrush — удаляет ненужные чанки и пересжимает данные.
optipng — lossless-оптимизатор, убирает лишнее.
pngquant — уже с потерями (lossy), но даёт сильное сжатие.
oxipng — быстрая Rust-альтернатива optipng.
2. JPEG
JPEG не использует чанки в том же смысле, что PNG, но содержит сегменты (segments), включая:
APP0, APP1 и др. — могут содержать EXIF, XMP, ICC-профили, комментарии, данные о камере и т.д.
COM — пользовательские комментарии.
Оптимизация: Удаление EXIF/XMP/ICC данных часто уменьшает размер на 10–30%, особенно для фото с камеры.
Инструменты:
jpegoptim — lossless-очистка метаданных и оптимизация таблиц Хаффмана.
mogrify -strip (из ImageMagick) — удаляет все профили и комментарии.
guetzli, mozjpeg — более агрессивные (иногда с потерями) оптимизаторы.
3. GIF
GIF может содержать:
Комментарии (Comment Extension).
Метаданные от программ (например, Adobe).
Лишние цвета в палитре.
Неоптимальные кадры (в анимированных GIF).
Удаление комментариев и оптимизация палитры/кадров помогает уменьшить размер.
Инструменты:
gifsicle — мощный инструмент для сжатия и оптимизации анимированных GIF.



































































































































































































