#java
#java
Вопрос:
У меня есть функция Java, в документации которой утверждается, что она не выделяет кучу памяти.
Как я могу проверить, ведет ли он (по-прежнему) себя так, как заявлено, во время выполнения?
В идеале я хотел бы сделать это во время обычного (производственного) запуска программы таким образом, чтобы программа могла быть проанализирована и показана в качестве выходных данных программы.
Я надеюсь на простой надежный runtime API, такой как:
- установите контрольную точку A, вызовите функцию, установите контрольную точку B, спросите, произошли ли какие-либо выделения памяти кучи в этом потоке между A и B.
- или временно включите режим «выдавать исключение, если в этом потоке происходит выделение памяти кучи».
Если это невозможно, я хотел бы, по крайней мере, иметь возможность выполнить эквивалент во время какого-либо запуска в режиме отладки / профилирования.
В основном я заинтересован в том, чтобы делать это во время выполнения, но если это возможно сделать с помощью инструментов статического анализа, я бы тоже был заинтересован в этом.
Я искал в Интернете и ничего не нашел. Существуют профилировщики памяти, например, JFR, которые могут делать такие вещи, как отчет и разбивка текущего использования кучи в любой момент времени, но я не видел никаких доказательств того, что они могут ответить на этот простой точный запрос «да или нет», «произошли ли какие-либо выделения в этом потоке в течение этого интервала». запрос.
Ответ №1:
Вы можете полностью сделать это с помощью инструментария времени выполнения. Возможно, вы не сможете сделать это для изолированной части вашей программы, но вы можете настроить что-то вроде https://github.com/google/allocation-instrumenter для изолированного запуска вашей программы (или выборки запусков в рабочей среде). Вы можете использовать его, например, следующим образом:
public class Test {
public static void main(String [] args) throws Exception {
AtomicInteger allocations = new AtomicInteger();
AllocationRecorder.addSampler(new Sampler() {
public void sampleAllocation(int count, String desc, Object newObj, long size) {
allocations.getAndIncrement();
}
});
codeBlock(); // do any initialization, classloading first!
int expectedAllocations = allocations.get();
codeBlock();
assert allocations.get() == expectedAllocations; // no more allocations
}
}
Комментарии:
1. Это выглядит очень многообещающе, особенно лежащая в основе технология. Но почему вы говорите: «возможно, вы не сможете сделать это для отдельной части вашей программы»? Весь смысл в том, чтобы задать запрос об изолированной части моей программы.
2. Я имею в виду, что это может повлиять на производительность остальной части вашего кода: нет хорошего способа убедиться, что обратный вызов выполняется только для нужного вам кода; он будет выполняться для каждого выделения. Вы можете настроить обратный вызов так, чтобы он выполнял что-либо только в том случае, если вы «включили» выборку, но производительность все равно будет затронута.
3. А, понятно. Похоже, это хорошо удовлетворило бы мои потребности, если бы я мог заставить его работать. У вас это работает на вас? В Linux, с openjdk14, следуя инструкциям на странице «начало работы», я не могу ни собрать его локально (
mvn package
завершает тесты с «Не увидел правильного количества ожидаемых базовых функций: <2>, но было:<0>», и при этом у меня не работает готовый jar (java -javaagent:path/to/allocation.jar Test
сбой с «Не удалось выполнить класс инструмента: java.lang. Исключение IlligalArgumentException в com.google.monitoring.runtime.instrumentation.asm.ClassReader. <инициализация>» (с трассировкой стека).4. Я нашел пару неудобных путей в лабиринте: (1) используйте предварительно созданный jar (3.0) с java8 (вместо java11 или java14) — это работает. (2) добавьте -DskipTests в
mvn package
команду; он по-прежнему завершается неудачей (при создании документов), но, по крайней мере, сначала создается полезный файл .jar в target / dir , который работает даже с текущими версиями Java.5. После того, как я наконец получил рабочую версию этой утилиты, она работает блестяще. Принимаю!
Ответ №2:
Я не думаю, что есть способ проверить это, кроме проверки экземпляров new
in метода и любых методов, которые он вызывает.
Тесты выделения памяти: невозможно изолировать эффекты метода.
Сравнительный анализ: вы можете протестировать просто запуск метода изолированно, но это приведет к сбою в случае, когда распределение в рабочей среде будет производиться условно на основе присутствия других ресурсов, например
if (MyOtherClass.myOtherVariable != null) {
int[] arr = new int[100];
}
В этом случае выделение памяти зависит от какого-либо другого шага, который мог произойти или не произойти.
Ответ №3:
Вы можете профилировать использование памяти в контролируемой среде, используя Runtime#freeMemory()
, отключая GC ( -XX: UseEpsilonGC
) и запуская только логику для тестирования.
// Logic to test (to load classes, do one-off initializations)
Runtime runtime = Runtime.getRuntime();
long start = runtime.freeMemory();
// Logic to test
long end = runtime.freeMemory();
assert start == end;
Комментарии:
1. Но это не будет работать в многопоточной среде, верно?
2. @fluffy только в тестах (то, что OP вызывает запуск debug-mode / profiling).