Bắt java.lang.OutOfMemoryError?


101

Tài liệu cho java.lang.Errornói:

Lỗi là một lớp con của Throwable chỉ ra các vấn đề nghiêm trọng mà một ứng dụng hợp lý không nên cố gắng bắt

Nhưng cũng như java.lang.Errormột lớp con của java.lang.Throwable, tôi có thể bắt được kiểu Throwable này.

Tôi hiểu tại sao không nên bắt loại ngoại lệ này. Theo như tôi hiểu, nếu chúng ta quyết định bắt nó, trình xử lý bắt không nên tự cấp phát bất kỳ bộ nhớ nào. Nếu không OutOfMemoryErrorsẽ bị ném lại.

Vì vậy, câu hỏi của tôi là:

  1. Có bất kỳ tình huống thực tế nào khi bắt java.lang.OutOfMemoryErrorcó thể là một ý tưởng hay không?
  2. Nếu chúng ta quyết định bắt java.lang.OutOfMemoryError, làm thế nào chúng ta có thể đảm bảo trình xử lý bắt không tự cấp phát bất kỳ bộ nhớ nào (bất kỳ công cụ hoặc phương pháp hay nhất nào)?


Đối với câu hỏi đầu tiên của bạn, tôi sẽ nói thêm rằng tôi sẽ bắt OutOfMemoryError để (ít nhất là cố gắng) thông báo cho người dùng về sự cố. Trước đây, lỗi không được phát hiện bởi mệnh đề catch (Exception e) và không có phản hồi nào được hiển thị cho người dùng.
Josep Rodríguez López

1
Có những trường hợp cụ thể, ví dụ, phân bổ một mảng khổng lồ, nơi người ta có thể bắt lỗi OOM xung quanh hoạt động đó và khôi phục một cách hợp lý. Nhưng đặt thử / bắt xung quanh một khối mã lớn và cố gắng khôi phục sạch sẽ và tiếp tục có lẽ là một ý tưởng tồi.
Hot Licks vào

Câu trả lời:


85

Tôi đồng ý và không đồng ý với hầu hết các câu trả lời ở đây.

Có một số tình huống mà bạn có thể muốn bắt gặp OutOfMemoryErrorvà theo kinh nghiệm của tôi (trên Windows và Solaris JVM), chỉ rất hiếm khi là OutOfMemoryErrorhồi chuông báo tử đối với JVM.

Chỉ có một lý do chính đáng để nắm bắt OutOfMemoryErrorvà đó là đóng cửa một cách duyên dáng, giải phóng sạch sẽ tài nguyên và ghi lại lý do lỗi tốt nhất bạn có thể (nếu vẫn có thể làm như vậy).

Nói chung, sự cố OutOfMemoryErrorxảy ra do phân bổ bộ nhớ khối không thể thỏa mãn với các tài nguyên còn lại của heap.

Khi Errorđược ném, heap chứa cùng số lượng các đối tượng được cấp phát như trước khi cấp phát không thành công và bây giờ là lúc để giảm các tham chiếu đến các đối tượng thời gian chạy để giải phóng thêm bộ nhớ có thể cần để dọn dẹp. Trong những trường hợp này, bạn thậm chí có thể tiếp tục nhưng đó chắc chắn sẽ là một ý tưởng tồi vì bạn không bao giờ có thể chắc chắn 100% rằng JVM đang ở trạng thái sửa chữa.

Trình diễn OutOfMemoryErrorkhông có nghĩa là JVM hết bộ nhớ trong khối bắt:

private static final int MEGABYTE = (1024*1024);
public static void runOutOfMemory() {
    MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
    for (int i=1; i <= 100; i++) {
        try {
            byte[] bytes = new byte[MEGABYTE*500];
        } catch (Exception e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
            long maxMemory = heapUsage.getMax() / MEGABYTE;
            long usedMemory = heapUsage.getUsed() / MEGABYTE;
            System.out.println(i+ " : Memory Use :" + usedMemory + "M/" + maxMemory + "M");
        }
    }
}

Đầu ra của mã này:

1 : Memory Use :0M/247M
..
..
..
98 : Memory Use :0M/247M
99 : Memory Use :0M/247M
100 : Memory Use :0M/247M

Nếu đang chạy một thứ gì đó quan trọng, tôi thường bắt Error, đăng nhập nó vào syserr, sau đó ghi lại nó bằng cách sử dụng khung ghi nhật ký mà tôi lựa chọn, sau đó tiến hành giải phóng tài nguyên và đóng cửa một cách sạch sẽ. Sự tồi tệ nhất có thể xảy ra là gì? Dù sao thì JVM cũng đang chết (hoặc đã chết) và bằng cách bắt được Errornó thì ít nhất cũng có cơ hội dọn dẹp.

Lưu ý là bạn phải nhắm mục tiêu bắt các loại lỗi này chỉ ở những nơi có thể dọn dẹp. Đừng chăn catch(Throwable t) {}ở khắp mọi nơi hoặc vô nghĩa như thế.


Đồng ý, tôi sẽ đăng thử nghiệm của mình về một câu trả lời mới.
Mister Smith

4
"bạn không bao giờ có thể chắc chắn 100% rằng JVM đang ở trạng thái OutOfMemoryErrorcó thể sửa chữa được": bởi vì có thể đã được ném từ một điểm đã đặt chương trình yout ở trạng thái không nhất quán, vì nó có thể bị ném bất cứ lúc nào . Xem stackoverflow.com/questions/8728866/…
Raedwald

Trong OpenJdk1.7.0_40, tôi không gặp bất kỳ lỗi hoặc ngoại lệ nào khi tôi chạy mã này. Ngay cả khi tôi đã thay đổi MEGABYTE thành GIGABYTE (1024 * 1024 * 1024). Đó có phải là do trình tối ưu hóa loại bỏ biến 'byte [] byte' vì nó không được sử dụng trong phần còn lại của mã không?
RoboAlex

"Có một số tình huống mà bạn có thể muốn bắt OutOfMemoryError" so với "Chỉ có một lý do chính đáng để bắt OutOfMemoryError" . Hãy quyết tâm !!!
Stephen C

3
Kịch bản trong thế giới thực khi bạn CÓ THỂ muốn gặp lỗi OutOfMemory: khi lỗi này xảy ra do cố gắng cấp phát một mảng có nhiều hơn 2G phần tử. Tên lỗi có một chút nhầm lẫn trong trường hợp này, nhưng nó vẫn là một OOM.
Charles Roth

31

Bạn có thể khôi phục từ nó:

package com.stackoverflow.q2679330;

public class Test {

    public static void main(String... args) {
        int size = Integer.MAX_VALUE;
        int factor = 10;

        while (true) {
            try {
                System.out.println("Trying to allocate " + size + " bytes");
                byte[] bytes = new byte[size];
                System.out.println("Succeed!");
                break;
            } catch (OutOfMemoryError e) {
                System.out.println("OOME .. Trying again with 10x less");
                size /= factor;
            }
        }
    }

}

Nhưng nó có ý nghĩa không? Bạn muốn làm gì? Tại sao ban đầu bạn sẽ phân bổ nhiều bộ nhớ như vậy? Bộ nhớ ít hơn cũng OK? Tại sao bạn vẫn chưa tận dụng nó? Hoặc nếu điều đó là không thể, tại sao không chỉ cho JVM thêm bộ nhớ từ đầu?

Quay lại câu hỏi của bạn:

1: Có bất kỳ tình huống từ thực nào khi bắt java.lang.OutOfMemoryError có thể là một ý tưởng hay không?

Không có gì nghĩ đến.

2: Nếu chúng ta bắt java.lang.OutOfMemoryError, làm thế nào chúng ta có thể chắc chắn rằng trình xử lý bắt đó không tự cấp phát bất kỳ bộ nhớ nào (bất kỳ công cụ hoặc thiết bị tốt nhất nào)?

Phụ thuộc vào những gì đã gây ra OOME. Nếu nó được khai báo bên ngoài trykhối và nó xảy ra từng bước, thì cơ hội của bạn là rất ít. Bạn có thể muốn đặt trước một số không gian bộ nhớ:

private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB.

và sau đó đặt nó thành 0 trong OOME:

} catch (OutOfMemoryException e) {
     reserve = new byte[0];
     // Ha! 1MB free!
}

Tất nhiên điều này hoàn toàn không có ý nghĩa;) Chỉ cần cung cấp cho JVM đủ bộ nhớ như thiết bị của bạn yêu cầu. Chạy một hồ sơ nếu cần thiết.


1
Mặc dù vậy, ngay cả việc dành không gian cũng không đảm bảo cho một giải pháp hoạt động. Không gian mà có thể được thực hiện bởi một số chủ đề khác nữa;)
Wolph

@Wolph: Sau đó, cung cấp thêm bộ nhớ cho JVM! O_o Tất cả với tất cả nó thực sự không có ý nghĩa;)
BalusC

2
Đoạn mã đầu tiên hoạt động vì đối tượng gây ra lỗi là một đối tượng LỚN duy nhất (mảng). Khi đạt đến mệnh đề bắt, nó đã được thu thập bởi JVM đói bộ nhớ. Nếu bạn đang sử dụng cùng một đối tượng bên ngoài khối try, hoặc trong chuỗi khác, hoặc trong cùng một đối tượng, JVM sẽ không thu thập nó, khiến bạn không thể tạo một đối tượng đơn mới thuộc bất kỳ loại nào. ví dụ, đoạn mã thứ hai có thể không hoạt động.
Mister Smith

3
tại sao không chỉ đơn giản là đặt nó thành null?
Pacerier

1
@MisterSmith Nhận xét của bạn không có ý nghĩa. Đối tượng lớn không tồn tại. Nó không được phân bổ ngay từ đầu: nó đã kích hoạt OOM, vì vậy nó chắc chắn không cần GC.
Marquis of Lorne,

15

Nói chung, bạn nên cố gắng bắt và phục hồi từ OOM.

  1. Một OOME cũng có thể đã được ném vào các chuỗi khác, bao gồm các chuỗi mà ứng dụng của bạn thậm chí không biết về nó. Bất kỳ chuỗi nào như vậy bây giờ sẽ chết và bất kỳ thứ gì đang chờ thông báo có thể bị kẹt mãi mãi. Tóm lại, ứng dụng của bạn có thể bị hỏng.

  2. Ngay cả khi bạn khôi phục thành công, JVM của bạn vẫn có thể bị chết đói hàng loạt và kết quả là ứng dụng của bạn sẽ hoạt động kém hiệu quả.

Điều tốt nhất để làm với một OOME là để JVM chết.

(Điều này giả định rằng JVM thực sự chết. Ví dụ: OOM trên chuỗi servlet Tomcat không giết JVM và điều này dẫn đến Tomcat đi vào trạng thái catatonic nơi nó sẽ không phản hồi bất kỳ yêu cầu nào ... thậm chí không yêu cầu khởi động lại.)

BIÊN TẬP

Tôi không nói rằng bắt OOM là một ý kiến ​​tồi. Các vấn đề phát sinh khi bạn cố gắng khôi phục từ OOME, cố ý hoặc do giám sát. Bất cứ khi nào bạn bắt gặp một OOM (trực tiếp hoặc dưới dạng một loại phụ của Lỗi hoặc Có thể ném), bạn nên ném lại nó hoặc sắp xếp để ứng dụng / JVM thoát.

Bên cạnh đó: Điều này gợi ý rằng để có được sự mạnh mẽ tối đa khi đối mặt với các OOM, một ứng dụng nên sử dụng Thread.setDefaultUncaughtExceptionHandler () để thiết lập một trình xử lý sẽ khiến ứng dụng thoát ra trong trường hợp có OOME, bất kể OOME được ném vào chuỗi nào. Tôi muốn quan tâm đến ý kiến ​​về điều này ...

Kịch bản khác duy nhất là khi bạn biết chắc chắn rằng OOM không dẫn đến bất kỳ thiệt hại nào về tài sản thế chấp; tức là bạn biết:

  • điều gì đã gây ra OOME,
  • ứng dụng đang làm gì vào thời điểm đó và chỉ cần loại bỏ tính toán đó, và
  • rằng một OOME (gần như) đồng thời không thể xảy ra trên một chuỗi khác.

Có những ứng dụng có thể biết những điều này, nhưng đối với hầu hết các ứng dụng, bạn không thể biết chắc rằng việc tiếp tục sau một OOME là an toàn. Ngay cả khi nó "hoạt động" theo kinh nghiệm khi bạn thử nó.

(Vấn đề là cần phải có bằng chứng chính thức để chứng minh rằng hậu quả của các OOME "được dự đoán trước" là an toàn và các OOME "không lường trước" không thể xảy ra trong tầm kiểm soát của việc thử / bắt OOME.)


Vâng tôi đồng ý với bạn. Nói chung, đó là ý kiến ​​tồi. Nhưng tại sao sau đó tôi có khả năng nắm bắt nó? :)
Denis Bazhenov

@dotsid - 1) vì có những trường hợp bạn nên bắt nó, và 2) vì không thể bắt OOM sẽ có tác động tiêu cực đến ngôn ngữ và / hoặc các phần khác của thời gian chạy Java.
Stephen C

1
Bạn nói: "vì có trường hợp nên bắt được". Vì vậy, đây là một phần của câu hỏi ban đầu của tôi. Các trường hợp bạn muốn bắt OOME là gì?
Denis Bazhenov

1
@dotsid - xem câu trả lời đã chỉnh sửa của tôi. Trường hợp duy nhất tôi có thể nghĩ đến để bắt OOM là khi bạn cần làm điều này để buộc một ứng dụng đa luồng thoát ra trong trường hợp có OOM. Bạn có thể muốn làm điều này cho tất cả các loại phụ của Error.
Stephen C

1
Nó không phải là vấn đề chỉ bắt OOME. Bạn cũng cần phải phục hồi từ chúng. Làm thế nào để một luồng phục hồi nếu nó được cho là (nói) thông báo cho một luồng khác ... nhưng nó có một OOME? Chắc chắn, JVM sẽ không chết. Nhưng ứng dụng có thể ngừng hoạt động do các luồng bị kẹt khi chờ thông báo từ các luồng khởi động lại do bắt lỗi OOME.
Stephen C

14

Có, có những tình huống trong thế giới thực. Đây là của tôi: Tôi cần xử lý tập dữ liệu của rất nhiều mục trên một cụm với bộ nhớ hạn chế cho mỗi nút. Một cá thể JVM nhất định lần lượt trải qua nhiều mục, nhưng một số mục quá lớn để xử lý trên cụm: Tôi có thể nắm bắt OutOfMemoryErrorvà lưu ý những mục nào quá lớn. Sau đó, tôi chỉ có thể chạy lại các mục lớn trên máy tính có nhiều RAM hơn.

(Vì đó là một phân bổ đa gigabyte duy nhất của một mảng bị lỗi, JVM vẫn ổn sau khi phát hiện lỗi và có đủ bộ nhớ để xử lý các mục khác.)


Vậy bạn có mã như thế byte[] bytes = new byte[length]nào? Tại sao không chỉ kiểm tra sizeở một điểm sớm hơn?
Raedwald

1
Bởi vì tương tự sizesẽ ổn với nhiều bộ nhớ hơn. Tôi đi qua ngoại lệ vì trong hầu hết các trường hợp, mọi thứ sẽ ổn.
Michael Kuhn

10

Chắc chắn có những tình huống mà việc bắt một OOME có ý nghĩa. IDEA bắt chúng và bật lên một hộp thoại để cho phép bạn thay đổi cài đặt bộ nhớ khởi động (và sau đó thoát ra khi bạn hoàn tất). Máy chủ ứng dụng có thể bắt và báo cáo chúng. Chìa khóa để làm điều này là thực hiện nó ở cấp độ cao trên công văn để bạn có cơ hội hợp lý để có một loạt tài nguyên được giải phóng tại thời điểm bạn bắt được ngoại lệ.

Bên cạnh kịch bản IDEA ở trên, nói chung việc đánh bắt phải là Throwable, không chỉ OOM cụ thể và nên được thực hiện trong bối cảnh mà ít nhất luồng sẽ bị chấm dứt trong thời gian ngắn.

Tất nhiên, hầu hết các trường hợp bộ nhớ bị bỏ đói và tình hình không thể phục hồi được, nhưng có những cách mà nó có ý nghĩa.


8

Tôi gặp câu hỏi này vì tôi đang băn khoăn không biết có nên bắt OutOfMemoryError trong trường hợp của mình hay không. Tôi đang trả lời ở đây một phần để chỉ ra một ví dụ khác khi việc bắt lỗi này có thể có ý nghĩa với ai đó (tức là tôi) và một phần để tìm hiểu xem liệu đó có phải là ý kiến ​​hay trong trường hợp của tôi thực sự hay không (với tôi là một nhà phát triển uber Junior, tôi không bao giờ có thể quá chắc chắn về bất kỳ dòng mã nào tôi viết).

Dù sao, tôi đang làm việc trên một ứng dụng Android có thể chạy trên các thiết bị khác nhau với kích thước bộ nhớ khác nhau. Phần nguy hiểm là giải mã một bitmap từ một tệp và hiển thị nó trong một phiên bản ImageView. Tôi không muốn hạn chế các thiết bị mạnh hơn về kích thước của bitmap được giải mã, cũng như không thể chắc chắn rằng ứng dụng sẽ không được chạy trên một số thiết bị cổ mà tôi chưa từng gặp có bộ nhớ rất thấp. Do đó tôi làm điều này:

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 
bitmapOptions.inSampleSize = 1;
boolean imageSet = false;
while (!imageSet) {
  try {
    image = BitmapFactory.decodeFile(filePath, bitmapOptions);
    imageView.setImageBitmap(image); 
    imageSet = true;
  }
  catch (OutOfMemoryError e) {
    bitmapOptions.inSampleSize *= 2;
  }
}

Bằng cách này, tôi quản lý để cung cấp cho các thiết bị ngày càng ít mạnh mẽ hơn theo nhu cầu và mong đợi của người dùng.


1
Một tùy chọn khác sẽ là tính toán mức độ lớn của bitmap mà bạn có thể xử lý thay vì thử và thất bại. "Ngoại lệ nên được sử dụng cho những trường hợp ngoại lệ" - Tôi nghĩ ai đó đã nói. Nhưng tôi sẽ nói, giải pháp của bạn có vẻ là cách dễ nhất, có lẽ không phải là tốt nhất, nhưng có lẽ là dễ nhất.
jontejj

Nó phụ thuộc vào codec. Hãy tưởng tượng một bmp 10MB có thể dẫn đến chỉ hơn 10MB heap một chút, trong khi JPEG 10MB sẽ "phát nổ". Tương tự trong trường hợp của tôi, khi tôi muốn phân tích cú pháp một XML có thể khác nhau rất nhiều tùy thuộc vào độ phức tạp của nội dung
Daniel Alder

5

Có, câu hỏi thực sự là "bạn sẽ làm gì trong trình xử lý ngoại lệ?" Đối với hầu hết mọi thứ hữu ích, bạn sẽ phân bổ nhiều bộ nhớ hơn. Nếu bạn muốn thực hiện một số công việc chẩn đoán khi lỗi OutOfMemoryError xảy ra, bạn có thể sử dụng -XX:OnOutOfMemoryError=<cmd>hook do HotSpot VM cung cấp. Nó sẽ thực thi (các) lệnh của bạn khi một OutOfMemoryError xảy ra và bạn có thể làm điều gì đó hữu ích bên ngoài heap của Java. Bạn thực sự muốn giữ cho ứng dụng không bị hết bộ nhớ ngay từ đầu, vì vậy tìm hiểu lý do tại sao nó xảy ra là bước đầu tiên. Sau đó, bạn có thể tăng kích thước đống của MaxPermSize nếu thích hợp. Dưới đây là một số móc HotSpot hữu ích khác:

-XX:+PrintCommandLineFlags
-XX:+PrintConcurrentLocks
-XX:+PrintClassHistogram

Xem danh sách đầy đủ tại đây


Nó thậm chí còn tồi tệ hơn bạn nghĩ. Bởi vì một OutOfMemeoryError có thể được ném vào bất kỳ điểm nào trong chương trình của bạn (không chỉ từ một newcâu lệnh) nên chương trình của bạn sẽ ở trạng thái không xác định khi bạn bắt được exction.
Raedwald

5

Tôi có một ứng dụng cần khôi phục sau lỗi OutOfMemoryError và trong các chương trình đơn luồng, nó luôn hoạt động nhưng đôi khi không hoạt động trong các chương trình đa luồng. Ứng dụng này là một công cụ kiểm tra Java tự động thực hiện các chuỗi kiểm tra đã tạo ở độ sâu tối đa có thể trên các lớp kiểm tra. Bây giờ, giao diện người dùng phải ổn định, nhưng công cụ thử nghiệm có thể hết bộ nhớ trong khi phát triển cây các trường hợp thử nghiệm. Tôi xử lý điều này bằng loại mã thành ngữ sau trong công cụ thử nghiệm:

boolean isOutOfMemory = false; // cờ được sử dụng để báo cáo
thử {
   SomeType largeVar;
   // Vòng lặp chính cấp phát ngày càng nhiều cho LargeVar
   // có thể kết thúc OK hoặc tăng OutOfMemoryError
}
bắt (OutOfMemoryError ex) {
   // LargeVar hiện đã nằm ngoài phạm vi, rác cũng vậy
   Hệ thống.gc (); // dọn dẹp dữ liệu bigVar
   isOutOfMemory = true; // cờ có sẵn để sử dụng
}
// cờ kiểm tra chương trình để báo cáo khôi phục

Điều này hoạt động mọi lúc trong các ứng dụng đơn luồng. Nhưng gần đây tôi đã đặt công cụ thử nghiệm của mình vào một chuỗi công nhân riêng biệt từ giao diện người dùng. Bây giờ, việc hết bộ nhớ có thể xảy ra tùy ý trong một trong hai luồng và tôi không rõ làm cách nào để bắt được nó.

Ví dụ: tôi đã xảy ra sự cố OOME trong khi các khung của GIF động trong giao diện người dùng của tôi đang được xoay vòng bởi một chuỗi độc quyền được tạo ra ở hậu trường bởi một lớp Swing nằm ngoài tầm kiểm soát của tôi. Tôi đã nghĩ rằng tôi đã phân bổ tất cả các tài nguyên cần thiết từ trước, nhưng rõ ràng trình tạo hoạt ảnh đang phân bổ bộ nhớ mỗi khi nó tìm nạp hình ảnh tiếp theo. Nếu ai đó có ý tưởng về cách xử lý OOME được nêu ra trong bất kỳ chuỗi nào , tôi rất muốn nghe.


Trong một ứng dụng luồng đơn, nếu bạn không còn sử dụng một số đối tượng mới có vấn đề mà quá trình tạo của chúng đã gây ra lỗi, chúng có thể được thu thập trong mệnh đề bắt. Tuy nhiên, nếu JVM phát hiện ra rằng đối tượng có thể được sử dụng sau đó, nó sẽ không thể được thu thập và ứng dụng sẽ nổ tung. Xem câu trả lời của tôi trong chủ đề này.
Mister Smith

4

OOME có thể được bắt nhưng nhìn chung sẽ vô dụng, tùy thuộc vào việc JVM có thể thu thập một số đối tượng khi đạt đến bắt hay không và còn lại bao nhiêu bộ nhớ heap vào thời điểm đó.

Ví dụ: trong JVM của tôi, chương trình này chạy đến khi hoàn thành:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
        }
        System.out.println("Test finished");
    }  
}

Tuy nhiên, chỉ cần thêm một dòng duy nhất trên bản ghi sẽ cho bạn thấy điều tôi đang nói đến:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
            System.out.println("size:" +ll.size());
        }
        System.out.println("Test finished");
    }
}

Chương trình đầu tiên chạy tốt vì khi đạt đến bắt, JVM phát hiện rằng danh sách sẽ không được sử dụng nữa (Phát hiện này cũng có thể là một tối ưu hóa được thực hiện tại thời điểm biên dịch). Vì vậy, khi chúng ta đến câu lệnh in, bộ nhớ heap đã được giải phóng gần như hoàn toàn, vì vậy bây giờ chúng ta có rất nhiều thao tác để tiếp tục. Đây là trường hợp tốt nhất.

Tuy nhiên, nếu mã được sắp xếp như danh sách llđược sử dụng sau khi OOME đã được chốt, thì JVM không thể thu thập được. Điều này xảy ra trong đoạn mã thứ hai. OOME, được kích hoạt bởi một tạo Long mới, được bắt, nhưng chúng ta sẽ sớm tạo một Đối tượng mới (một Chuỗi trong System.out,printlndòng) và đống gần như đầy, vì vậy một OOME mới sẽ được ném ra. Đây là trường hợp xấu nhất: chúng tôi đã cố gắng tạo một đối tượng mới, chúng tôi không thành công, chúng tôi bắt được OOME, vâng, nhưng bây giờ lệnh đầu tiên yêu cầu bộ nhớ heap mới (ví dụ: tạo một đối tượng mới) sẽ tạo ra một OOME mới. Hãy nghĩ về nó, chúng ta có thể làm gì khác vào lúc này khi bộ nhớ còn lại rất ít ?. Có lẽ chỉ là xuất ngoại. Do đó vô ích.

Trong số các lý do khiến JVM không thu thập tài nguyên, có một lý do thực sự đáng sợ: một tài nguyên được chia sẻ với các luồng khác cũng đang sử dụng nó. Bất kỳ ai có não đều có thể thấy việc bắt OOME nguy hiểm như thế nào nếu được chèn vào một số ứng dụng phi thử nghiệm dưới bất kỳ hình thức nào.

Tôi đang sử dụng Windows x86 32bits JVM (JRE6). Bộ nhớ mặc định cho mỗi ứng dụng Java là 64MB.


Điều gì sẽ xảy ra nếu tôi làm ll=nullbên trong khối bắt?
Naanavanalla

3

Lý do duy nhất tôi có thể nghĩ ra tại sao bắt lỗi OOM có thể là bạn có một số cấu trúc dữ liệu lớn mà bạn không sử dụng nữa và có thể đặt thành null và giải phóng một số bộ nhớ. Nhưng (1) điều đó có nghĩa là bạn đang lãng phí bộ nhớ, và bạn nên sửa mã của mình thay vì cứ khập khiễng chạy theo một OOME, và (2) ngay cả khi bạn bắt gặp nó, bạn sẽ làm gì? OOM có thể xảy ra bất cứ lúc nào, có khả năng khiến mọi thứ hoàn thành một nửa.


3

Đối với câu hỏi 2, tôi đã thấy giải pháp mà tôi sẽ đề xuất, bởi BalusC.

  1. Có bất kỳ tình huống từ thực nào khi bắt java.lang.OutOfMemoryError có thể là một ý tưởng hay không?

Tôi nghĩ tôi vừa xem qua một ví dụ điển hình. Khi ứng dụng awt đang gửi tin nhắn, OutOfMemoryError chưa gửi được hiển thị trên stderr và quá trình xử lý tin nhắn hiện tại bị dừng. Nhưng ứng dụng vẫn tiếp tục chạy! Người dùng vẫn có thể đưa ra các lệnh khác mà không biết về các vấn đề nghiêm trọng xảy ra phía sau hiện trường. Đặc biệt là khi anh ta không thể hoặc không quan sát sai số chuẩn. Vì vậy, bắt được ngoại lệ oom và cung cấp (hoặc ít nhất là đề xuất) khởi động lại ứng dụng là điều mong muốn.


3

Tôi chỉ có một tình huống mà việc bắt OutOfMemoryError có vẻ hợp lý và có vẻ hoạt động.

Tình huống: trong Ứng dụng Android, tôi muốn hiển thị nhiều ảnh bitmap ở độ phân giải cao nhất có thể và tôi muốn có thể thu phóng chúng thành thạo.

Do khả năng thu phóng thông thạo, tôi muốn có các bitmap trong bộ nhớ. Tuy nhiên, Android có những hạn chế trong bộ nhớ là phụ thuộc vào thiết bị và khó kiểm soát.

Trong trường hợp này, có thể có OutOfMemoryError khi đọc bitmap. Ở đây, sẽ hữu ích nếu tôi nắm bắt được nó và sau đó tiếp tục với độ phân giải thấp hơn.


0
  1. Phụ thuộc vào cách bạn định nghĩa "tốt". Chúng tôi làm điều đó trong ứng dụng web lỗi của mình và nó hoạt động hầu hết thời gian (may mắn thay, bây giờ OutOfMemorykhông xảy ra do một bản sửa lỗi không liên quan). Tuy nhiên, ngay cả khi bạn nắm bắt được nó, nó vẫn có thể bị hỏng một số mã quan trọng: nếu bạn có nhiều luồng, cấp phát bộ nhớ có thể không thành công trong bất kỳ luồng nào trong số đó. Vì vậy, tùy thuộc vào ứng dụng của bạn, vẫn có 10-90% khả năng nó bị hỏng không thể phục hồi.
  2. Theo như tôi hiểu, việc giải nén ngăn xếp nặng trên đường đi sẽ làm mất hiệu lực rất nhiều tham chiếu và do đó giải phóng nhiều bộ nhớ mà bạn không nên quan tâm đến điều đó.

CHỈNH SỬA: Tôi khuyên bạn nên dùng thử. Giả sử, viết một chương trình gọi đệ quy một hàm cấp phát bộ nhớ tăng dần. Nắm bắt OutOfMemoryErrorvà xem liệu bạn có thể tiếp tục một cách có ý nghĩa từ thời điểm đó hay không. Theo kinh nghiệm của tôi, bạn sẽ có thể, mặc dù trong trường hợp của tôi, nó đã xảy ra dưới máy chủ WebLogic, vì vậy có thể đã có một số ma thuật đen liên quan.


-1

Bạn có thể bắt bất cứ thứ gì trong Throwable, nói chung bạn chỉ nên bắt các lớp con của Exception ngoại trừ RuntimeException (mặc dù một phần lớn các nhà phát triển cũng bắt RuntimeException ... nhưng đó không bao giờ là ý định của các nhà thiết kế ngôn ngữ).

Nếu bạn bắt gặp OutOfMemoryError bạn sẽ làm gì? Máy ảo hết bộ nhớ, về cơ bản tất cả những gì bạn có thể làm là thoát. Bạn có thể thậm chí không thể mở hộp thoại để cho họ biết bạn đã hết bộ nhớ vì điều đó sẽ chiếm bộ nhớ :-)

VM ném OutOfMemoryError khi nó thực sự hết bộ nhớ (thực sự là tất cả các Lỗi đều chỉ ra các tình huống không thể khôi phục) và bạn thực sự không thể làm gì để giải quyết nó.

Những việc cần làm là tìm hiểu lý do tại sao bạn sắp hết bộ nhớ (sử dụng một trình biên dịch, như trình biên dịch trong NetBeans) và đảm bảo rằng bạn không bị rò rỉ bộ nhớ. Nếu bạn không bị rò rỉ bộ nhớ thì hãy tăng bộ nhớ mà bạn cấp cho máy ảo.


7
Một quan niệm sai lầm mà bài viết của bạn vẫn tồn tại là OOM cho biết JVM đã hết bộ nhớ. Thay vào đó, nó thực sự chỉ ra rằng JVM không thể cấp phát tất cả bộ nhớ mà nó được hướng dẫn. Tức là, nếu JVM có 10B không gian và bạn 'tạo mới' một đối tượng 100B, nó sẽ không thành công, nhưng bạn có thể quay lại và 'tạo mới' một đối tượng 5B và ổn.
Tim Bender

1
Và nếu tôi chỉ cần 5B thì tại sao tôi lại yêu cầu 10B? Nếu bạn đang phân bổ dựa trên thử và sai, bạn đang làm sai.
TofuBeer

2
Tôi đoán ý của Tim rằng bạn vẫn có thể thực hiện một số công việc ngay cả với tình huống OutOfMemory. Chẳng hạn, có thể còn đủ bộ nhớ để mở một hộp thoại.
Stephen Eilert
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.