2009-06-04 38 views
15

Làm thế nào để xử lý StackOverflowError trong Java?Làm thế nào để xử lý StackOverflowError trong Java?

+25

Sự cố. Đó là những gì tôi luôn làm. –

+1

Vui lòng đăng mã đang gây tràn ngăn xếp. Tránh tràn ngăn xếp gần như luôn luôn tốt hơn sau đó cố gắng để xử lý các ngoại lệ. –

+8

Ahh, sự trớ trêu của câu hỏi đó trên một trang web có tên này ... –

Trả lời

14

Tôi không chắc chắn ý của bạn là "xử lý".

Bạn chắc chắn có thể bắt rằng lỗi:

public class Example { 
    public static void endless() { 
     endless(); 
    } 

    public static void main(String args[]) { 
     try { 
      endless(); 
     } catch(StackOverflowError t) { 
      // more general: catch(Error t) 
      // anything: catch(Throwable t) 
      System.out.println("Caught "+t); 
      t.printStackTrace(); 
     } 
     System.out.println("After the error..."); 
    } 
} 

nhưng điều đó rất có thể là một ý tưởng tồi, trừ khi bạn biết chính xác những gì bạn đang làm.

+3

Xử lý Ngoại lệ có nghĩa là muốn tiếp tục sau khi bỏ qua ngoại lệ. Vì nó là stackOverflowException, tôi nghĩ rằng không có không gian ngăn xếp có sẵn được yêu cầu bởi java. Vì vậy, trong trường hợp như thế nào để tiến xa hơn? –

+4

Bạn rất có thể có vấn đề nghiêm trọng trong chương trình của mình. Đánh giá dấu vết ngăn xếp và kiểm tra xem bạn có thấy bất kỳ điều gì đáng ngờ không. Bạn có thể tiếp tục sau khi bắt được StackOverflowError vì callstack đã bị dọn sạch bởi lỗi được ném ...Tôi chỉ có thể lặp lại chính mình, mặc dù: tìm lỗi thực sự gây ra vấn đề! – Huxi

+4

Ankur, một StachOverflowError của nó, không phải StackOverflowException, sự khác biệt là tên "ERROR" chỉ ra rằng nó không nên được bắt qua try ... bắt nhưng bạn nên viết lại mã của bạn như tôi và hầu hết những người khác ở đây đang đề xuất. – Avrom

17

Bạn có thể có một số đệ quy vô hạn đang diễn ra.

I.e. một phương thức tự gọi chính nó qua và hơn

public void sillyMethod() 
{ 
    sillyMethod(); 
} 

Một phương pháp để xử lý việc này là sửa mã của bạn để đệ quy chấm dứt thay vì tiếp tục mãi mãi.

+3

đôi khi việc đệ quy vô hạn cải trang chính nó rất tốt. Nhìn vào stacktrace bên trong trình gỡ rối sau khi stackoverflow. –

+4

+1 cho tên phương thức. Ngoài ra: KnightsWhoSayNeet() – kevinarpe

1

Tôi đoán bạn không thể - hoặc ít nhất phụ thuộc vào jvm bạn sử dụng. Stack overflow có nghĩa là bạn không có chỗ để lưu các biến cục bộ và trả về các địa chỉ. Nếu jvm của bạn làm một số hình thức biên dịch, bạn có stackoverflow trong jvm là tốt và điều đó có nghĩa là, bạn không thể xử lý nó hoặc bắt nó. Jvm phải chấm dứt.

Có thể có cách tạo một jvm cho phép có hành vi như vậy, nhưng nó sẽ chậm.

Tôi chưa thử nghiệm hành vi với jvm, nhưng trong .net bạn không thể xử lý luồng lưu trữ. Ngay cả cố gắng bắt sẽ không giúp đỡ. Kể từ java và .net dựa trên cùng một khái niệm (máy ảo với jit) tôi nghi ngờ java sẽ hành xử giống nhau. Sự hiện diện của một stackoverflow-exception trong .NET cho thấy, có thể có một số vm cho phép chương trình bắt nó, bình thường thì không.

0

Dấu vết ngăn xếp phải cho biết bản chất của vấn đề. Nên có một số vòng lặp rõ ràng khi bạn đọc theo dõi ngăn xếp.

Nếu nó không phải là lỗi, bạn cần thêm bộ đếm hoặc một số cơ chế khác để tạm dừng đệ quy trước khi đệ quy đi sâu đến nỗi nó gây tràn ngăn xếp.

Ví dụ về điều này có thể là nếu bạn đang xử lý XML lồng nhau trong một mô hình DOM với các cuộc gọi đệ quy và XML được lồng vào sâu đến mức nó gây tràn ngăn xếp với các cuộc gọi lồng nhau của bạn (không, nhưng có thể). Điều này sẽ phải được khá sâu làm tổ để gây ra một tràn ngăn xếp mặc dù.

0

Như đã đề cập trong nhiều chủ đề này, nguyên nhân phổ biến cho việc này là cuộc gọi phương thức đệ quy không kết thúc. Nếu có thể tránh tràn ngăn xếp và nếu bạn trong thử nghiệm này, bạn nên xem xét điều này trong hầu hết các trường hợp là một lỗi nghiêm trọng. Trong một số trường hợp, bạn có thể cấu hình kích thước ngăn xếp luồng trong Java lớn hơn để xử lý một số trường hợp (bộ dữ liệu lớn được quản lý trong bộ nhớ cục bộ, các cuộc gọi đệ quy dài), nhưng điều này sẽ làm tăng tổng dung lượng bộ nhớ. các chủ đề có sẵn trong VM. Nói chung nếu bạn nhận được ngoại lệ này các chủ đề và bất kỳ dữ liệu địa phương để thread này nên được coi là bánh mì nướng và không được sử dụng (nghĩa là nghi ngờ và có thể bị hỏng).

13

Hãy xem Raymond Chen bài đăng của When debugging a stack overflow, you want to focus on the repeating recursive part. Trích xuất:

If you go hunting through your defect tracking database trying to see whether this is a known issue or not, a search for the top functions on the stack is unlikely to find anything interesting. That's because stack overflows tend to happen at a random point in the recursion; each stack overflow looks superficially different from every other one even if they are the same stack overflow.

Suppose you're singing the song Frère Jacques, except that you sing each verse a few tones higher than the previous one. Eventually, you will reach the top of your singing range, and precisely where that happens depends on where your vocal limit lines up against the melody. In the melody, the first three notes are each a new "record high" (i.e., the notes are higher than any other note sung so far), and new record highs appear in the three notes of the third measure, and a final record high in the second note of the fifth measure.

If the melody represented a program's stack usage, a stack overflow could possibly occur at any of those five locations in the program's execution. In other words, the same underlying runaway recursion (musically represented by an ever-higher rendition of the melody) can manifest itself in five different ways. The "recursion" in this analogy was rather quick, just eight bars before the loop repeated. In real life, the loop can be quite long, leading to dozens of potential points where the stack overflow can manifest itself.

If you are faced with a stack overflow, then, you want to ignore the top of the stack, since that's just focusing on the specific note that exceeded your vocal range. You really want to find the entire melody, since that's what's common to all the stack overflows with the same root cause.

+0

liên kết rất hữu ích - đó là những gì tôi đã học để làm trong trường hợp tràn ngăn xếp. –

7

Bạn có thể muốn xem tùy chọn "-Xss" có được JVM hỗ trợ hay không.Nếu vậy, bạn có thể thử đặt nó thành giá trị 512k (mặc định là 256k dưới 32 bit Windows và Unix) và xem điều đó có làm được gì không (ngoài việc khiến bạn ngồi lâu hơn cho đến khi StackOverflowException). Lưu ý rằng đây là một cài đặt cho mỗi luồng, vì vậy nếu bạn có nhiều chủ đề đang chạy, bạn cũng có thể muốn tăng cường cài đặt heap của mình.

+1

+1, đây là câu trả lời duy nhất giải quyết nguyên nhân gốc rễ của vấn đề. Tôi làm việc trong môi trường doanh nghiệp với hàng trăm dịch vụ, và nhiều người trong số họ không liên tục - và họ không sử dụng đệ quy, họ chỉ làm rất nhiều. Các dịch vụ là đá vững chắc khi kích thước ngăn xếp được tăng từ mặc định là 1MB, lên 4MB hoặc thậm chí 16MB. Và khi một "StackOverflowError" xảy ra, chúng ta không nhất thiết có thể dựa vào nó để xuất hiện trong nhật ký, nó có thể là tuyệt đối ^% £ * 1 của một vấn đề để theo dõi. – Contango

0

đơn giản,

Nhìn vào stack trace rằng StackOverflowError sản xuất, do đó bạn biết được nơi trong mã của bạn nó xảy ra và sử dụng nó để tìm ra cách để viết lại mã của bạn để nó không gọi bản thân một cách đệ quy (các có thể gây ra lỗi của bạn) vì vậy nó sẽ không xảy ra lần nữa.

StackOverflow Gương không phải là thứ cần được xử lý thông qua mệnh đề try ... nhưng nó trỏ tới một lỗ hổng cơ bản trong logic mã của bạn cần được bạn sửa.

0

java.lang.Error javadoc:

An Lỗi là một lớp con của Throwable cho biết vấn đề nghiêm trọng mà một ứng dụng hợp lý không nên cố gắng để bắt. Hầu hết các lỗi như vậy là điều kiện bất thường. Lỗi ThreadDeath, mặc dù điều kiện "bình thường", cũng là một phân lớp của Lỗi vì hầu hết các ứng dụng không nên cố gắng nắm bắt nó. Một phương pháp không bắt buộc phải khai báo trong mệnh đề ném của nó bất kỳ lớp con nào của Lỗi có thể được ném trong khi thực hiện phương thức nhưng không bị bắt, vì các lỗi này là các điều kiện bất thường không bao giờ xảy ra.

Vì vậy, đừng. Cố gắng tìm thấy những gì sai trong logic của mã của bạn. Ngoại lệ này ocurrs rất thường xuyên vì đệ quy vô hạn.

4

Câu trả lời đúng là câu trả lời đã được đưa ra. Bạn có thể hoặc là a) có một lỗi trong mã của bạn dẫn đến một đệ quy vô hạn mà thường khá dễ chẩn đoán và sửa chữa, hoặc b) có mã mà có thể dẫn đến việc thu thập rất sâu ví dụ đệ quy đi qua cây nhị phân không cân bằng. Trong trường hợp thứ hai, bạn cần phải thay đổi mã của mình để không phân bổ thông tin trên ngăn xếp (tức là để không recurse) mà thay vào đó phân bổ nó trong heap.

Ví dụ, đối với chuyển đổi cây không cân bằng, bạn có thể lưu trữ các nút cần được xem xét lại trong cấu trúc dữ liệu Stack. Bạn có thể lặp lại các nhánh bên trái đẩy mỗi nút khi bạn truy cập vào nó cho đến khi bạn nhấn một cái lá, bạn sẽ xử lý, sau đó bật một nút ra khỏi ngăn xếp, xử lý nó, sau đó khởi động lại vòng lặp của bạn đúng con (chỉ cần thiết lập biến vòng lặp của bạn đến nút bên phải.) Điều này sẽ sử dụng một số lượng không đổi của ngăn xếp bằng cách di chuyển tất cả mọi thứ đã được trên ngăn xếp đến đống trong cấu trúc dữ liệu Stack. Heap thường phong phú hơn nhiều so với chồng.

Như một cái gì đó thường là một ý tưởng cực kỳ tồi, nhưng là cần thiết trong trường hợp sử dụng bộ nhớ bị hạn chế, bạn có thể sử dụng đảo ngược con trỏ. Trong kỹ thuật này, bạn mã hóa ngăn xếp vào cấu trúc bạn đang đi ngang qua và bằng cách sử dụng lại các liên kết mà bạn đang duyệt qua, bạn có thể thực hiện việc này mà không cần hoặc ít bộ nhớ bổ sung đáng kể. Sử dụng ví dụ trên, thay vì đẩy các nút khi chúng ta lặp lại, chúng ta chỉ cần nhớ cha mẹ trực tiếp của chúng ta, và tại mỗi lần lặp lại, chúng ta thiết lập liên kết mà chúng ta đi qua đến parent hiện tại và sau đó là parent hiện tại tới node chúng ta đang rời. Khi chúng tôi đến một chiếc lá, chúng tôi xử lý nó, sau đó đi đến cha mẹ của chúng tôi và sau đó chúng tôi có một câu hỏi hóc búa. Chúng tôi không biết có nên sửa nhánh bên trái, xử lý nút này hay không và tiếp tục với nhánh bên phải, hoặc sửa đúng nhánh và đi tới cha mẹ của chúng ta. Vì vậy, chúng tôi cần phân bổ thêm một chút thông tin khi chúng tôi lặp lại. Thông thường, để thực hiện ở mức độ thấp của kỹ thuật này, bit đó sẽ được lưu trữ trong chính con trỏ dẫn đến không có bộ nhớ bổ sung và bộ nhớ liên tục tổng thể. Đây không phải là một tùy chọn trong Java, nhưng nó có thể có khả năng loại bỏ bit này trong các trường được sử dụng cho những thứ khác.Trong trường hợp xấu nhất, điều này vẫn còn ít nhất 32 hoặc 64 lần giảm số lượng bộ nhớ cần thiết. Tất nhiên, thuật toán này là cực kỳ dễ dàng để có được sai với kết quả hoàn toàn khó hiểu và sẽ nâng cao sự tàn phá hoàn toàn với đồng thời. Vì vậy, nó gần như không bao giờ có giá trị cơn ác mộng bảo trì, ngoại trừ nơi phân bổ bộ nhớ là không thể phủ nhận. Ví dụ điển hình là một bộ thu gom rác nơi các thuật toán như thế này là phổ biến.

Điều tôi thực sự muốn nói đến, là khi bạn có thể muốn xử lý StackOverflowError. Cụ thể là để cung cấp loại bỏ cuộc gọi đuôi trên JVM. Một cách tiếp cận là sử dụng phong cách trampoline, thay vì thực hiện một cuộc gọi đuôi bạn trở về một đối tượng thủ tục nullary, hoặc nếu bạn chỉ trả về một giá trị bạn trả lại đó. [Lưu ý: điều này đòi hỏi một số phương tiện nói rằng một hàm trả về hoặc là A hoặc B. Trong Java, có lẽ cách nhẹ nhất để làm điều này là trả về một kiểu bình thường và ném cái kia là một ngoại lệ.] Sau đó, bất cứ khi nào bạn gọi một phương thức, bạn cần phải thực hiện một vòng lặp while gọi các thủ tục nullary (sẽ tự trả về một thủ tục null hoặc một giá trị) cho đến khi bạn nhận được một giá trị. Một vòng lặp vô tận sẽ trở thành một vòng lặp while thường xuyên buộc các đối tượng thủ tục trả về các đối tượng thủ tục. Lợi ích của phong cách trampoline là nó chỉ sử dụng một hệ số không đổi nhiều hơn bạn sẽ sử dụng với việc triển khai đúng cách loại bỏ tất cả các cuộc gọi đuôi, nó sử dụng ngăn xếp Java bình thường cho các cuộc gọi không đuôi, bản dịch đơn giản và nó chỉ phát triển mã bởi một yếu tố liên tục (tẻ nhạt). Hạn chế là bạn phân bổ một đối tượng trên mọi cuộc gọi phương thức (mà ngay lập tức sẽ trở thành rác) và tiêu thụ các đối tượng này liên quan đến một vài cuộc gọi gián tiếp cho mỗi cuộc gọi đuôi.

Điều lý tưởng cần làm là không bao giờ phân bổ các thủ tục vô hiệu đó hoặc bất kỳ thứ gì khác ở nơi đầu tiên, đó chính xác là việc loại bỏ cuộc gọi đuôi sẽ thực hiện được. Làm việc với những gì Java cung cấp mặc dù, những gì chúng ta có thể làm là chạy mã như bình thường và chỉ thực hiện các thủ tục rỗng khi chúng ta chạy ra khỏi ngăn xếp. Bây giờ chúng ta vẫn phân bổ những khung vô dụng đó, nhưng chúng ta làm như vậy trên stack chứ không phải đống và deallocate chúng với số lượng lớn, ngoài ra, các cuộc gọi của chúng ta là các cuộc gọi Java trực tiếp bình thường. Cách dễ nhất để mô tả chuyển đổi này là viết lại tất cả các phương thức đa lệnh gọi thành các phương thức có hai lệnh gọi, tức là fgh() {f(); g(); h(); } trở thành fgh() {f(); gh(); } và gh() {g(); h(); }. Để đơn giản, tôi sẽ giả sử tất cả các phương thức kết thúc trong một cuộc gọi đuôi, có thể được sắp xếp bằng cách chỉ đóng gói phần còn lại của một phương thức vào một phương thức riêng biệt, mặc dù trong thực tế, bạn muốn xử lý chúng trực tiếp. Sau các phép biến đổi này, chúng ta có ba trường hợp, hoặc một phương thức có các cuộc gọi không, trong trường hợp đó không có gì để làm, hoặc nó có một cuộc gọi (đuôi), trong trường hợp chúng ta bọc nó trong một khối try-catch giống như chúng ta sẽ cuộc gọi đuôi trong hai trường hợp cuộc gọi. Cuối cùng, nó có thể có hai cuộc gọi, một cuộc gọi không đuôi và một cuộc gọi đuôi, trong trường hợp này chúng ta áp dụng phép biến đổi sau đây bằng ví dụ (sử dụng ký hiệu lambda của C# có thể dễ dàng được thay thế bằng một lớp bên trong vô danh với một số tăng trưởng):

// top-level handler 
Action tlh(Action act) { 
    return() => { 
     while(true) { 
      try { act(); break; } catch(Bounce e) { tlh(() => e.run())(); } 
     } 
    } 
} 

gh() { 
    try { g(); } catch(Bounce e) { 
     throw new Bounce(tlh(() => { 
      e.run(); 
      try { h(); } catch(StackOverflowError e) { 
       throw new Bounce(tlh(() => h()); 
      } 
     }); 
    } 
    try { h(); } catch(StackOverflowError e) { 
     throw new Bounce(tlh(() => h())); 
    } 
} 

Lợi ích chính ở đây là nếu không có ngoại lệ được ném, đây là mã giống như khi chúng tôi bắt đầu chỉ với một số trình xử lý ngoại lệ bổ sung được cài đặt. Kể từ khi cuộc gọi đuôi (các cuộc gọi h()) không xử lý các trường hợp ngoại lệ Bounce, ngoại lệ đó sẽ bay qua chúng unwinding những (không cần thiết) khung từ ngăn xếp. Các cuộc gọi không đuôi bắt các trường hợp ngoại lệ Bounce và rethrow chúng với mã còn lại được thêm vào. Điều này sẽ giải phóng ngăn xếp tất cả các con đường lên đến cấp cao nhất, loại bỏ các khung cuộc gọi đuôi nhưng ghi nhớ các khung cuộc gọi không đuôi trong thủ tục nullary. Khi chúng ta cuối cùng thực thi thủ tục trong trường hợp Thoát ra ngoài ở cấp cao nhất, chúng ta sẽ tạo lại tất cả các khung gọi không đuôi. Tại thời điểm này, nếu chúng tôi ngay lập tức chạy ra khỏi ngăn xếp một lần nữa, sau đó, kể từ khi chúng tôi không cài đặt lại các trình xử lý StackOverflowError, nó sẽ đi vô ích như mong muốn, kể từ khi chúng tôi thực sự ra khỏi ngăn xếp. Nếu chúng ta có thêm một chút nữa, một StackOverflowError mới sẽ được cài đặt khi thích hợp. Hơn nữa, nếu chúng ta thực hiện tiến bộ, nhưng sau đó chạy ra khỏi ngăn xếp một lần nữa, không có lợi ích nào để giải phóng các khung hình mà chúng ta đã sẵn sàng, vì vậy chúng tôi cài đặt các trình xử lý cấp cao mới để ngăn xếp sẽ chỉ được mở cho chúng.Vấn đề lớn nhất với phương pháp này là bạn có thể muốn gọi các phương thức Java bình thường và bạn có thể có ít ngăn xếp tùy ý khi bạn thực hiện, vì vậy chúng có đủ không gian để bắt đầu nhưng không hoàn thành và bạn không thể tiếp tục chúng ở giữa. Có ít nhất hai giải pháp cho điều này. Đầu tiên là gửi tất cả các công việc như vậy đến một chủ đề riêng biệt mà sẽ có ngăn xếp của riêng nó. Điều này là khá hiệu quả và khá dễ dàng và sẽ không giới thiệu bất kỳ concurrency (trừ khi bạn muốn nó.) Một tùy chọn khác chỉ đơn giản là cố ý thư giãn ngăn xếp trước khi gọi bất kỳ phương pháp Java bình thường bằng cách đơn giản ném một StackOverflowError ngay lập tức trước khi họ. Nếu nó vẫn chạy ra khỏi không gian ngăn xếp khi bạn tiếp tục, sau đó bạn đã bị bắt vít.

Điều tương tự cũng có thể được thực hiện để thực hiện việc tiếp tục ngay tức thì. Thật không may, việc chuyển đổi này không thực sự dễ chịu khi làm bằng tay trong Java, và có lẽ là ranh giới cho các ngôn ngữ như C# hoặc Scala. Vì vậy, các phép biến đổi như thế này có xu hướng được thực hiện bằng các ngôn ngữ nhắm vào JVM chứ không phải bởi con người.

-1
/* 
Using Throwable we can trap any know error in JAVA.. 
*/ 
public class TestRecur { 
    private int i = 0; 


    public static void main(String[] args) { 
     try { 
      new TestRecur().show(); 
     } catch (Throwable err) { 
      System.err.println("Error..."); 
     } 
    } 

    private void show() { 
     System.out.println("I = " + i++); 
     show(); 
    } 
} 

// Tuy nhiên u có thể có một cái nhìn tại liên kết: http://marxsoftware.blogspot.in/2009/07/diagnosing-and-resolving.html để // hiểu snipets mã mà có thể gây lỗi

1

Hầu hết các cơ hội để có được StackOverflowError là bằng cách sử dụng [dài vô hạn /] recursions trong một hàm đệ quy.

Bạn có thể tránh đệ quy hàm bằng cách thay đổi thiết kế ứng dụng của bạn để sử dụng đối tượng dữ liệu có thể xếp chồng. Có các mẫu mã hóa để chuyển đổi các mã đệ quy thành các khối mã lặp lại. Hãy nhìn vào bên dưới answeres:

Vì vậy, bạn tránh bộ nhớ xếp chồng bằng Java cho các cuộc gọi chức năng lặn của bạn, bằng cách sử dụng ngăn xếp dữ liệu của riêng bạn.

0

trong một số trường hợp, bạn không thể bắt stackoverflowerror. bất cứ khi nào bạn thử, bạn sẽ gặp một cái mới. bởi vì là java vm. bạn nên tìm các khối mã đệ quy, chẳng hạn như Andrew Bullock's said.

Các vấn đề liên quan