Czym są wycieki pamięci i jak je naprawić?

Tak jak mózg jest kluczowy dla ludzkiego życia, pamięć jest równie ważna dla komputerów. Twój system nie może wykonywać zadań, jeśli nie ma wystarczającej ilości pamięci RAM.

Z powodu wycieków pamięci może wystąpić niedobór pamięci RAM i inne problemy z pamięcią. Dlatego pokażemy, jak wykryć wycieki pamięci i je naprawić.

Ale wcześniej przyjrzyjmy się bliżej wyciekom pamięci i dlaczego warto je naprawić.

Czym są wycieki pamięci?

Wyobraź sobie parking tuż obok centrum handlowego, na którym stoją wszystkie samochody, niezależnie od tego, czy kupujący skończyli zakupy, czy nie. Z biegiem czasu nie będzie już miejsca na parkowanie nowych pojazdów, co doprowadzi do zatorów w ruchu i zmniejszenia ogólnej wydajności centrum handlowego.

Źródło obrazu: prateeknima.medium.com

Podobnie jest z komputerami!

Aplikacje komputerowe, podobnie jak samochody na parkingu, mogą zapomnieć o zwolnieniu używanej pamięci, gdy nie są już potrzebne. Obciąża to pamięć i nie pozostawia miejsca na płynne wykonywanie nowych zadań, co skutkuje częstym błędem pamięci zwanym wyciekiem pamięci.

Przykładowy kod pokazujący wyciek pamięci:

void memory_allocation() {
    int *ptr = (int*)malloc(sizeof(int));
}

Powyższy fragment kodu C przydziela część pamięci dla zmiennej całkowitej i przypisuje jej lokalizację w pamięci do wskaźnika „ptr”. Nie napisano jednak kodu zwalniającego pamięć, co prowadzi do wycieków pamięci.

def infinite_rec():
    return infinite_rec()

W powyższym kodzie Pythona nie ma przypadku podstawowego, który umożliwiałby zakończenie funkcji. Zatem powyższy kod powoduje przepełnienie stosu i wycieki pamięci.

Typowe przyczyny wycieków pamięci

Zaniedbanie programisty

Pierwszą przyczyną wycieku pamięci jest zaniedbanie programisty.

Programiści często przydzielają dane do pamięci, ale czasami zapominają o ich zwolnieniu, gdy nie są już potrzebne. W pewnym momencie powoduje to zajęcie całej pamięci i brak miejsca na nadchodzące zadania, co prowadzi do tak zwanego błędu „wycieku pamięci”.

Języki programisty

Używanie języków programowania bez wbudowanego systemu zarządzania pamięcią może powodować wycieki pamięci.

Języki programowania, takie jak Java, mają wbudowane moduły zbierające elementy bezużyteczne, które automatycznie zajmują się zarządzaniem pamięcią.

Ale na przykład C++ nie ma wbudowanego modułu zbierającego elementy bezużyteczne. Ty, programista, powinieneś ręcznie obsługiwać pamięć, co prowadzi do wycieków pamięci, gdy zapomnisz o ręcznym oczyszczeniu pamięci.

Duże wykorzystanie pamięci podręcznej

Często używane zadania, dane lub aplikacje są buforowane w celu zapewnienia szybszego dostępu.

Może to prowadzić do błędu wycieku pamięci, jeśli elementy znajdują się w pamięci podręcznej, ale nie zostały wyczyszczone, mimo że są nieaktualne lub nie są już zgodne z bieżącymi wzorcami użytkowania.

Użycie zmiennych globalnych

Zmienne globalne przechowują przydzielone dane przez cały okres istnienia aplikacji. Zatem użycie większej liczby zmiennych globalnych powoduje zużycie dużej ilości pamięci przez dłuższy czas i powoduje wycieki pamięci.

Nieefektywne struktury danych

Programiści często tworzą własne struktury danych w celu realizacji niestandardowych funkcjonalności. Jednak błędy wycieku pamięci pojawiają się, gdy te struktury danych nie mogą zwolnić używanej pamięci.

Niezamknięte połączenia

Niezamykanie plików, baz danych, połączeń sieciowych itp. po użyciu może również prowadzić do błędów związanych z wyciekiem pamięci.

Konsekwencje wycieków pamięci

Niska wydajność — w miarę gromadzenia się wycieków pamięci nastąpi stopniowy spadek wydajności aplikacji lub systemu. Dzieje się tak dlatego, że nie będzie dostępnej pamięci na wykonanie zadań, co spowalnia działanie aplikacji.

Awarie aplikacji – aplikacjom kończy się pamięć w miarę zwiększania się wycieków pamięci. W pewnym momencie, gdy nie ma dostępnej pamięci, program ulega awarii, co powoduje utratę danych i awarię aplikacji.

Luki w zabezpieczeniach — nieprawidłowe czyszczenie wrażliwych danych, takich jak hasła, dane osobowe lub poufne informacje z pamięci po użyciu, naraża te dane na ataki w przypadku wycieków pamięci.

Wyczerpanie zasobów – aplikacje zajmują więcej miejsca w pamięci RAM, gdy zabraknie im pamięci z powodu wycieków pamięci. Zwiększa to zużycie zasobów i zmniejsza ogólną wydajność systemu.

Jak wykryć wycieki pamięci?

Ręczna kontrola kodu

Sprawdź swój kod źródłowy, aby znaleźć przypadki, w których pamięć jest alokowana, ale nie jest czyszczona po użyciu. Poszukaj zmiennych i obiektów w kodzie, które wykorzystują pamięć, ale nie zwalniają jej, gdy nie są już potrzebne.

Miej także oko na główne źródła przechowywania danych, tzn. upewnij się, że struktury danych dobrze zarządzają przydzieloną pamięcią.

Analiza kodu statycznego

Różne dobrze zaprojektowane narzędzia do analizy statycznej analizują kod źródłowy kompilatora i wykrywają przypadki wycieków pamięci.

Czasami śledzą typowe wzorce, reguły i błędy w kodzie, aby wykryć potencjalne wycieki pamięci, jeszcze zanim one wystąpią.

Narzędzia analizy dynamicznej

Narzędzia te wykorzystują podejście dynamiczne do analizy kodu podczas wykonywania i wykrywania wycieków pamięci.

Narzędzia do analizy dynamicznej badają zachowanie obiektów, funkcji i wykorzystania pamięci w czasie wykonywania. Dlatego narzędzia te są bardzo dokładne w wykrywaniu wycieków pamięci.

Narzędzia do profilowania

Narzędzia do profilowania dają wgląd w sposób, w jaki aplikacja wykorzystuje pamięć.

Jako programista możesz wykorzystać te informacje do analizy wykorzystania pamięci przez program i optymalizacji technik zarządzania pamięcią, aby zapobiec awariom aplikacji i problemom z degradacją pamięci.

Biblioteki wykrywania wycieków pamięci

Niektóre języki programowania oferują biblioteki wbudowane lub biblioteki innych firm umożliwiające wykrywanie wycieków pamięci w programie.

Na przykład Java ma moduł zbierający elementy bezużyteczne do obsługi pamięci, a C++ oferuje CrtDbg do zarządzania pamięcią.

Ponadto wyspecjalizowane biblioteki, takie jak LeakCanary, Valgrind, YourKit itp., rozwiązują problem wycieków pamięci w różnych typach aplikacji.

Jak naprawić wyciek pamięci?

Identyfikuj wycieki pamięci

Aby naprawić wycieki pamięci, musisz je najpierw zidentyfikować.

Możesz przeprowadzić ręczną kontrolę lub skorzystać z automatycznego narzędzia, aby wykryć, czy w aplikacji występuje wyciek pamięci. Aby wykryć wycieki, możesz wypróbować inne metody wykrywania wycieków pamięci wymienione powyżej.

Zidentyfikuj obiekty powodujące wyciek

Po potwierdzeniu w powyższym kroku, że aplikacja powoduje wyciek pamięci, należy poszukać obiektów i struktur danych powodujących wyciek. Dowiedz się, w jaki sposób przydzielana jest im pamięć i gdzie mają ją zwalniać.

Twórz przypadki testowe

Teraz zawęziłeś dokładne miejsce wycieku pamięci. Utwórz więc przypadek testowy, aby upewnić się, że poprawnie zidentyfikowałeś źródło wycieku pamięci i aby upewnić się, że wyciek ustąpi po naprawieniu tych konkretnych obiektów.

Napraw kod

Dodaj kod zwolnienia pamięci, aby zwolnić zablokowaną pamięć przez zidentyfikowane wadliwe obiekty. Jeśli kod już istnieje, zaktualizuj go, aby upewnić się, że prawidłowo zwalnia używaną pamięć.

Przetestuj jeszcze raz

Ponownie użyj narzędzi do wykrywania wycieków lub automatycznych testów, aby sprawdzić, czy aplikacja działa zgodnie z oczekiwaniami i czy nie ma blokady pamięci.

Przetestuj także wydajność i funkcjonalność swojej aplikacji, aby upewnić się, że aktualizacja kodu nie wpłynie na inne czynniki aplikacji.

Najlepsze praktyki zapobiegania wyciekom pamięci

Bądź odpowiedzialnym programistą

Powinieneś pamiętać o zwalnianiu używanej pamięci lub zwalnianiu wskaźników pamięci podczas pisania samego kodu. Zminimalizuje to problemy związane z wyciekiem pamięci.

Pamiętasz poniższy kod? Jak wspomniano na początku artykułu, nie ma fragmentu kodu zwalniającego pamięć, co prowadzi do wycieku pamięci.

void memory_allocation() {
    int *ptr = (int*)malloc(sizeof(int));
}

Oto jak jako programista możesz zwolnić pamięć.

delete ptr;

Używaj wyposażonych języków programowania

Języki programowania, takie jak Java lub Python, wykorzystują wbudowane biblioteki do zarządzania pamięcią, takie jak moduły zbierające elementy bezużyteczne, aby automatycznie obsługiwać wycieki pamięci.

Chociaż przeoczysz kilka przypadków, te wbudowane narzędzia sobie z nimi poradzą, zapobiegając potencjalnym wyciekom pamięci.

Dlatego zalecam używanie takich języków programowania, w których wbudowane jest zarządzanie pamięcią.

Referencje cykliczne

Unikaj odwołań cyklicznych w swoim programie.

Odniesienia cykliczne podążają za zamkniętą pętlą obiektów odnoszących się do siebie. Na przykład obiekt a odnosi się do b, obiekt b odnosi się do c, a obiekt c ponownie odnosi się do a, bez końca w pętli. Zatem odniesienia cykliczne prowadzą do nieskończonej pętli, powodując wycieki pamięci.

Minimalizuj użycie zmiennych globalnych

Powinieneś unikać używania zmiennych globalnych, jeśli obawiasz się o wydajność pamięci. Zmienne globalne zużywają pamięć przez cały czas działania aplikacji, co jest złą praktyką w zarządzaniu pamięcią.

Przejdź więc na zmienne lokalne. Są one wydajne pod względem pamięci, ponieważ zwalniają pamięć po zakończeniu wywołania funkcji.

Zmienne globalne wyglądają następująco, ale używaj ich tylko wtedy, gdy jest to konieczne.

int x = 5 // Global variable
void func(){
    print(x)
}

Ale użyj zmiennych lokalnych w następujący sposób:

void func(){
    int x = 5 // Local variable
    print(x)
}

Ogranicz pamięć podręczną

Ustaw limit pamięci, z której może korzystać pamięć podręczna. Czasami wszystkie zadania wykonywane w systemie zostaną przeniesione do pamięci podręcznej, a nagromadzona pamięć podręczna prowadzi do wycieków pamięci.

Zatem ograniczenie pamięci podręcznej może zapobiec występowaniu wycieków pamięci.

Przetestuj dobrze

Uwzględnij testy wycieków pamięci w fazie testowania.

Twórz automatyczne testy i uwzględnij wszystkie przypadki brzegowe, aby wykryć wycieki pamięci przed udostępnieniem kodu do produkcji.

Wyposaż narzędzia do monitorowania

Użyj narzędzi do automatycznego profilowania, aby monitorować wykorzystanie pamięci. Regularne śledzenie zużycia pamięci pomaga zidentyfikować potencjalne wycieki i naprawić je z wyprzedzeniem.

Profiler Visual Studio, profiler NET Memory i JProfiler to kilka dobrych narzędzi w tym kontekście.

Wniosek

Aby osiągnąć maksymalną wydajność aplikacji, konieczne jest efektywne zarządzanie pamięcią, a wycieków pamięci nie można w tym kontekście ignorować. Aby efektywnie zarządzać pamięcią, należy zaradzić wyciekom pamięci i zapobiegać ich występowaniu w przyszłości. W tym artykule dowiesz się, jak możesz to zrobić.

Pokazaliśmy różne metody wykrywania wycieków pamięci, sprawdzone kroki, jak je naprawić, oraz praktyki, które możesz zastosować, aby uniknąć przyszłych wycieków pamięci.

Następnie możesz także dowiedzieć się, jak naprawić błąd „braku pamięci” w systemie Windows w ciągu 5 minut.