Sprawdzenie wycieku pamięci w testach

0

Hej, uruchamiam sobie w Intellij testy integracyjne i nagle zaczął lecieć out of heap memory. Projekt jest zarządzany przez gradle. Zaciągnąłem do Intellij plugin Visualvm, ale z tego co czytam ma on jakieś kłopoty z gradle i generalnie mam te ikonki od uruchmienia przez VisualVM szare.

Znacie jakieś inne sposoby, abym sprawdził ewentualny wyciek pamięci w tych testach i gdzie on występuje?

0

a próbowałeś uruchamiać te testy z terminala? co prawda nie wiem jak masz projekt zbudowany (w sensie modułów etc.), ale wydaje mi się, że z terminala powinno się uruchomić. Piję do tego, że może intellij zżera za dużo
albo możesz spróbować podbić ilość pamięci w jvm

1
  1. Gradle to rak
  2. Ale jednak intellij przecież umie sam zbudować projekt i go odpalić, więc nie do końca rozumiem problem
  3. Można VisualVM wpiąć w działajacy proces, więc odpal go standalone, odpal aplikacje i się podepnij
0

Ok widzę, że heap cały czas rośnie:

screenshot-20210114001714.png

I nie wiem czy tych wszystkich obiektów nie jest za dużo xd?:

screenshot-20210114001817.png

1

Tych concurrent hash map node to masz dość sporo :D 1.5 mln elementów w tej macpie. Co ty tam trzymasz? Co to za aplikacja? Można ona potrzebuje po prostu więcej heapu i tyle?

0

@Shalom:
screenshot-20210114003611.png

Też mi chodzi kilka uśpionych wątków od Rabbita :O

0

To może np. robisz coś bardzo dziwnego i nie sprzątasz po testach i np. podnosisz aplikacje kolejny raz co test? Tak jak napisałem trudno wróżyć z fusów niestety. VisualVM pozwala "prześledzić" pewne obiekty żeby dojść do tego skąd się biorą, ale jeśli to się dzieje "poza twoim kodem" to nie wiem.

0

Wygląda na problem w tych testach integracyjnych. Jakieś proste testy performance (JMeter albo Gatling) bym odpalił i sprawdził na GCEasy (ale nie lokalnie), czy dalej masz taki problem.

1

Może tworzysz nowe komponenty za każdym razem kiedy trzeba obsłużyć wiadomość. Popularnym błędem jest np. tworzenie klienta http per request - może tutaj coś podobnego osiągnąłeś :)

2

Panie, ty masz tam 512 mb na heap.... (Edit: a widze, że jednak max to 1g)

https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html

Ustaw sobie jakiś ludzki rozmiar i do przodu.

test {

  useJUnitPlatform()

  minHeapSize = "1g"
  maxHeapSize = "2g"

Dopiero jak to nie pomoże to zobacz wycieki.

EDIT:4
@Bambo
Pokaż no te OutOfMemory - co tam dokładniej jest napisane w wyjątku, który leci. OOM - OOM nie równy - jest kilka ciekawych przypadków - tu może być taki.

0

@jarekr000000:

Na samym końcu leci ok 10 takich wyjątków i proces się kończy. Jest tak, że przechodzi 129 test, potem czekam 5 minut, po drodze lecą te wyjątki i koniec.

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#31-1

Wygląda jakby Rabbit sobie tworzył coś. Prawdopodobnie w parencie domenowym mamy coś skopane.

EDIT:
Oczywiście zwiększenie heapa pomogło i działa. Wykresy:

screenshot-20210114105154.png

Zastanawiam się tylko czy to normalne, że aż tyle żre. No i tutaj widać, że fajnie zwalnia ten heap, ale WTF z GC??!?!!? On nie chodzi w ogóle?

2

Że zżera pare gigabajtów - czasem normalne - takie czasy, takie programy. RAM nie taki drogi to i nie ma czym sie przejmować (do czasu).

Zobacz może zakłądkę metaspace - bo masz serio dziwne te obserwacje. Czegoś na tym rysunku nie widać i nie wiem czego. (Strzał na Metaspace też trochę wątpliwy).

EDIT:
Pokaż parametry JVM.

0

@jarekr000000:
metaspace:
screenshot-20210114110740.png

JVM:
screenshot-20210114110832.png

Co dokładnie powie Ci metaspace?

I w ogóle czy to normalne, że trzyma 1,5mln tych ConcurrentHashMapów? I dlaczego GC tam jest cały czas bez aktywności? o_O

@Charles_Ray:

Każdy test dziedziczy po klasie abstrakcyjnej:

@SpringBootTest
@ContextConfiguration(classes = {ServiceNotificationApplication.class, AbstractIT.Config.class})
@RunWith(SpringRunner.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
@AutoConfigureMockMvc
@AutoConfigureWireMock(port = 27000)
public abstract class AbstractIT {

    @Before
    public void setup() throws JsonProcessingException {
        new RestClientStubs().defineImageStubs();
    }

    @TestConfiguration
    public static class Config {
         @Primary
        @Bean
        CacheManager cacheManager() {
            return new ConcurrentMapCacheManager();
        }

// itd

2

Metaspace też się może przepełnić i tez możesz mieć OOM - przy pewnych projektach (użycie expression language, engine javascriptu) tam się dzieją cuda.
Niestety zniknął Ci chyba(!) oryginalny powód OOM przez ten UncaughtExceptionHandler. Ale metaspace raczej nie jest problemem jak widać.

Niskie użycie GC może znaczyć, że działa ekstremalnie szybko i wydajnie. Dopiero niskie gc w połączeniu z OOM to coś ultra dziwnego. Nie wiem co się dzieje.
Parametry JVM są normalne.

1

No to ja widzę 2 rzeczy:

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)

oraz

@Before
public void setup() throws JsonProcessingException {
    new RestClientStubs().defineImageStubs();
}

Im więcej testów, tym te rzeczy mają większy impact na zasoby. Po co stawianie wszystkiego od nowa po każdym teście/klasie?

Potwierdziłbym lub wykluczył tę hipotezę, że ten problem dotyczy tylko testów w taki sposób, że zrobiłbym jakiś trywialny test wydajnościowy i poobserwował heapa.

1

Oczywiście zwiększenie heapa pomogło i działa.

Dość ryzykowne stwierdzenie :P Puściłbym tych testów więcej żeby się upewnić że heap dalej nie rosnie ponad stan, bo zwiększenie heapu mogło np. tylko ukryć problem na chwile ;)
Ale nie wygląda to źle, ot widać że apka potrzebuje trochę ponad 500MB heapu to "normalnego działania" stąd kiedy było go mniej to się wywalało.

0

Raczej zmniejsz z powrotem tę pamięć i zrób dumpa w momencie błędu (HeapDumpOnOutOfMemoryError).
Być może to nie wina Twojego kodu a frajmłorka / toola / api / cokolwiek innego i po prostu powinno się zwiększyć max heap.
Ale równie dobrze właściwy OOM może być generowany przez jakiś bug w Twoim kodzie.
A na takich wykresach podczas działania aplikacji możesz tego nie wyłapać.

https://plumbr.io/blog/memory-leaks/solving-outofmemoryerror-dump-is-not-a-waste
https://dzone.com/articles/how-to-capture-java-heap-dumps-7-options

1

A weź jeszcze popatrz pod kątem tego czy Twoje obiekty jakie tworzysz, mają nadpisane metody equals/hashCode? I czy sporo klas jest mutowalnych? Bo jeśli np tworzysz (bezpośrednio lub przez framework) jakąś mapę, i potem te obiekty które są kluczami zmieniają swój hashCode, to potem taki klucz leży w tej mapie na wieczność - stąd może te 1,5 mln hashmap nodów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1