Programmering

Diagnostisering og løsning av StackOverflowError

En nylig JavaWorld-fellesskapsforummelding (Stack Overflow after instantiating new object) minnet meg om at det grunnleggende om StackOverflowError ikke alltid forstås godt av folk som er nye for Java. Heldigvis er StackOverflowError en av de enkleste av kjøretidsfeilene å feilsøke, og i dette blogginnlegget vil jeg demonstrere hvor enkelt det ofte er å diagnostisere en StackOverflowError. Merk at potensialet for stackoverløp ikke er begrenset til Java.

Diagnostisering av årsaken til en StackOverflowError kan være ganske rettferdig hvis koden er kompilert med feilsøkingsalternativet slått på slik at linjenumre er tilgjengelige i den resulterende stabelsporingen. I slike tilfeller handler det vanligvis bare om å finne det gjentatte mønsteret for linjenumre i stabelsporet. Mønsteret for gjentatte linjenumre er nyttig fordi en StackOverflowError ofte er forårsaket av ubestemt rekursjon. De gjentatte linjenumrene indikerer koden som kalles direkte eller indirekte rekursivt. Vær oppmerksom på at det er andre situasjoner enn ubegrenset rekursjon der det kan oppstå en stabeloverløp, men dette blogginnlegget er begrenset til StackOverflowError forårsaket av ubegrenset rekursjon.

Forholdet til rekursjon ble dårlig til StackOverflowError er bemerket i Javadoc-beskrivelsen for StackOverflowError som sier at denne feilen er "Kastet når en stabeloverløp oppstår fordi et program gjentas for dypt." Det er viktig at StackOverflowError ender med ordet Feil og er en feil (utvider java.lang.Error via java.lang.VirtualMachineError) i stedet for et unntatt kontrollert eller kjøretid. Forskjellen er betydelig. De Feil og Unntak er hver en spesialisert Throwable, men den tiltenkte håndteringen er ganske annerledes. Java Tutorial viser til at feil vanligvis er eksterne for Java-applikasjonen og dermed normalt ikke kan og ikke skal fanges opp eller håndteres av applikasjonen.

Jeg vil demonstrere å løpe inn i StackOverflowError via ubegrenset rekursjon med tre forskjellige eksempler. Koden som brukes for disse eksemplene, finnes i tre klasser, hvorav den første (og hovedklassen) vises neste. Jeg lister opp alle tre klassene i sin helhet fordi linjenumre er viktige når vi feilsøker StackOverflowError.

StackOverflowErrorDemonstrator.java

pakke dustin.examples.stackoverflow; importere java.io.IOException; importere java.io.OutputStream; / ** * Denne klassen viser forskjellige måter som en StackOverflowError kan * oppstå. * / public class StackOverflowErrorDemonstrator {private static final String NEW_LINE = System.getProperty ("line.separator"); / ** Vilkårlig strengbasert datamedlem. * / private String stringVar = ""; / ** * Enkel tilgang som viser utilsiktet rekursjon blitt dårlig. Når * påkalt, vil denne metoden gjentatte ganger kalle seg selv. Fordi det ikke er noen * spesifisert avslutningsbetingelse for å avslutte rekursjonen, kan det forventes en * StackOverflowError. * * @return Strengvariabel. * / public String getStringVar () {// // ADVARSEL: // // Dette er DÅRLIG! Dette vil kalle seg rekursivt til stakken // overløper og en StackOverflowError blir kastet. Den tiltenkte linjen i // dette tilfellet burde vært: // returner this.stringVar; returner getStringVar (); } / ** * Beregn faktor for det angitte heltallet. Denne metoden er avhengig av * rekursjon. * * @paramnummer Nummeret som ønsket faktura er. * @return Faktorverdien til det oppgitte nummeret. * / public int calculatorFactorial (final int number) {// ADVARSEL: Dette vil ende dårlig hvis et tall som er mindre enn null er oppgitt. // En bedre måte å gjøre dette på vises her, men kommenterte. // returnummer <= 1? 1: nummer * beregneFaktor (nummer-1); retur nummer == 1? 1: nummer * beregneFaktorisk (nummer-1); } / ** * Denne metoden demonstrerer hvordan utilsiktet rekursjon ofte fører til * StackOverflowError fordi det ikke er gitt en avslutningsbetingelse for * den utilsiktede rekursjonen. * / public void runUnintentionalRecursionExample () {final String unusedString = this.getStringVar (); } / ** * Denne metoden viser hvordan utilsiktet rekursjon som en del av en syklisk * avhengighet kan føre til StackOverflowError hvis den ikke respekteres nøye. * / public void runUnintentionalCyclicRecusionExample () {final State newMexico = State.buildState ("New Mexico", "NM", "Santa Fe"); System.out.println ("Den nylig konstruerte staten er:"); System.out.println (newMexico); } / ** * Demonstrerer hvordan selv tiltenkt rekursjon kan resultere i en StackOverflowError * når den avsluttende tilstanden til den rekursive funksjonaliteten aldri * er oppfylt. * / public void runIntentionalRecursiveWithDysfunctionalTermination () {final int numberForFactorial = -1; System.out.print ("Faktoriet til" + numberForFactorial + "er:"); System.out.println (beregneFaktor (nummerForFaktor)); } / ** * Skriv hovedklassene til denne klassen til den medfølgende OutputStream. * * @param ut OutputStream som du kan skrive alternativene for dette testprogrammet til. * / public static void writeOptionsToStream (final OutputStream out) {final String option1 = "1. Utilsiktet (ingen avslutningsbetingelser) enkel metode rekursjon"; final String option2 = "2. Utilsiktet (ingen avslutningstilstand) syklisk rekursjon"; final String option3 = "3. Mislykket avslutning rekursjon"; 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 gitt OutputStream)"); System.out.println (alternativ1); System.out.println (alternativ2); System.out.println (option3); }} / ** * Hovedfunksjon for å kjøre StackOverflowErrorDemonstrator. * / public static void main (final String [] argumenter) {if (argumenter.lengde <1) {System.err.println ("Du må oppgi et argument og det eneste argumentet skal være"); System.err.println ("ett av følgende alternativer:"); writeOptionsToStream (System.err); System.exit (-1); } int alternativ = 0; prøv {option = Integer.valueOf (argumenter [0]); } catch (NumberFormatException notNumericFormat) {System.err.println ("Du skrev inn et ikke-numerisk (ugyldig) alternativ [" + argumenter [0] + "]"); writeOptionsToStream (System.err); System.exit (-2); } endelig StackOverflowErrorDemonstrator me = ny StackOverflowErrorDemonstrator (); switch (alternativ) {case 1: me.runUnintentionalRecursionExample (); gå i stykker; tilfelle 2: me.runUnintentionalCyclicRecusionExample (); gå i stykker; tilfelle 3: me.runIntentionalRecursiveWithDysfunctionalTermination (); gå i stykker; standard: System.err.println ("Du ga et uventet alternativ [" + alternativ + "]"); }}} 

Klassen ovenfor viser tre typer ubegrenset rekursjon: utilsiktet og helt utilsiktet rekursjon, utilsiktet rekursjon assosiert med bevisst sykliske forhold, og tiltenkt rekursjon med utilstrekkelig avslutningstilstand. Hver av disse og deres produksjon blir diskutert neste.

Fullstendig utilsiktet rekursjon

Det kan være tider når rekursjon skjer uten hensikt med det. En vanlig årsak kan være å ha en metode som ved et uhell kaller seg selv. For eksempel er det ikke for vanskelig å bli litt for uforsiktig og velge en IDEs første anbefaling om en returverdi for en "get" -metode som kan ende opp med å bli et kall til den samme metoden! Dette er faktisk eksemplet vist i klassen ovenfor. De getStringVar () metoden kaller seg gjentatte ganger til StackOverflowError er oppstått. Utgangen vises som følger:

Unntak i tråden "main" java.lang.StackOverflowError at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVarflow.arrorVarrowarStarrVar stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackflow.tackflow.stackflow.stackflow.teststrøm .stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examratorStackflowDemonstratorStarrVest n.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) på 

Bunnsporet vist ovenfor er faktisk mange ganger lengre enn det jeg plasserte over, men det er rett og slett det samme gjentakende mønsteret. Fordi mønsteret gjentas, er det lett å diagnostisere at linje 34 i klassen er den som forårsaker problemet. Når vi ser på den linjen, ser vi at det faktisk er uttalelsen returner getStringVar () som ender opp med å kalle seg gjentatte ganger. I dette tilfellet kan vi raskt innse at den tiltenkte oppførselen var å i stedet returner dette.stringVar;.

Utilsiktet rekursjon med sykliske forhold

Det er visse risikoer ved å ha sykliske forhold mellom klassene. En av disse risikoene er større sannsynlighet for å komme i utilsiktet rekursjon der de sykliske avhengighetene kontinuerlig kalles mellom objekter til stakken renner over. For å demonstrere dette bruker jeg to klasser til. De Stat klasse og By klasse har et syklisk relationshiop fordi a Stat forekomst har en referanse til hovedstaden By og en By har en referanse til Stat der den ligger.

State.java

pakke dustin.examples.stackoverflow; / ** * En klasse som representerer en stat og med vilje er en del av et syklisk * forhold mellom by og stat. * / public class State {private static final String NEW_LINE = System.getProperty ("line.separator"); / ** Statens navn. * / privat strengnavn; / ** To-bokstavs forkortelse for stat. * / privat strengforkortelse; / ** By som er hovedstaden i staten. * / privat byhovedstad; / ** * Statisk byggemetode som er den tiltenkte metoden for instantiering av meg. * * @param newName Navn på nylig innstiftet stat. * @param newForkortelse To-bokstavs forkortelse av staten. * @param newCapitalCityName Navn på hovedstaden. * / public static State buildState (final String newName, final String newAbbreviation, final String newCapitalCityName) {final State instance = new State (newName, newAbbreviation); instance.capitalCity = new City (newCapitalCityName, instance); returinstans; } / ** * Parameterisert konstruktør som godtar data for å fylle ut ny forekomst av staten. * * @param newName Navn på nylig innstiftet stat. * @param newForkortelse To-bokstavs forkortelse av staten. * / private State (final String newName, final String newAbbreviation) {this.name = newName; this.abbreviation = newAbbreviation; } / ** * Gi strengrepresentasjon av statlig forekomst. * * @return My String representasjon. * / @ Override public String toString () {// ADVARSEL: Dette vil ende dårlig fordi det kaller Citys toString () // -metode implisitt og Citys toString () -metode kaller denne // State.toString () -metoden. returner "StateName:" + this.name + NEW_LINE + "StateAbbreviation:" + this.abbreviation + NEW_LINE + "CapitalCity:" + this.capitalCity; }} 

City.java

$config[zx-auto] not found$config[zx-overlay] not found