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.
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:
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() .
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.
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.
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.
Für weitere Tipps und Tricks mit Pause Program siehe Debuggen ohne Breakpoints und Debugger.godMode() - JVM-Anwendungen mit dem Debugger hacken
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.
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:
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:
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.
Gehen wir zurück zur Anwendung und prüfen. Das Klicken auf Button N lässt die App nicht mehr hängen.
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!