Programiranje

Diagnosticiranje in reševanje StackOverflowError

Nedavno sporočilo foruma skupnosti JavaWorld (Stack Overflow po namestitvi novega predmeta) me je opozorilo, da ljudje, ki so novi na Javi, osnov StackOverflowError ne razumejo vedno dobro. Na srečo je StackOverflowError ena najlažjih napak med izvajanjem za odpravljanje napak in v tem objavljanju v spletnem dnevniku bom pokazal, kako enostavno je diagnosticirati StackOverflowError. Upoštevajte, da možnost prelivanja skladov ni omejena na Javo.

Diagnosticiranje vzroka StackOverflowError je lahko dokaj enostavno, če je bila koda prevedena z vključeno možnostjo odpravljanja napak, tako da so številke vrstic na voljo v nastali sledovi sklada. V takih primerih je običajno preprosto najti ponavljajoči se vzorec številk vrstic v sledu sklada. Vzorec ponavljanja številk vrstic je koristen, ker StackOverflowError pogosto povzroči nedokončana rekurzija. Ponavljajoče se številke vrstic označujejo kodo, ki jo neposredno ali posredno rekurzivno kličete. Upoštevajte, da obstajajo tudi primeri, ki niso neomejena rekurzija, v katerih lahko pride do prelivanja skladov, vendar je to objavljanje v spletnem dnevniku omejeno na StackOverflowError ki jih povzroča neomejena rekurzija.

Odnos rekurzije je bil slab StackOverflowError je zapisano v opisu Javadoc za StackOverflowError, ki navaja, da je ta napaka "Vržena, ko pride do prelivanja sklada, ker se aplikacija pregloboko ponovi." Pomembno je, da StackOverflowError konča z besedo Napaka in je napaka (razširi java.lang.Error prek java.lang.VirtualMachineError) in ne preverjena ali izvajalna izjema. Razlika je pomembna. The Napaka in Izjema so vsaka specializirana naprava za metanje, vendar je njihovo predvideno ravnanje povsem drugačno. Vadnica za Java poudarja, da so napake običajno zunanje za aplikacijo Java, zato jih aplikacija navadno ne more in ne sme zajeti ali obravnavati.

Pokazal bom nalet StackOverflowError prek neomejene rekurzije s tremi različnimi primeri. Koda, uporabljena za te primere, je v treh razredih, od katerih je prvi (in glavni razred) prikazan v nadaljevanju. Vse tri razrede naštejem v celoti, ker so številke vrstic pri odpravljanju napak pomembne StackOverflowError.

StackOverflowErrorDemonstrator.java

paket dustin.examples.stackoverflow; import java.io.IOException; import java.io.OutputStream; / ** * Ta razred prikazuje različne načine, kako se lahko * zgodi StackOverflowError. * / javni razred StackOverflowErrorDemonstrator {zasebni statični končni niz NEW_LINE = System.getProperty ("line.separator"); / ** Prostovoljni podatkovni član na osnovi niza. * / private String stringVar = ""; / ** * Preprost pripomoček, ki bo pokazal, da je nenamerna rekurzija pokvarjena. Ko se ta metoda enkrat pokliče, se bo večkrat poklicala. Ker za končanje rekurzije ni določenega * pogoja zaključka, je pričakovati * StackOverflowError. * * @return Spremenljivka niza. * / public String getStringVar () {// // OPOZORILO: // // To je LOŠO! To se bo rekurzivno poklicalo, dokler se // ne prelije in vrže StackOverflowError. Predvidena vrstica v // tem primeru bi morala biti: // return this.stringVar; vrni getStringVar (); } / ** * Izračunaj faktorijel podanega celega števila. Ta metoda temelji na * rekurziji. * * @param number Številka, katere faktorijel je zaželen. * @return Faktorska vrednost podane številke. * / public int calcuFactorial (končna int številka) {// OPOZORILO: To se bo slabo končalo, če je na voljo število, manjše od nič. // Tukaj je prikazan boljši način za to, vendar komentiran. // vrne številko <= 1? 1: število * izračunaj Faktorial (število-1); vrnitev številka == 1? 1: število * izračunaj Faktorial (število-1); } / ** * Ta metoda prikazuje, kako nenamerna rekurzija pogosto vodi do * StackOverflowError, ker za nenamerno rekurzijo ni predviden noben pogojni pogoj. * / public void runUnintentionalRecursionExample () {final String unusedString = this.getStringVar (); } / ** * Ta metoda prikazuje, kako lahko nenamerna rekurzija kot del ciklične * odvisnosti povzroči StackOverflowError, če je ne upoštevamo natančno. * / public void runUnintentionalCyclicRecusionExample () {final State newMexico = State.buildState ("New Mexico", "NM", "Santa Fe"); System.out.println ("Novo zgrajena država je:"); System.out.println (newMexico); } / ** * Prikazuje, kako lahko celo predvidena rekurzija povzroči StackOverflowError *, ko končni pogoj rekurzivne funkcionalnosti nikoli ni * izpolnjen. * / public void runIntentionalRecursiveWithDysfunctionalTermination () {končno int številoForFactorial = -1; System.out.print ("Faktor" + numberForFactorial + "je:"); System.out.println (izračunajFactorial (številoForFactorial)); } / ** * V priloženi OutputStream napišite glavne možnosti tega razreda. * * @param ven OutputStream, kamor želite zapisati možnosti te testne aplikacije. * / public static void writeOptionsToStream (končni OutputStream out) {final String option1 = "1. Nenamerna (brez pogoja zaključka) enojna metoda rekurzija"; final String option2 = "2. nenamerna (brez pogoja zaključka) ciklična rekurzija"; final String option3 = "3. Nepravilna zaključna rekurzija"; poskusite {out.write ((možnost1 + NEW_LINE) .getBytes ()); out.write ((možnost2 + NEW_LINE) .getBytes ()); out.write ((možnost3 + NEW_LINE) .getBytes ()); } catch (IOException ioEx) {System.err.println ("(Ne morem zapisati v zagotovljeni OutputStream)"); System.out.println (možnost1); System.out.println (možnost2); System.out.println (možnost3); }} / ** * Glavna funkcija za zagon StackOverflowErrorDemonstrator. * / public static void main (final String [] argumentov) {if (argument.length <1) {System.err.println ("Navesti morate argument in ta en argument mora biti"); System.err.println ("ena od naslednjih možnosti:"); writeOptionsToStream (System.err); System.exit (-1); } int možnost = 0; poskusite {option = Integer.valueOf (argumenti [0]); } catch (NumberFormatException notNumericFormat) {System.err.println ("Vnesli ste neštevilsko (neveljavno) možnost [" + argumenti [0] + "]"); writeOptionsToStream (System.err); System.exit (-2); } končni StackOverflowErrorDemonstrator me = nov StackOverflowErrorDemonstrator (); stikalo (možnost) {primer 1: me.runUnintentionalRecursionExample (); odmor; 2. primer: me.runUnintentionalCyclicRecusionExample (); odmor; primer 3: me.runIntentionalRecursiveWithDysfunctionalTermination (); odmor; privzeto: System.err.println ("Navedli ste nepričakovano možnost [" + možnost + "]"); }}} 

Zgornji razred prikazuje tri vrste neomejene rekurzije: naključna in popolnoma nenamerna rekurzija, nenamerna rekurzija, povezana z namerno cikličnimi odnosi, in predvidena rekurzija z nezadostnimi zaključnimi pogoji. Vsako od teh in njihov rezultat bomo obravnavali v nadaljevanju.

Popolnoma nenamerna rekurzija

Včasih pride do rekurzije brez kakršnega koli namena. Pogost vzrok je lahko, da se metoda slučajno pokliče. Na primer, ni preveč težko postati nekoliko preveč nepreviden in izbrati prvo priporočilo IDE-ja za povratno vrednost za metodo "get", ki bi na koncu lahko bila klic te iste metode! To je pravzaprav primer, prikazan v zgornjem razredu. The getStringVar () metoda se večkrat pokliče, dokler se StackOverflowError se sreča. Izhod se bo prikazal na naslednji način:

Izjema v niti "main" java.lang.StackOverflowError pri dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstraverEstrorDemonststververstErrorDemonststr. stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) pri dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) pri dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) pri dustin.examples .stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) na dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) pri dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) pri dusti n.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) ob 

Zgoraj prikazana sled sklada je sicer velikokrat daljša od tiste, ki sem jo postavil zgoraj, vendar gre preprosto za isti ponavljajoči se vzorec. Ker se vzorec ponavlja, je enostavno diagnosticirati, da je vrstica 34 razreda povzročitelj težav. Ko pogledamo to vrstico, vidimo, da je to res izjava vrni getStringVar () ki se na koncu večkrat pokliče. V tem primeru lahko hitro ugotovimo, da je namesto tega namenjeno vedenje vrni this.stringVar;.

Nenamerna rekurzija s cikličnimi odnosi

Obstajajo določena tveganja za ciklično razmerje med razredi. Eno od teh tveganj je večja verjetnost, da bo prišlo do nenamerne rekurzije, kjer se ciklične odvisnosti med predmeti neprestano kličejo, dokler sklad ne preplavi. Za dokazovanje tega uporabim še dva predavanja. The Država razred in Mesto razreda imajo ciklične relacijehiop, ker a Država primer sklicuje na svoj kapital Mesto in a Mesto se sklicuje na Država v katerem se nahaja.

State.java

paket dustin.examples.stackoverflow; / ** * Razred, ki predstavlja državo in je namerno del cikličnega * odnosa med mestom in državo. * / stanje javnega razreda {private static final String NEW_LINE = System.getProperty ("line.separator"); / ** Ime države. * / ime zasebnega niza; / ** Dvočrkovna okrajšava za državo. * / private String okrajšava; / ** Mesto, ki je glavno mesto države. * / zasebni City capitalCity; / ** * Metoda statičnega graditelja, ki je predvidena metoda za namestitev mojega primera. * * @param newName Ime nove instancirane države. * @param newAbbreviation Dvočrkovna okrajšava države. * @param newCapitalCityName Ime glavnega mesta. * / javno statično stanje buildState (končni niz newName, končni niz newAbbreviation, končni niz newCapitalCityName) {končni primerek države = novo stanje (newName, newAbbreviation); instance.capitalCity = novo mesto (newCapitalCityName, primerek); primerek vrnitve; } / ** * Parametrizirani konstruktor sprejema podatke za zapolnitev novega primerka države. * * @param newName Ime na novo vzpostavljene države. * @param newAbbreviation Dvočrkovna okrajšava države. * / private State (končni niz newName, končni niz newAbbreviation) {this.name = newName; this.abbreviation = newAbbreviation; } / ** * Zagotovite nizno predstavitev državne instance. * * @ vrni predstavitev mojega niza. * / @Override javni niz toString () {// OPOZORILO: To se bo slabo končalo, ker implicitno pokliče Cityjevo toString () // metodo, Cityjeva toString () pa to // State.toString () metodo. vrni "StateName:" + this.name + NEW_LINE + "StateAbbreviation:" + this.abbreviation + NEW_LINE + "CapitalCity:" + this.capitalCity; }} 

Mesto.java

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