Programmering

Diagnosticering og løsning af StackOverflowError

En nylig JavaWorld Community-forummeddelelse (Stack Overflow efter at have startet et nyt objekt) mindede mig om, at det grundlæggende i StackOverflowError ikke altid forstås godt af folk, der er nye til Java. Heldigvis er StackOverflowError en af ​​de nemmeste af runtime-fejlene at debugge, og i denne blogindlæg vil jeg demonstrere, hvor let det ofte er at diagnosticere en StackOverflowError. Bemærk, at potentialet for stackoverløb ikke er begrænset til Java.

Diagnosticering af årsagen til en StackOverflowError kan være ret ligefrem, hvis koden er blevet kompileret med debug-indstillingen aktiveret, så linjenumre er tilgængelige i det resulterende stakspor. I sådanne tilfælde er det typisk bare et spørgsmål om at finde det gentagne mønster af linjenumre i staksporet. Mønsteret for gentagne linjenumre er nyttigt, fordi en StackOverflowError ofte er forårsaget af uafsluttet rekursion. De gentagne linjenumre angiver den kode, der kaldes direkte eller indirekte rekursivt. Bemærk, at der er andre situationer end ubegrænset rekursion, hvor et stackoverløb kan forekomme, men dette blogindlæg er begrænset til StackOverflowError forårsaget af ubegrænset rekursion.

Forholdet mellem rekursion blev dårligt StackOverflowError bemærkes i Javadoc-beskrivelsen til StackOverflowError, der siger, at denne fejl er "Kastet, når et stackoverløb opstår, fordi et program gentages for dybt." Det er vigtigt, at StackOverflowError ender med ordet Fejl og er en fejl (udvider java.lang.Error via java.lang.VirtualMachineError) snarere end en kontrolleret eller runtime undtagelse. Forskellen er betydelig. Det Fejl og Undtagelse er hver især en specialudkastbar, men deres påtænkte håndtering er helt anderledes. Java Tutorial påpeger, at fejl typisk er eksterne for Java-applikationen og derfor normalt ikke kan og bør fanges eller håndteres af applikationen.

Jeg vil demonstrere at løbe ind i StackOverflowError via ubegrænset rekursion med tre forskellige eksempler. Koden, der anvendes til disse eksempler, findes i tre klasser, hvoraf den første (og hovedklassen) vises derefter. Jeg viser alle tre klasser i sin helhed, fordi linjenumre er betydningsfulde, når fejlretning af StackOverflowError.

StackOverflowErrorDemonstrator.java

pakke dustin.examples.stackoverflow; importere java.io.IOException; importere java.io.OutputStream; / ** * Denne klasse viser forskellige måder, som en StackOverflowError kan * forekomme. * / offentlig klasse StackOverflowErrorDemonstrator {privat statisk endelig streng NEW_LINE = System.getProperty ("line.separator"); / ** Vilkårlig strengbaseret datamedlem. * / private String stringVar = ""; / ** * Enkel accessor, der viser utilsigtet rekursion gået dårligt. Når * påkaldt, vil denne metode gentagne gange kalde sig selv. Da der ikke er nogen * specificeret afslutningsbetingelse for at afslutte rekursionen, kan der forventes en * StackOverflowError. * * @return Stringvariabel. * / public String getStringVar () {// // ADVARSEL: // // Dette er DÅRLIGT! Dette kalder sig selv rekursivt, indtil stakken // overløber, og en StackOverflowError kastes. Den tilsigtede linje i // denne sag skulle have været: // returner this.stringVar; returner getStringVar (); } / ** * Beregn faktor for det angivne heltal. Denne metode er afhængig af * rekursion. * * @param-nummer Det nummer, hvis faktura ønskes. * @return Faktorværdien af ​​det angivne nummer. * / public int calculatorFactorial (final int number) {// ADVARSEL: Dette slutter dårligt, hvis der er angivet et tal mindre end nul. // En bedre måde at gøre dette på vises her, men kommenteres. // return nummer <= 1? 1: antal * beregneFaktorisk (nummer-1); retur nummer == 1? 1: antal * beregneFaktorisk (nummer-1); } / ** * Denne metode demonstrerer, hvordan utilsigtet rekursion ofte fører til * StackOverflowError, fordi der ikke er nogen opsigelsesbetingelser for * utilsigtet rekursion. * / public void runUnintentionalRecursionExample () {final String unusedString = this.getStringVar (); } / ** * Denne metode viser, hvordan utilsigtet rekursion som en del af en cyklisk * afhængighed kan føre til StackOverflowError, hvis den ikke respekteres nøje. * / public void runUnintentionalCyclicRecusionExample () {final State newMexico = State.buildState ("New Mexico", "NM", "Santa Fe"); System.out.println ("Den nyopførte tilstand er:"); System.out.println (newMexico); } / ** * Viser, hvordan selv tilsigtet rekursion kan resultere i en StackOverflowError *, når den afsluttende tilstand for den rekursive funktionalitet aldrig * er opfyldt. * / public void runIntentionalRecursiveWithDysfunctionalTermination () {final int numberForFactorial = -1; System.out.print ("Faktoriet for" + nummerForFaktorisk + "er:"); System.out.println (beregneFaktor (nummerForFaktor)); } / ** * Skriv denne klasses hovedindstillinger til den leverede OutputStream. * * @param ud OutputStream, som denne testapplikations muligheder skal skrives til. * / public static void writeOptionsToStream (final OutputStream out) {final String option1 = "1. Utilsigtet (ingen opsigelsesbetingelse) single method recursion"; final String option2 = "2. Utilsigtet (ingen opsigelsesbetingelse) cyklisk rekursion"; final String option3 = "3. Fejlbehæftet opsigelsesrekursion"; prøv {out.write ((option1 + NEW_LINE) .getBytes ()); out.write ((option2 + NEW_LINE) .getBytes ()); out.write ((option3 + NEW_LINE) .getBytes ()); } fange (IOException ioEx) {System.err.println ("(Kan ikke skrive til forudsat OutputStream)"); System.out.println (option1); System.out.println (option2); System.out.println (option3); }} / ** * Hovedfunktion til kørsel af StackOverflowErrorDemonstrator. * / public static void main (final String [] argumenter) {if (argumenter.længde <1) {System.err.println ("Du skal angive et argument, og det eneste argument skal være"); System.err.println ("en af ​​følgende muligheder:"); writeOptionsToStream (System.err); System.exit (-1); } int option = 0; prøv {option = Integer.valueOf (argumenter [0]); } fange (NumberFormatException notNumericFormat) {System.err.println ("Du indtastede en ikke-numerisk (ugyldig) mulighed [" + argumenter [0] + "]"); writeOptionsToStream (System.err); System.exit (-2); } sidste StackOverflowErrorDemonstrator mig = ny StackOverflowErrorDemonstrator (); switch (option) {case 1: me.runUnintentionalRecursionExample (); pause; tilfælde 2: me.runUnintentionalCyclicRecusionExample (); pause; tilfælde 3: me.runIntentionalRecursiveWithDysfunctionalTermination (); pause; standard: System.err.println ("Du har givet en uventet mulighed [" + option + "]"); }}} 

Klassen ovenfor demonstrerer tre typer ubegrænset rekursion: utilsigtet og helt utilsigtet rekursion, utilsigtet rekursion forbundet med bevidst cykliske forhold og tilsigtet rekursion med utilstrækkelig opsigelsesbetingelse. Hver af disse og deres output diskuteres derefter.

Helt utilsigtet rekursion

Der kan være tidspunkter, hvor rekursion finder sted uden nogen som helst hensigt om det. En almindelig årsag kan være at have en metode ved et uheld at kalde sig selv. For eksempel er det ikke for svært at blive lidt for skødesløs og vælge en IDEs første anbefaling om en returværdi til en "get" -metode, der muligvis ender med at blive et opkald til den samme metode! Dette er faktisk eksemplet vist i klassen ovenfor. Det getStringVar () metoden gentagne gange kalder sig, indtil StackOverflowError er stødt på. Outputtet vises som følger:

Undtagelse i tråden "main" java.lang.StackOverflowError at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVarator. stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) ved dustin.examples.stackflow.tackflow.stackflow.stackflow.stackflow.starrflow .stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) ved dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examrStarrVeststrømstrømstrømstrøm n.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) ved 

Staksporet vist ovenfor er faktisk mange gange længere end det, jeg placerede ovenfor, men det er simpelthen det samme gentagne mønster. Fordi mønsteret gentager sig, er det let at diagnosticere, at linie 34 i klassen er årsagsproblemet. Når vi ser på denne linje, ser vi, at det faktisk er udsagnet returner getStringVar () det ender med at gentagne gange kalde sig selv. I dette tilfælde kan vi hurtigt indse, at den tilsigtede adfærd i stedet var returner denne.stringVar;.

Utilsigtet rekursion med cykliske forhold

Der er visse risici ved at have cykliske forhold mellem klasser. En af disse risici er større sandsynlighed for at løbe ind i utilsigtet rekursion, hvor de cykliske afhængigheder konstant kaldes mellem objekter, indtil stakken løber over. For at demonstrere dette bruger jeg yderligere to klasser. Det Stat klasse og By klasse har et cyklisk relationshiop, fordi en Stat instans har en henvisning til sin kapital By og en By har en henvisning til Stat hvor den er placeret.

Stat.java

pakke dustin.examples.stackoverflow; / ** * En klasse, der repræsenterer en stat og med vilje er en del af et cyklisk * forhold mellem by og stat. * / public class State {private static final String NEW_LINE = System.getProperty ("line.separator"); / ** Statens navn. * / privat strengnavn; / ** To bogstaver forkortelse for stat. * / privat strengforkortelse; / ** By, der er hovedstaden i staten. * / privat byhovedstad; / ** * Statisk byggemetode, der er den tilsigtede metode til instantiering af mig. * * @param newName Navn på nyligt instantieret stat. * @param newAbbreviation Forkortelse af to bogstaver for staten. * @param newCapitalCityName Hovedstadens navn. * / public static State buildState (final String newName, final String newAbbreviation, final String newCapitalCityName) {final State instance = new State (newName, newAbbreviation); eksempel.capitalCity = ny by (newCapitalCityName, forekomst); returinstans } / ** * Parameteriseret konstruktør, der accepterer data for at udfylde den nye forekomst af staten. * * @param newName Navn på nyligt instantieret stat. * @param newAbbreviation Forkortelse af to bogstaver for staten. * / privat stat (endelig streng nyt navn, endelig streng ny forkortelse) {dette.navn = nyt navn; this.abbreviation = newAbbreviation; } / ** * Angiv strengrepræsentation af statsinstansen. * * @retur Min strengrepræsentation. * / @Override public String toString () {// ADVARSEL: Dette ender dårligt, fordi det kalder Citys toString () // metode implicit og Citys toString () metode kalder denne // State.toString () metode. returner "StateName:" + this.name + NEW_LINE + "StateAbbreviation:" + this.abbreviation + NEW_LINE + "CapitalCity:" + this.capitalCity; }} 

By.java