Speicherleck

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

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[Bearbeiten]

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. Das kann auch zum sog. Thrashing führen, wobei ständig Seiten in den Hintergrundspeicher aus und wieder eingelagert werden. 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 allokieren und es kommt zu einem Programmfehler. Im schlimmsten Fall kann 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[Bearbeiten]

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 Speicher zu allokieren fehlschugen. 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]

Um Speicherlecks aufzuspüren existieren mehrere Möglichkeiten:

  1. Analyse der Referenzen auf Speicherbereiche (z. B. Smart Pointer).
  2. Analyse des Quelltext, ähnlich der formalen Verifikation.
  3. Analyse konkreter Laufzeit-Situationen im Rahmen eines Software-Tests.

Automatische Speicherbereinigung[Bearbeiten]

Falls die verwendete Laufzeitumgebung eine automatische Speicherbereinigung bereitstellt, versucht diese zu ermitteln, auf welche Speicherbereiche ein Prozess nicht mehr zugreift. 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 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[Bearbeiten]

Im Gegensatz zur automatischen Speicherbereinigung muss der Anwendungsentwickler bei einer manuellen Speicherverwaltung explizit dynamisch 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[Bearbeiten]

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 allokierten Speicherbereiche mit und an welcher Stelle im Programm die Allokation stattfand. Bei Programmende überprüft es, ob alle allokierte 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[Bearbeiten]

Eine Programmiertechnik die Speicherlecks verhindern kann ist RAII (Ressourcenbelegung ist Initialisierung). Hierbei ist 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 i. d. R. genau bekannt ist.

Formale Verifikation[Bearbeiten]

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 untersucht werden.

Beispiele[Bearbeiten]

C[Bearbeiten]

Das bekannteste Beispiel für fehlende automatische Speicherverwaltung ist die Sprache C. Neuer Speicher wird in C-Programmen durch die Funktion malloc angefordert. Dabei liefert malloc einen Zeiger auf den Anfang des entsprechenden Speicherbereichs. Dieser Verweis ist notwendig, um den Speicher mittels geeignetem Code wieder freizugeben (mittels der Funktion free). Geht der Zeiger verloren oder wird verändert, kann der Prozess nicht mehr auf diesen Speicher zugreifen und ihn damit auch nicht freigeben.

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 allozierten 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[Bearbeiten]

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.

Einzelnachweise[Bearbeiten]

  1. http://linux-mm.org/OOM_Killer

Weblinks[Bearbeiten]