Programmēšana

StackOverflowError diagnosticēšana un novēršana

Nesenais JavaWorld kopienas foruma ziņojums (kaudzes pārpilde pēc jauna objekta izveides) man atgādināja, ka StackOverflowError pamati ne vienmēr ir labi saprotami Java lietotājiem. Par laimi, StackOverflowError ir viena no vieglākām runtime kļūdām atkļūdot, un šajā emuāra ierakstā es parādīšu, cik viegli bieži ir diagnosticēt StackOverflowError. Ņemiet vērā, ka kaudzes pārpildes iespēja nav tikai Java.

StackOverflowError cēloņa diagnosticēšana var būt diezgan vienkārša, ja kods ir sastādīts, ieslēdzot atkļūdošanas opciju, lai rezultātā kaudzes izsekojumā būtu pieejami līniju numuri. Šādos gadījumos parasti vienkārši ir jāatrod rindu numuru atkārtojošais modelis kaudzes izsekojumā. Rindu numuru atkārtošanas modelis ir noderīgs, jo StackOverflowError bieži izraisa nebeidzama rekursija. Atkārtojas rindas numuri norāda kodu, kas tiek tieši vai netieši rekursīvi izsaukts. Ņemiet vērā, ka pastāv situācijas, kas nav neierobežota rekursija, kurās var rasties kaudzes pārpilde, taču šī emuāra izlikšana ir ierobežota ar StackOverflowError ko izraisa neierobežota rekursija.

Rekursijas attiecības ir kļuvušas sliktas StackOverflowError ir atzīmēts StackOverflowError aprakstā Javadoc, kurā teikts, ka šī kļūda tiek "izmesta, kad rodas kaudzes pārpilde, jo lietojumprogramma atkārtojas pārāk dziļi". Zīmīgi, ka StackOverflowError beidzas ar vārdu Kļūda un ir kļūda (paplašina java.lang.Error caur java.lang.VirtualMachineError), nevis pārbaudītu vai izpildlaika izņēmumu. Atšķirība ir ievērojama. The Kļūda un Izņēmums katrs no tiem ir specializēts izmetams, taču to paredzētā apstrāde ir diezgan atšķirīga. Java apmācība norāda, ka kļūdas parasti nav Java lietojumprogrammas, un tāpēc parasti tās nevar un nevajadzētu uztvert vai rīkoties ar lietojumprogrammu.

Es demonstrēšu ieskriešanos StackOverflowError izmantojot neierobežotu rekursiju ar trim dažādiem piemēriem. Šiem piemēriem izmantotais kods ir iekļauts trīs klasēs, no kurām pirmā (un galvenā klase) tiek parādīta nākamajā. Es uzskaita visas trīs klases kopumā, jo atkļūdojot., Rindu numuriem ir liela nozīme StackOverflowError.

StackOverflowErrorDemonstrator.java

pakete dustin.examples.stackoverflow; importēt java.io.IOException; importēt java.io.OutputStream; / ** * Šī klase parāda dažādus veidus, kā var rasties StackOverflowError *. * / public class StackOverflowErrorDemonstrator {private static final String NEW_LINE = System.getProperty ("line.separator"); / ** Patvaļīgs virknes datu dalībnieks. * / privāta virkne stringVar = ""; / ** * Vienkāršs piekļuvējs, kas parādīs nejaušu rekursiju. Pēc * izsaukšanas šī metode atkārtoti sauks sevi. Tā kā rekursijas izbeigšanai nav * norādītā izbeigšanas nosacījuma, ir sagaidāms * StackOverflowError. * * @return String mainīgais. * / public String getStringVar () {// // BRĪDINĀJUMS: // // Tas ir SLIKTI! Tas rekursīvi sauks sevi, līdz kaudze // pārplūst un tiek izmesta StackOverflowError. Paredzētajai rindai // šajā gadījumā vajadzēja būt: // return this.stringVar; atgūt getStringVar (); } / ** * Aprēķiniet norādītā veselā skaitļa faktoriālu. Šī metode balstās uz * rekursiju. * * @param number Skaitlis, kura faktoriāls ir vēlams. * @return Norādītā numura faktoriālā vērtība. * / public int calcFactorial (galīgais int numurs) {// BRĪDINĀJUMS: Tas beigsies slikti, ja tiks norādīts skaitlis, kas mazāks par nulli. // Šeit ir parādīts labāks veids, kā to izdarīt, bet komentēts. // atgriešanās numurs <= 1? 1: skaitlis * aprēķinātFaktoriāls (skaitlis-1); atgriešanās numurs == 1? 1: skaitlis * aprēķinātFaktoriāls (skaitlis-1); } / ** * Šī metode parāda, kā neparedzēta rekursija bieži noved pie * StackOverflowError, jo * neparedzētai rekursijai nav paredzēts pārtraukšanas nosacījums. * / public void runUnintentionalRecursionExample () {final String unusedString = this.getStringVar (); } / ** * Šī metode parāda, kā neparedzēta rekursija kā daļa no cikliskas * atkarības var izraisīt StackOverflowError, ja tā netiek rūpīgi ievērota. * / public void runUnintentionalCyclicRecusionExample () {final State newMexico = State.buildState ("New Mexico", "NM", "Santa Fe"); System.out.println ("Jaunizveidotais stāvoklis ir:"); System.out.println (newMexico); } / ** * Parāda, kā pat paredzētais rekursijas rezultāts var būt StackOverflowError *, kad rekurzīvās funkcionalitātes beigu nosacījums nekad nav * izpildīts. * / public void runIntentionalRecursiveWithDysfunctionalTermination () {final int numberForFactorial = -1; System.out.print ("" + numberForFactorial + "faktoriāls ir:"); System.out.println (aprēķinātFactorial (numberForFactorial)); } / ** * Uzrakstiet šīs klases galvenās opcijas paredzētajā OutputStream. * * @param out OutputStream, uz kuru rakstīt šīs testa lietojumprogrammas opcijas. * / public static void writeOptionsToStream (final OutputStream out) {final String option1 = "1. Neparedzēts (bez izbeigšanas nosacījuma) vienas metodes rekursija"; final String option2 = "2. Neparedzēta (bez pārtraukšanas nosacījuma) cikliska rekursija"; final String3. variants = "3. Bojāta pārtraukšanas rekursija"; mēģiniet {out.write ((option1 + NEW_LINE) .getBytes ()); out.write ((2. opcija + NEW_LINE). getBytes ()); out.write ((opcija3 + NEW_LINE) .getBytes ()); } catch (IOException ioEx) {System.err.println ("(Nevar rakstīt uz nodrošināto OutputStream)"); System.out.println (1. opcija); System.out.println (2. opcija); System.out.println (3. opcija); }} / ** * Galvenā funkcija StackOverflowErrorDemonstrator palaišanai. * / public static void main (final String [] argumenti) {if (argumenti.length <1) {System.err.println ("Jums jānorāda arguments, un šim vienīgajam argumentam jābūt"); System.err.println ("viena no šīm opcijām:"); writeOptionsToStream (System.err); System.exit (-1); } int variants = 0; mēģiniet {option = Integer.valueOf (argumenti [0]); } catch (NumberFormatException notNumericFormat) {System.err.println ("Jūs ievadījāt opciju, kas nav ciparu (nederīga) [" + argumenti [0] + "]"); writeOptionsToStream (System.err); Sistēma.iziet (-2); } final StackOverflowErrorDemonstrator me = new StackOverflowErrorDemonstrator (); slēdzis (opcija) {gadījums 1: me.runUnintentionalRecursionExample (); pārtraukums; 2. gadījums: me.runUnintentionalCyclicRecusionExample (); pārtraukums; 3. gadījums: me.runIntentionalRecursiveWithDysfunctionalTermination (); pārtraukums; noklusējums: System.err.println ("Jūs sniedzāt neparedzētu opciju [" + opcija + "]"); }}} 

Iepriekš minētā klase demonstrē trīs neierobežotas rekursijas veidus: nejauša un pilnīgi neparedzēta rekursija, neparedzēta rekursija, kas saistīta ar tīši cikliskām attiecībām, un paredzēta rekursija ar nepietiekamu pārtraukšanas nosacījumu. Katrs no tiem un to rezultāts tiek apspriesti tālāk.

Pilnīgi neparedzēta rekursija

Var būt reizes, kad rekursija notiek bez jebkāda nolūka. Biežākais iemesls varētu būt metodes nejauša izsaukšana. Piemēram, nav pārāk grūti kļūt mazliet neuzmanīgam un izvēlēties IDE pirmo ieteikumu par atguves vērtību metodei "get", kas varētu beigties ar aicinājumu uz šo pašu metodi! Tas faktiski ir piemērs, kas parādīts klasē iepriekš. The getStringVar () metode sevi atkārtoti sauc līdz StackOverflowError ir sastopams. Rezultāts parādīsies šādi:

Izņēmums pavedienā "main" java.lang.StackOverflowError at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowEstrackover stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) pie dustin.examples .stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) at dustin.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) pie dusti n.examples.stackoverflow.StackOverflowErrorDemonstrator.getStringVar (StackOverflowErrorDemonstrator.java:34) plkst. 

Iepriekš parādītā kaudzes izsekošana faktiski ir daudz reižu garāka nekā tā, kuru es ievietoju iepriekš, bet tas vienkārši ir tas pats atkārtojošais modelis. Tā kā modelis atkārtojas, ir viegli diagnosticēt, ka klases 34. rinda ir problēmu izraisītājs. Aplūkojot šo līniju, mēs redzam, ka tas patiešām ir apgalvojums atgriezt getStringVar () kas galu galā atkārtoti sauc sevi. Šajā gadījumā mēs varam ātri saprast, ka iecerētā rīcība bija tā vietā atgriezt šo.stringVar;.

Neparedzēta rekursija ar cikliskām attiecībām

Cikliskas attiecības starp klasēm rada zināmus riskus. Viens no šiem riskiem ir lielāka iespējamība nonākt neparedzētā rekursijā, kur cikliskās atkarības pastāvīgi tiek sauktas starp objektiem, līdz kaudze pārpilda. Lai to parādītu, es izmantoju vēl divas klases. The Valsts klase un Pilsēta klasei ir cikliskas attiecības, jo a Valsts piemēram, ir atsauce uz tās kapitālu Pilsēta un a Pilsēta ir atsauce uz Valsts kurā tā atrodas.

Valsts.java

pakete dustin.examples.stackoverflow; / ** * Klase, kas pārstāv valsti un apzināti ir daļa no cikliskām * attiecībām starp pilsētu un štatu. * / public class State {private static final String NEW_LINE = System.getProperty ("line.separator"); / ** Valsts nosaukums. * / privāts virknes nosaukums; / ** Divu burtu saīsinājums valstij. * / privātās virknes saīsinājums; / ** Pilsēta, kas ir valsts galvaspilsēta. * / privātā pilsētas galvaspilsēta; / ** * Statiskā veidotāja metode, kas ir paredzēta manis instancēšanai. * * @param newName Jaunizveidotās valsts nosaukums. * @param newAbbreviation Valsts divu burtu saīsinājums. * @param newCapitalCityName Galvaspilsētas nosaukums. * / public static State buildState (final String newName, final String newAbbreviation, final String newCapitalCityName) {gala štata instance = new State (newName, newAbbreviation); instance.capitalCity = jauna pilsēta (newCapitalCityName, instance); atgriešanās instance; } / ** * Parametrizēts konstruktors, kas pieņem datus, lai aizpildītu jauno valsts gadījumu. * * @param newName Jaunizveidotās valsts nosaukums. * @param newAbbreviation Valsts divu burtu saīsinājums. * / private state (galīgā virkne newName, galīgā virkne newAbbreviation) {this.name = newName; this.abbreviation = newAbreviation; } / ** * Nodrošiniet virknes pārstāvību valsts instancē. * * @return My String attēlojums. * / @ Pārvarēt publisko virkni toString () {// BRĪDINĀJUMS: Tas beigsies slikti, jo tas netieši izsauc pilsētas metodi toString () // un pilsētas metode toString () izsauc šo metodi // State.toString (). atgriež "StateName:" + this.name + NEW_LINE + "StateAbbreviation:" + this.abbreviation + NEW_LINE + "CapitalCity:" + this.capitalCity; }} 

Pilsēta.java

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