Nicht reagierende Apps debuggen

Andere Sprachen: English Español Français 日本語 한국어 Português 中文

Es gibt viele Debugger-Tutorials, die dir beibringen, wie man Zeilen-Breakpoints setzt, Werte protokolliert oder Ausdrucke auswertet. Obwohl dieses Wissen allein dir viele Werkzeuge zum Debuggen deiner Anwendung bietet, können reale Szenarien etwas kniffliger sein und einen fortgeschritteneren Ansatz erfordern.

In diesem Artikel werden wir lernen, wie man Code lokalisiert, der ein UI-Einfrieren verursacht, ohne viel Vorwissen über das Projekt zu haben, und fehlerhaften Code im laufenden Betrieb korrigiert.

Das Problem

Wenn du mitmachen möchtest, beginne mit dem Klonen dieses Repositories: https://github.com/flounder4130/debugger-example

Angenommen, du hast eine komplexe Anwendung, die hängt, wenn du eine bestimmte Aktion ausführst. Du weißt, wie der Fehler reproduziert werden kann, aber die Schwierigkeit besteht darin, dass du nicht weißt, welcher Teil des Codes für diese Funktionalität zuständig ist.

Die Benutzeroberfläche der Beispielanwendung hat viele Schaltflächen, um verschiedene Aktionen auszuführen.

In unserer Beispiel-App passiert das Hängen, wenn du auf Button N klickst. Es ist jedoch nicht so einfach, den Code zu finden, der für diese Aktion verantwortlich ist:

Die Suche nach Button N im gesamten Projekt ergibt keine Ergebnisse. Die Suche nach Button N im gesamten Projekt ergibt keine Ergebnisse.

Schauen wir uns an, wie wir den Debugger verwenden können, um ihn zu finden.

Methoden-Breakpoints

Der Vorteil von Methoden-Breakpoints gegenüber Zeilen-Breakpoints ist, dass sie auf ganze Klassenhierarchien angewendet werden können. Wie ist das in unserem Fall nützlich?

Wenn du dir das Beispielprojekt anschaust, wirst du sehen, dass alle Action-Klassen von der Action -Schnittstelle mit einer einzigen Methode abgeleitet sind: perform() .

Methoden-Haltepunkt-Icon im Editor-Gutter Methoden-Haltepunkt-Icon im Editor-Gutter

Das Setzen eines Methoden-Breakpoints in dieser Interface-Methode wird die Anwendung anhalten, wann immer eine der abgeleiteten Methoden aufgerufen wird. Um einen Methoden-Breakpoint zu setzen, klicke auf die Zeile, die die Methode deklariert.

Starte die Debugger-Sitzung und klicke auf Button N. Die Anwendung wird in ActionImpl14 angehalten. Jetzt wissen wir, wo sich der Code für diesen Button befindet.

Die Anwendung wurde in einer Klasse, die das Action-Interface implementiert, ausgesetzt. Die Anwendung wurde in einer Klasse, die das Action-Interface implementiert, ausgesetzt.

Obwohl wir uns in diesem Artikel auf das Finden des Bugs konzentrieren, kann diese Technik dir auch viel Zeit sparen, wenn du verstehen möchtest, wie etwas in einer großen Codebasis funktioniert.

Anwendung pausieren

Der Ansatz mit Methoden-Breakpoints funktioniert gut, aber er basiert auf der Annahme, dass wir etwas über die übergeordnete Schnittstelle wissen. Was, wenn diese Annahme falsch ist, oder wir diesen Ansatz aus einem anderen Grund nicht verwenden können?

Nun, wir können es sogar ohne Breakpoints machen. Klicke auf Button N, und während die Anwendung hängt, gehe zu IntelliJ IDEA. Wähle im Hauptmenü Run | Debugging Actions | Pause Program.

Der Aufrufstack für den Hauptthread zeigt, was er gerade tut. Der Aufrufstack für den Hauptthread zeigt, was er gerade tut.

Die Anwendung wird angehalten, sodass wir den aktuellen Zustand der Threads im Tab Threads & Variables untersuchen können. Dies gibt uns ein Verständnis davon, was die Anwendung gerade tut. Da sie hängt, können wir die hängende Methode identifizieren und zum Aufrufpunkt zurückverfolgen.

Dieser Ansatz hat einige Vorteile gegenüber einem traditionelleren Thread-Dump, den wir gleich behandeln werden. Zum Beispiel liefert er Informationen über Variablen in einer praktischen Form und ermöglicht es dir, die weitere Ausführung des Programms zu steuern.

Thread-Dumps

Schließlich können wir einen Thread-Dump verwenden, was streng genommen keine Debugger-Funktion ist. Er ist verfügbar, unabhängig davon, ob du den Debugger verwendest.

Klicke auf Button N. Während die Anwendung hängt, gehe zu IntelliJ IDEA. Wähle im Hauptmenü Run | Debugging Actions | Get Thread Dump.

Durchsuche die verfügbaren Threads auf der linken Seite, und in AWT-EventQueue wirst du sehen, was das Problem verursacht.

Thread-Dump-Viewer in IntelliJ IDEA Thread-Dump-Viewer in IntelliJ IDEA

Der Nachteil von Thread-Dumps ist, dass sie nur eine Momentaufnahme des Programmzustands zum Zeitpunkt ihrer Erstellung liefern. Du kannst Thread-Dumps nicht verwenden, um Variablen zu untersuchen oder die Programmausführung zu steuern.

In unserem Beispiel müssen wir nicht auf einen Thread-Dump zurückgreifen. Ich wollte diese Technik jedoch erwähnen, da sie in anderen Fällen nützlich sein kann, z.B. wenn du versuchst, eine Anwendung zu debuggen, die ohne den Debug-Agenten gestartet wurde.

Das Problem verstehen

Unabhängig von der Debugging-Technik kommen wir bei ActionImpl14 an. In dieser Klasse hatte jemand beabsichtigt, die Arbeit in einem separaten Thread auszuführen, aber verwechselte Thread.start() mit Thread.run() , was den Code im selben Thread wie den aufrufenden Code ausführt.

Der statische Analyzer von IntelliJ IDEA warnt uns sogar zur Entwurfszeit davor:

Die statische Analyse von IntelliJ IDEA gibt eine Warnung über einen verdächtigen Aufruf von Thread.run() aus. Die statische Analyse von IntelliJ IDEA gibt eine Warnung über einen verdächtigen Aufruf von Thread.run() aus.

Eine Methode, die schwere Arbeit leistet (oder in diesem Fall viel schläft), wird im UI-Thread aufgerufen und blockiert ihn, bis die Methode beendet ist. Deshalb können wir nach dem Klick auf Button N eine Zeit lang nichts in der UI tun.

HotSwap

Nachdem wir die Ursache des Bugs entdeckt haben, beheben wir das Problem.

Wir könnten das Programm stoppen, den Code neu kompilieren und dann erneut ausführen. Es ist jedoch nicht immer praktisch, die gesamte Anwendung nur wegen einer kleinen Änderung neu zu deployen.

Machen wir es auf die clevere Art. Korrigiere zunächst den Code mit dem vorgeschlagenen Quick-Fix:

Das Kontextmenü (Alt-Eingabe) bietet eine Option zur Behebung des verdächtigen Codes. Das Kontextmenü (Alt-Eingabe) bietet eine Option zur Behebung des verdächtigen Codes.

Nachdem der Code bereit ist, klicke auf Run | Debugging Actions | Reload Changed Classes. Ein Balloon erscheint und bestätigt, dass der neue Code seinen Weg in die VM gefunden hat.

Ein Ballon bestätigt, dass aktualisierte Klassen ihren Weg zur Laufzeit gefunden haben. Ein Ballon bestätigt, dass aktualisierte Klassen ihren Weg zur Laufzeit gefunden haben.

Gehen wir zurück zur Anwendung und prüfen. Das Klicken auf Button N lässt die App nicht mehr hängen.

Tip icon

Beachte, dass HotSwap seine Einschränkungen hat. Wenn du an erweiterten HotSwap-Fähigkeiten interessiert bist, könnte es eine gute Idee sein, einen Blick auf fortgeschrittene Werkzeuge wie DCEVM oder JRebel zu werfen

Zusammenfassung

Mit unserer Überlegung und ein paar Debugger-Funktionen konnten wir den Code lokalisieren, der ein UI-Einfrieren in unserem Projekt verursachte. Dann haben wir den Code korrigiert, ohne Zeit für Neukompilierung und erneutes Deployment zu verschwenden, was bei realen Projekten langwierig sein kann.

Ich hoffe, du findest die beschriebenen Techniken hilfreich. Lass mich wissen, was du denkst!

Bleib dran für mehr!

all posts ->