Nedir, StackOverflowError
neden olur ve bunlarla nasıl başa çıkmalıyım?
new Object() {{getClass().newInstance();}};
bazı statik bağlamlara ekleyin (örneğin main
yöntem). Örnek bağlamından çalışmaz (yalnızca atar InstantiationException
).
Nedir, StackOverflowError
neden olur ve bunlarla nasıl başa çıkmalıyım?
new Object() {{getClass().newInstance();}};
bazı statik bağlamlara ekleyin (örneğin main
yöntem). Örnek bağlamından çalışmaz (yalnızca atar InstantiationException
).
Yanıtlar:
Parametreler ve yerel değişkenler yığına tahsis edilir (referans türleriyle, nesne öbek üzerinde yaşar ve yığında bu nesneyi referanstaki bir değişken). Yığın genellikle adres alanınızın üst ucunda yaşar ve kullanıldıkça adres alanının altına doğru (yani sıfıra doğru) gider .
İşleminizde ayrıca işleminizin alt ucunda yaşayan bir yığın vardır . Belleği ayırdıkça, bu yığın adres alanınızın üst ucuna doğru büyüyebilir. Gördüğünüz gibi, yığının yığınla "çarpışması" olasılığı vardır (biraz tektonik plakalar gibi !!!).
Yığın taşmasının en yaygın nedeni hatalı bir yinelemeli çağrıdır . Genellikle, özyinelemeli işlevleriniz doğru sonlandırma durumuna sahip olmadığında, bu nedenle kendini sonsuza dek çağırır. Veya fesih koşulu iyi olduğunda, bunun yerine getirilmeden önce çok fazla yinelemeli çağrı yapılması gerekebilir.
Bununla birlikte, GUI programlama ile dolaylı özyineleme oluşturmak mümkündür . Örneğin, uygulamanız boya mesajlarını işliyor olabilir ve bunları işlerken, sistemin başka bir boya mesajı göndermesine neden olan bir işlevi çağırabilir. Burada açıkça kendinizi aramadınız, ancak OS / VM sizin için yaptı.
Onlarla başa çıkmak için kodunuzu incelemeniz gerekir. Kendilerini çağıran işlevleriniz varsa, sonlandırma koşulunuz olup olmadığını kontrol edin. Varsa, işlevi çağırırken bağımsız değişkenlerden en az birini değiştirdiğinizi, aksi takdirde özyinelemeli olarak adlandırılan işlev için görünür bir değişiklik olmayacağını ve sonlandırma koşulunun yararsız olduğunu kontrol edin. Ayrıca, geçerli bir sonlandırma durumuna ulaşmadan önce yığın alanınızın belleğinin bitebileceğini unutmayın, bu nedenle yönteminizin daha fazla yinelemeli çağrı gerektiren giriş değerlerini işleyebildiğinden emin olun.
Açık bir özyinelemeli işleviniz yoksa, dolaylı olarak işlevinizin çağrılmasına neden olacak herhangi bir kütüphane işlevini çağırıp çağırmadığınızı kontrol edin (yukarıdaki örtük durum gibi).
Bunu tanımlamak için önce yerel değişkenlerin ve nesnelerin nasıl saklandığını anlayalım.
Yerel değişken yığın halinde saklanır :
Resme bakarsanız, işlerin nasıl çalıştığını anlayabilmelisiniz.
Bir Java uygulaması tarafından bir işlev çağrısı çağrıldığında, çağrı yığınına bir yığın çerçevesi atanır. Yığın çerçevesi, çağrılan yöntemin parametrelerini, yerel parametrelerini ve yöntemin dönüş adresini içerir. Dönüş adresi, çağrılan yöntem döndürüldükten sonra program yürütmesinin devam edeceği yürütme noktasını belirtir. Yeni bir yığın çerçevesi için yer yoksa, StackOverflowError
Java Sanal Makinesi (JVM) tarafından atılır.
Bir Java uygulamasının yığınını tüketebilecek en yaygın durum özyineleme'dir. Özyinelemede, bir yöntem yürütülmesi sırasında kendini çağırır. Özyineleme, güçlü bir genel amaçlı programlama tekniği olarak kabul edilir, ancak önlemek için dikkatli kullanılmalıdır StackOverflowError
.
A atma örneği StackOverflowError
aşağıda gösterilmiştir:
StackOverflowErrorExample.java:
public class StackOverflowErrorExample {
public static void recursivePrint(int num) {
System.out.println("Number: " + num);
if (num == 0)
return;
else
recursivePrint(++num);
}
public static void main(String[] args) {
StackOverflowErrorExample.recursivePrint(1);
}
}
Bu örnekte, recursivePrint
bir tamsayı yazdırıp sonra bir sonraki ardışık tamsayı argüman olarak çağıran özyinelemeli bir yöntem tanımlarız . Yineleme 0
, parametre olarak geçene kadar sona erer . Bununla birlikte, örneğimizde, parametreyi 1'den ve onun takip eden takipçilerinden geçtik, sonuç olarak, özyineleme asla sona ermeyecektir.
-Xss1M
1MB'ye eşit iş parçacığı yığınının boyutunu belirten bayrağı kullanan bir örnek yürütme aşağıda gösterilmiştir:
Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" java.lang.StackOverflowError
at java.io.PrintStream.write(PrintStream.java:480)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
...
JVM'nin ilk yapılandırmasına bağlı olarak sonuçlar farklılık gösterebilir, ancak sonunda StackOverflowError
atılır. Bu örnek, dikkatli bir şekilde uygulanmazsa, özyinelemenin nasıl sorunlara neden olabileceğinin çok iyi bir örneğidir.
StackOverflowError ile nasıl başa çıkılır
En basit çözüm, yığın izlemesini dikkatle incelemek ve satır numaralarının tekrar eden desenini tespit etmektir. Bu satır numaraları, özyinelemeli olarak adlandırılan kodu gösterir. Bu satırları algıladıktan sonra, kodunuzu dikkatlice incelemeli ve özyinelemenin neden hiç sona ermediğini anlamalısınız.
Özyinelemenin doğru bir şekilde uygulandığını doğruladıysanız, daha fazla sayıda çağrmaya izin vermek için yığının boyutunu artırabilirsiniz. Yüklenen Java Sanal Makinesi'ne (JVM) bağlı olarak, varsayılan iş parçacığı yığın boyutu 512 KB veya 1 MB'ye eşit olabilir . -Xss
Bayrağı kullanarak iplik yığını boyutunu artırabilirsiniz . Bu bayrak, projenin yapılandırması veya komut satırı aracılığıyla belirtilebilir. -Xss
Argümanın biçimi
:
-Xss<size>[g|G|m|M|k|K]
Gibi bir fonksiyonunuz varsa:
int foo()
{
// more stuff
foo();
}
Daha sonra foo () kendini çağırmaya, derinleşmeye ve derinleşmeye devam eder ve içinde bulunduğunuz işlevleri doldurmak için kullanılan alan dolduğunda yığın taşması hatası alırsınız.
Yığın taşması tam olarak şu anlama gelir: yığın taşması. Genellikle programda, yerel kapsam değişkenleri ve rutin yürütme sona erdiğinde nereye dönüleceği adreslerini içeren bir yığın bulunur. Bu yığın, bellekte bir yerde sabit bir bellek aralığı olma eğilimindedir, bu nedenle ne kadar değer içerebileceği sınırlıdır.
Yığın boşsa, patlayamazsınız, eğer yaparsanız yığın düşüklüğü hatası alırsınız.
Yığın doluysa, itemezsiniz, eğer yaparsanız yığın taşması hatası alırsınız.
Böylece yığına çok fazla tahsis ettiğiniz yerde yığın taşması görünür. Örneğin, söz konusu özyinelemede.
Bazı uygulamalar bazı özyineleme biçimlerini optimize eder. Özellikle kuyruk özyineleme. Kuyruk özyinelemeli rutinler, özyinelemeli çağrının rutinin yaptığı son şey olarak göründüğü rutinler biçimidir. Bu rutin çağrı basitçe bir sıçramaya dönüşür.
Bazı uygulamalar özyineleme için kendi yığınlarını uygulayana kadar devam eder, bu nedenle özyineleme, sistemin belleği bitene kadar devam etmesine izin verir.
Deneyebileceğiniz en kolay şey, yapabileceğiniz yığın boyutunuzu artırmak olacaktır. Bunu yapamazsanız, ikinci en iyi şey, yığın taşmasına neden olan bir şey olup olmadığına bakmak olacaktır. Çağrıdan önce ve sonra bir şeyi rutin olarak yazdırarak deneyin. Bu, başarısız rutini bulmanıza yardımcı olur.
Yığın taşması genellikle yuva çağrıları işlevinin çok derinlemesine (özyineleme kullanıldığında kolay, yani kendisini çağıran bir işlev) veya yığının kullanılmasının daha uygun olacağı yığın üzerinde büyük miktarda bellek ayrılmasıyla çağrılır.
Dediğiniz gibi, bir kod göstermeniz gerekiyor. :-)
Yığın taşması hatası genellikle işleviniz çok derin yuvalama çağırdığında oluşur. Bunun nasıl gerçekleştiğine ilişkin bazı örnekler için Stack Overflow Code Golf iş parçacığına bakın (bu soru olması durumunda, yanıtlar bilerek yığın taşmasına neden olur).
Yığın taşmalarının en yaygın nedeni aşırı derin veya sonsuz tekrarlamadır . Sorununuz buysa, Java Recursion hakkındaki bu öğretici sorunun anlaşılmasına yardımcı olabilir.
StackOverflowError
yığına olduğu gibi OutOfMemoryError
yığına.
Sınırsız özyinelemeli çağrılar yığın alanının tükenmesine neden olur.
Aşağıdaki örnek aşağıdakileri üretir StackOverflowError
:
class StackOverflowDemo
{
public static void unboundedRecursiveCall() {
unboundedRecursiveCall();
}
public static void main(String[] args)
{
unboundedRecursiveCall();
}
}
StackOverflowError
toplam olmayan bellek içi çağrıların (bayt cinsinden) toplam yığının boyutunu (bayt cinsinden) aşmasını önlemek için özyinelemeli çağrılar sınırlandırılmışsa önlenebilir.
Burada, tek başına bağlı bir listeyi tersine çevirmek için özyinelemeli bir algoritma örneği verilmiştir. Aşağıdaki özelliklere sahip bir dizüstü bilgisayarda (4G bellek, Intel Core i5 2.3GHz CPU, 64 bit Windows 7), bu işlev 10.000'e yakın bağlantılı bir liste için StackOverflow hatasıyla çalışır.
Demek istediğim, özyinelemeyi daima sistemin ölçeğini göz önünde bulundurarak akıllıca kullanmalıyız. Genellikle özyineleme, daha iyi ölçeklenen yinelemeli programa dönüştürülebilir. (Aynı algoritmanın yinelemeli bir versiyonu sayfanın alt kısmında verilmiştir, 9 milisaniyede 1 milyon büyüklüğünde tekil bağlantılı bir listeyi tersine çevirir.)
private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){
LinkedListNode second = first.next;
first.next = x;
if(second != null){
return doReverseRecursively(first, second);
}else{
return first;
}
}
public static LinkedListNode reverseRecursively(LinkedListNode head){
return doReverseRecursively(null, head);
}
Aynı Algoritmanın Yinelemeli Sürümü:
public static LinkedListNode reverseIteratively(LinkedListNode head){
return doReverseIteratively(null, head);
}
private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) {
while (first != null) {
LinkedListNode second = first.next;
first.next = x;
x = first;
if (second == null) {
break;
} else {
first = second;
}
}
return first;
}
public static LinkedListNode reverseIteratively(LinkedListNode head){
return doReverseIteratively(null, head);
}
A StackOverflowError
, Java'daki bir çalışma zamanı hatasıdır.
JVM tarafından ayrılan çağrı yığını belleği miktarı aşıldığında atılır.
StackOverflowError
Atılan bir yaygın durum, çağrı yığını aşırı derin veya sonsuz özyineleme nedeniyle aşıldığındadır.
Misal:
public class Factorial {
public static int factorial(int n){
if(n == 1){
return 1;
}
else{
return n * factorial(n-1);
}
}
public static void main(String[] args){
System.out.println("Main method started");
int result = Factorial.factorial(-1);
System.out.println("Factorial ==>"+result);
System.out.println("Main method ended");
}
}
Yığın izleme:
Main method started
Exception in thread "main" java.lang.StackOverflowError
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
Yukarıdaki durumda, programlı değişiklikler yapılarak önlenebilir. Ancak program mantığı doğruysa ve yine de oluşursa, yığın boyutunun artırılması gerekir.
Bu tipik bir vaka olduğunu java.lang.StackOverflowError
... yönteminde yinelemeli içinde çıkışı olmayan kendini çağırıyor doubleValue()
, floatValue()
vb
public class Rational extends Number implements Comparable<Rational> {
private int num;
private int denom;
public Rational(int num, int denom) {
this.num = num;
this.denom = denom;
}
public int compareTo(Rational r) {
if ((num / denom) - (r.num / r.denom) > 0) {
return +1;
} else if ((num / denom) - (r.num / r.denom) < 0) {
return -1;
}
return 0;
}
public Rational add(Rational r) {
return new Rational(num + r.num, denom + r.denom);
}
public Rational sub(Rational r) {
return new Rational(num - r.num, denom - r.denom);
}
public Rational mul(Rational r) {
return new Rational(num * r.num, denom * r.denom);
}
public Rational div(Rational r) {
return new Rational(num * r.denom, denom * r.num);
}
public int gcd(Rational r) {
int i = 1;
while (i != 0) {
i = denom % r.denom;
denom = r.denom;
r.denom = i;
}
return denom;
}
public String toString() {
String a = num + "/" + denom;
return a;
}
public double doubleValue() {
return (double) doubleValue();
}
public float floatValue() {
return (float) floatValue();
}
public int intValue() {
return (int) intValue();
}
public long longValue() {
return (long) longValue();
}
}
public class Main {
public static void main(String[] args) {
Rational a = new Rational(2, 4);
Rational b = new Rational(2, 6);
System.out.println(a + " + " + b + " = " + a.add(b));
System.out.println(a + " - " + b + " = " + a.sub(b));
System.out.println(a + " * " + b + " = " + a.mul(b));
System.out.println(a + " / " + b + " = " + a.div(b));
Rational[] arr = {new Rational(7, 1), new Rational(6, 1),
new Rational(5, 1), new Rational(4, 1),
new Rational(3, 1), new Rational(2, 1),
new Rational(1, 1), new Rational(1, 2),
new Rational(1, 3), new Rational(1, 4),
new Rational(1, 5), new Rational(1, 6),
new Rational(1, 7), new Rational(1, 8),
new Rational(1, 9), new Rational(0, 1)};
selectSort(arr);
for (int i = 0; i < arr.length - 1; ++i) {
if (arr[i].compareTo(arr[i + 1]) > 0) {
System.exit(1);
}
}
Number n = new Rational(3, 2);
System.out.println(n.doubleValue());
System.out.println(n.floatValue());
System.out.println(n.intValue());
System.out.println(n.longValue());
}
public static <T extends Comparable<? super T>> void selectSort(T[] array) {
T temp;
int mini;
for (int i = 0; i < array.length - 1; ++i) {
mini = i;
for (int j = i + 1; j < array.length; ++j) {
if (array[j].compareTo(array[mini]) < 0) {
mini = j;
}
}
if (i != mini) {
temp = array[i];
array[i] = array[mini];
array[mini] = temp;
}
}
}
}
2/4 + 2/6 = 4/10
Exception in thread "main" java.lang.StackOverflowError
2/4 - 2/6 = 0/-2
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 * 2/6 = 4/24
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 / 2/6 = 12/8
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
İşte bir örnek
public static void main(String[] args) {
System.out.println(add5(1));
}
public static int add5(int a) {
return add5(a) + 5;
}
Bir StackOverflowError temelde, büyük olasılıkla kendini çağıran ve sonsuzluk için devam eden (veya bir StackOverflowError verene kadar) bir şey yapmaya çalıştığınızdadır.
add5(a)
kendini arayacak, sonra tekrar arayacak vb.
"Yığın taşması (taşma)" terimi sıklıkla kullanılır, ancak yanlış adlandırma; saldırılar yığının üzerine taşmaz, ancak yığındaki arabellekleri.
- Prof. Dr. Dieter Gollmann'ın ders slaytlarından