Speicherleck
Speicherleck (englisch memory leak, gelegentlich auch Speicherloch oder kurz memleak) bezeichnet einen Fehler in der Speicherverwaltung eines Computerprogramms, der dazu führt, dass es einen Teil des Arbeitsspeichers zwar belegt, diesen jedoch weder freigibt noch nutzt.
Problematik
Arbeitsspeicher ist ein nur in endlicher Menge verfügbares Betriebsmittel, das einem Programm auch nicht ohne weiteres wieder entzogen werden darf. Wenn ein Programm durch Speicherlecks zunehmend mehr Arbeitsspeicher belegt, kann es dazu kommen, dass die Leistungsfähigkeit des Computers sinkt, da Teile des Arbeitsspeichers ausgelagert werden müssen. Wenn die Menge an Speicher, die ein Prozess belegen kann, beschränkt wurde, ist es dem Programm irgendwann nicht mehr möglich, weiter Speicher zu allozieren und es kommt zu einem Programmfehler. Bei Betriebssystemen mit unzureichendem Speicherschutz (z. B. bei Embedded-Systemen) kann im schlimmsten Fall die Überbelegung des Speichers dazu führen, dass auch Teile des Betriebssystems nicht mehr korrekt ausgeführt werden können und das System abstürzt.
Lösungsmöglichkeiten
Um Speicherlecks aufzuspüren, existieren mehrere Möglichkeiten:
- Analyse der Referenzen auf Speicherbereiche (z. B. Smart Pointer); diese Analyse erkennt nur nicht mehr zugreifbare Speicherbereiche.
- Analyse des Quelltexts auf formale Korrektheit.
- Analyse konkreter Laufzeit-Situationen im Rahmen eines Software-Tests.
Einschränkung negativer Auswirkungen
Der Speicher, der höchstens von einem Prozess belegt werden darf, kann durch das Betriebssystem beschränkt werden. Das behebt den ursprünglichen Fehler nicht, aber begrenzt die Auswirkungen auf andere Prozesse.
Unter Linux existiert eine Kernelfunktion, der OOM-Killer (engl. out of memory killer), die zum Einsatz kommt, wenn alle Versuche fehlschlugen, Speicher zu allozieren. Sie wählt unter den laufenden Prozessen u. a. denjenigen mit dem höchsten Speicherbedarf, der kürzesten Laufzeit und der niedrigsten Priorität und beendet diesen zwangsweise. Nach Beendigung des Prozesses wird aller von ihm belegter Arbeitsspeicher wieder freigegeben.[1]
Automatische Speicherbereinigung
Falls die verwendete Laufzeitumgebung eine automatische Speicherbereinigung (Garbage collection) bereitstellt, versucht diese zu ermitteln, auf welche Speicherbereiche ein Prozess nicht mehr zugreifen kann. Stellt die Laufzeitumgebung fest, dass ein belegter Speicherbereich für das Programm nicht mehr erreichbar ist, wird dieser wieder freigegeben. Nicht mehr erreichbar heißt in diesem Zusammenhang, dass keine gültige Referenz – ausgenommen weak references – auf den belegten Speicherbereich mehr existiert. Speicherlecks können dabei dennoch entstehen, wenn der Speicher für das Programm noch erreichbar ist, d. h. es eine Referenz auf den Speicher hält, ihn aber dennoch nicht mehr verwendet.
Explizite Speicherfreigabe
Im Gegensatz zur automatischen Speicherbereinigung muss der Anwendungsentwickler bei einer manuellen Speicherverwaltung explizit dynamische Speicherbereiche wieder freigeben. Versäumt er dies bspw. am Ende einer Funktion, wird anschließend die Referenz auf den noch reservierten Speicher gelöscht und der Bereich kann nicht mehr freigegeben werden.
Systematische Tests
Durch systematisches Testen mit Hilfe entsprechender Werkzeuge, die mit einer gewissen Sicherheit feststellen können, welche Speicherbereiche einem Speicherleck zuzuordnen sind, kann man den Problemen der formalen Verifikation entgehen.
Ein bekanntes solches Werkzeug (auf Linux-Systemen) ist memcheck von Valgrind. Es führt eine Liste der allozierten Speicherbereiche mit und an welcher Stelle im Programm die Allokation stattfand. Bei Programmende überprüft es, ob alle allozierte Bereiche auch wieder freigegeben wurden. In sehr einfachen oder sehr gravierenden Fällen kann es ausreichen, nur den Speicherverbrauch eines Prozesses im zeitlichen Verlauf zu beobachten.
RAII
Eine Programmiertechnik, die Speicherlecks verhindern kann, ist RAII (Ressourcenbelegung ist Initialisierung). Hierbei wird der Speicherbereich einer Variablen bei ihrer Initialisierung reserviert und beim Verlassen ihres Gültigkeitsbereichs automatisch wieder freigegeben. Das hat gegenüber der automatischen Speicherverwaltung den Vorteil, dass die „Lebensdauer“ eines Objekts in der Regel genau bekannt ist.
RAII schützt nicht in jedem Fall vor Speicherlecks zweiter Art.
Formale Verifikation
Durch einen Korrektheitsbeweis können insbesondere auch Speicherlecks entdeckt werden. Dieses Verfahren ist jedoch sehr zeitaufwendig und benötigt Expertenwissen. Die auf Speicherlecks spezialisierte Frage kann auch computergestützt mittels Statischer Code-Analyse untersucht werden.
Beispiele
C
Das bekannteste Beispiel für fehlende automatische Speicherverwaltung ist die Sprache C. Neuer Speicher wird hier durch Funktionen wie malloc angefordert. Dabei liefern diese einen Zeiger auf den Anfang des entsprechenden Speicherbereichs. Der Verweis ist notwendig, um die Zuweisung zu identifizieren und sie mittels geeignetem Code wieder freizugeben (der Funktion free) oder nachträglich zu modifizieren. Geht der Zeiger verloren, dann kann der Prozess nicht mehr auf diesen Speicher zugreifen und ihn damit auch nicht freigeben, bzw. während seiner Laufzeit erneut verwenden. Ein verwandtes Problem besteht, wenn der Zeiger verändert wird, sowie umgekehrt, wenn der Speicher fälschlicherweise mehrfach deallokiert wird.
Das folgende Beispiel zeigt die Entstehung solch eines Speicherlecks:
#include <stdlib.h>
int main(void)
{
int *a; /* Zeiger auf einen als Ganzzahl interpretierten Speicherbereich */
/* Speicher für Zeiger reservieren. */
a = malloc(sizeof(int));
/* ... */
a = malloc(sizeof(int)); /* Zeiger auf zuvor allokierten Speicher wird überschrieben. */
free(a); /* Nur der zweite Speicherblock wird freigegeben --> Speicherleck */
return EXIT_SUCCESS;
}
Ab dem zweiten a = malloc()
ist es nicht mehr möglich, auf den Speicherbereich zuzugreifen, auf den a zuvor verwies. Der Bereich kann dann regulär nicht mehr vom Programm freigegeben werden.
Automatische Speicherbereinigung
Das folgende Beispiel in Java zeigt, dass alleine der Ansatz der automatischen Speicherbereinigung nicht reicht, um Speicherlecks aufzudecken:
private static List<Integer> nummern = new ArrayList<>();
public void erzeugeSpeicherleck() {
for (int i=1; i<10000; i++)
nummern.add(i);
}
// kein weiterer lesender Zugriff auf die List nummern
Man erkennt hier, dass der Speicherbedarf ständig anwächst, es handelt sich also um ein Speicherleck, da kein lesender Zugriff mehr auf die Listen-Einträge erfolgt. Dieser Fehler wäre recht leicht durch statische Analyse zu erkennen, wohingegen der Graph aus Referenzen und Speicherbereichen den Fehler nicht erkennen lässt.