Как определить, свободен ли раздел кода Java от выделения кучи?

#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).