กำลังจับ java.lang.OutOfMemoryError?


102

เอกสารสำหรับjava.lang.Errorพูดว่า:

ข้อผิดพลาดเป็นคลาสย่อยของ Throwable ที่บ่งชี้ถึงปัญหาร้ายแรงที่แอปพลิเคชันที่สมเหตุสมผลไม่ควรพยายามจับ

แต่ในฐานะjava.lang.Errorคลาสย่อยjava.lang.Throwableฉันสามารถจับ Throwable ประเภทนี้ได้

ฉันเข้าใจว่าเหตุใดจึงไม่ควรจับข้อยกเว้นแบบนี้ เท่าที่ฉันเข้าใจถ้าเราตัดสินใจที่จะจับมันตัวจัดการจับไม่ควรจัดสรรหน่วยความจำใด ๆ ด้วยตัวเอง มิฉะนั้นOutOfMemoryErrorจะถูกโยนอีกครั้ง

ดังนั้นคำถามของฉันคือ:

  1. มีสถานการณ์ในโลกแห่งความเป็นจริงเมื่อจับได้java.lang.OutOfMemoryErrorอาจเป็นความคิดที่ดีหรือไม่?
  2. หากเราตัดสินใจที่จะจับjava.lang.OutOfMemoryErrorเราจะแน่ใจได้อย่างไรว่าตัวจัดการการจับไม่ได้จัดสรรหน่วยความจำใด ๆ ด้วยตัวเอง (เครื่องมือหรือแนวทางปฏิบัติที่ดีที่สุด)

3
คำถามที่คล้ายกัน: stackoverflow.com/questions/1692230/…และstackoverflow.com/questions/352780/…
BalusC

สำหรับคำถามแรกของคุณฉันจะเพิ่มว่าฉันจะตรวจจับ OutOfMemoryError เพื่อ (อย่างน้อยก็พยายาม) แจ้งให้ผู้ใช้ทราบถึงปัญหา ก่อนหน้านี้ข้อผิดพลาดไม่ถูกจับโดยประโยค catch (Exception e) และไม่มีข้อเสนอแนะใด ๆ แสดงให้ผู้ใช้เห็น
Josep RodríguezLópez

1
มีบางกรณีเช่นการจัดสรรอาร์เรย์ขนาดมหึมาซึ่งสามารถตรวจจับข้อผิดพลาด OOM รอบการดำเนินการนั้นและกู้คืนได้ดีพอสมควร แต่การวาง try / catch รอบ ๆ โค้ดขนาดใหญ่และพยายามกู้คืนอย่างหมดจดและดำเนินการต่ออาจเป็นความคิดที่ไม่ดี
Hot Licks

คำตอบ:


86

มีหลายสถานการณ์ที่คุณอาจต้องการOutOfMemoryErrorสัมผัสและจากประสบการณ์ของฉัน (บน Windows และ Solaris JVMs) มีเพียงไม่บ่อยนักที่จะOutOfMemoryErrorต้องเสียชีวิตกับ JVM

มีเพียงเหตุผลเดียวที่ดีที่จะจับOutOfMemoryErrorและนั่นคือการปิดอย่างสง่างามปล่อยทรัพยากรอย่างหมดจดและบันทึกสาเหตุของความล้มเหลวที่ดีที่สุดที่คุณสามารถทำได้ (หากยังสามารถทำได้)

โดยทั่วไปOutOfMemoryErrorเกิดขึ้นเนื่องจากการจัดสรรหน่วยความจำบล็อกที่ไม่สามารถพอใจกับทรัพยากรที่เหลืออยู่ของฮีป

เมื่อErrorถูกโยนฮีปจะมีอ็อบเจ็กต์ที่จัดสรรจำนวนเท่ากันกับก่อนการจัดสรรที่ไม่สำเร็จและตอนนี้เป็นเวลาที่จะทิ้งการอ้างอิงไปยังอ็อบเจ็กต์รันไทม์เพื่อเพิ่มหน่วยความจำที่จำเป็นสำหรับการล้าง ในกรณีเหล่านี้อาจเป็นไปได้ที่จะดำเนินการต่อ แต่นั่นจะเป็นความคิดที่ไม่ดีอย่างแน่นอนเนื่องจากคุณไม่สามารถมั่นใจได้ 100% ว่า JVM อยู่ในสถานะที่ซ่อมแซมได้

การสาธิตที่OutOfMemoryErrorไม่ได้หมายความว่า JVM ไม่มีหน่วยความจำในบล็อก catch:

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");
        }
    }
}

ผลลัพธ์ของรหัสนี้:

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

หากเรียกใช้สิ่งที่สำคัญฉันมักจะจับErrorบันทึกลงใน syserr จากนั้นบันทึกโดยใช้กรอบการบันทึกที่ฉันเลือกจากนั้นดำเนินการปล่อยทรัพยากรและปิดตัวลงอย่างสะอาดตา อะไรที่เลวร้ายที่สุดที่อาจเกิดขึ้น? JVM กำลังจะตาย (หรือตายไปแล้ว) อยู่ดีและด้วยการจับได้Errorก็มีโอกาสอย่างน้อยในการล้างข้อมูล

ข้อแม้คือคุณต้องกำหนดเป้าหมายการจับข้อผิดพลาดประเภทนี้เฉพาะในสถานที่ที่สามารถล้างข้อมูลได้ อย่าคลุมโปงcatch(Throwable t) {}ทุกที่หรือเรื่องไร้สาระแบบนั้น


ตกลงฉันจะโพสต์การทดสอบของฉันเกี่ยวกับคำตอบใหม่
Mister Smith

4
"คุณไม่สามารถมั่นใจได้ 100% ว่า JVM อยู่ในสถานะที่ซ่อมแซมได้" เนื่องจากOutOfMemoryErrorอาจมีการโยนจากจุดที่ทำให้โปรแกรม yout อยู่ในสถานะที่ไม่สอดคล้องกันเนื่องจากสามารถโยนได้ตลอดเวลา ดูstackoverflow.com/questions/8728866/…
Raedwald

ใน OpenJdk1.7.0_40 ฉันไม่ได้รับข้อผิดพลาดหรือข้อยกเว้นใด ๆ เมื่อฉันเรียกใช้รหัสนี้ แม้ฉันจะเปลี่ยน MEGABYTE เป็น GIGABYTE (1024 * 1024 * 1024) เป็นเพราะเครื่องมือเพิ่มประสิทธิภาพลบตัวแปร 'ไบต์ [] ไบต์' เนื่องจากไม่ได้ใช้ในโค้ดส่วนที่เหลือหรือไม่
RoboAlex

"มีจำนวนของสถานการณ์ที่คุณอาจต้องการที่จะจับ OutOfMemoryError คือ"เมื่อเทียบกับ"มีเพียงหนึ่งเหตุผลที่ดีในการจับ OutOfMemoryError คือ" ให้ขึ้นใจของคุณ!!!
Stephen C

3
สถานการณ์จริงเมื่อคุณอาจต้องการตรวจจับข้อผิดพลาด OutOfMemory: เมื่อมันเกิดจากการพยายามจัดสรรอาร์เรย์ที่มีองค์ประกอบมากกว่า 2G ชื่อข้อผิดพลาดเป็นชื่อที่ผิดเล็กน้อยในกรณีนี้ แต่ยังคงเป็น OOM
Charles Roth

31

คุณสามารถกู้คืนได้:

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;
            }
        }
    }

}

แต่มันสมเหตุสมผลหรือไม่? คุณอยากทำอะไรอีก ทำไมตอนแรกคุณจัดสรรหน่วยความจำมากขนาดนั้น? หน่วยความจำน้อยลงด้วยหรือไม่? ทำไมคุณไม่ใช้มันต่อไปล่ะ? หรือถ้าเป็นไปไม่ได้ทำไมไม่เพียงแค่ให้หน่วยความจำ JVM เพิ่มขึ้นตั้งแต่ต้น

กลับไปที่คำถามของคุณ:

1: มีสถานการณ์คำจริงหรือไม่เมื่อจับ java.lang.OutOfMemoryError อาจเป็นความคิดที่ดี?

ไม่มีใครอยู่ในใจ

2: ถ้าเราจับ java.lang.OutOfMemoryError เราจะแน่ใจได้อย่างไรว่าตัวจัดการจับไม่ได้จัดสรรหน่วยความจำด้วยตัวเอง (เครื่องมือใด ๆ หรือแนวทางปฏิบัติที่ดีที่สุด)

ขึ้นอยู่กับสิ่งที่ทำให้เกิด OOME หากมีการประกาศนอกtryบล็อกและเกิดขึ้นทีละขั้นตอนโอกาสของคุณก็มีน้อย คุณอาจต้องการจองพื้นที่หน่วยความจำไว้ล่วงหน้า:

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

จากนั้นตั้งค่าเป็นศูนย์ระหว่าง OOME:

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

แน่นอนว่าสิ่งนี้ไม่สมเหตุสมผลเลย) เพียงแค่ให้หน่วยความจำ JVM เพียงพอตามที่คุณต้องการ เรียกใช้ผู้สร้างโปรไฟล์หากจำเป็น


1
แม้แต่การจองพื้นที่ก็ไม่รับประกันว่าจะได้โซลูชันที่ใช้งานได้จริง ช่องว่างนั้นอาจถูกใช้โดยเธรดอื่นด้วย)
Wolph

@ วอล์ฟ: จากนั้นเพิ่มหน่วยความจำ JVM ให้มากขึ้น! O_o ทั้งหมดมันไม่มีเหตุผลจริงๆ;)
BalusC

2
ตัวอย่างแรกใช้งานได้เนื่องจากออบเจ็กต์ที่ทริกเกอร์ข้อผิดพลาดเป็นอ็อบเจ็กต์ BIG เดียว (อาร์เรย์) เมื่อถึงประโยค catch จะถูกรวบรวมโดย JVM ที่หิวโหยหน่วยความจำ หากคุณใช้อ็อบเจ็กต์เดียวกันนอกบล็อกลองหรือในเธรดอื่นหรือในการจับเดียวกัน JVM จะไม่รวบรวมมันทำให้ไม่สามารถสร้างอ็อบเจ็กต์เดี่ยวใหม่ได้ทุกชนิด ตัวอย่างเช่นตัวอย่างที่สองอาจไม่ทำงาน
Mister Smith

3
ทำไมไม่เพียงแค่ตั้งค่าเป็นnull?
Pacerier

1
@MisterSmith ความคิดเห็นของคุณไม่สมเหตุสมผล ไม่มีวัตถุขนาดใหญ่ ไม่ได้รับการจัดสรรตั้งแต่แรก: เรียกใช้ OOM ดังนั้นจึงไม่จำเป็นต้องใช้ GC อย่างแน่นอน
Marquis of Lorne

16

โดยทั่วไปเป็นความคิดที่ดีที่จะพยายามจับและกู้คืนจาก OOM

  1. OOME อาจถูกส่งไปยังเธรดอื่น ๆ รวมถึงเธรดที่แอปพลิเคชันของคุณไม่รู้ด้วยซ้ำ เธรดดังกล่าวจะถูกปิดใช้งานและทุกสิ่งที่รอการแจ้งเตือนอาจติดขัดตลอดไป ในระยะสั้นแอปของคุณอาจเสียในที่สุด

  2. แม้ว่าคุณจะกู้คืนได้สำเร็จ แต่ JVM ของคุณอาจยังคงทุกข์ทรมานจากความอดอยากเป็นจำนวนมากและแอปพลิเคชันของคุณจะทำงานได้อย่างยอดเยี่ยม

สิ่งที่ดีที่สุดในการทำ OOME คือปล่อยให้ JVM ตาย

(นี้อนุมานว่า JVM ไม่ตาย. สำหรับ Ooms เช่นในหัวข้อเซิร์ฟเล็ต Tomcat ไม่ฆ่า JVM และนำไปสู่การนี้เพื่อ Tomcat จะเป็นรัฐที่ไม่สามารถเคลื่อนไหวได้ที่มันจะไม่ตอบสนองต่อการร้องขอใด ๆ ... ไม่ได้ร้องขอ เริ่มต้นใหม่.)

แก้ไข

ฉันไม่ได้บอกว่ามันเป็นความคิดที่ไม่ดีที่จะจับ OOM เลย ปัญหาเกิดขึ้นเมื่อคุณพยายามกู้คืนจาก OOME ไม่ว่าจะโดยเจตนาหรือโดยการกำกับดูแล เมื่อใดก็ตามที่คุณตรวจจับ OOM (โดยตรงหรือเป็นประเภทย่อยของ Error หรือ Throwable) คุณควรเปลี่ยนใหม่หรือจัดการให้แอปพลิเคชัน / JVM ออก

นอกเหนือ: สิ่งนี้ชี้ให้เห็นว่าเพื่อความทนทานสูงสุดในการเผชิญกับ OOM แอปพลิเคชันควรใช้Thread.setDefaultUncaughtExceptionHandler ()เพื่อตั้งค่าตัวจัดการที่จะทำให้แอปพลิเคชันออกจากการทำงานในกรณีที่ OOME ไม่ว่า OOME จะโยนเธรดใดก็ตาม ฉันสนใจความคิดเห็นเกี่ยวกับเรื่องนี้ ...

อีกสถานการณ์เดียวคือเมื่อคุณทราบแน่นอนว่า OOM ไม่ได้ส่งผลให้หลักประกันเสียหายใด ๆ เช่นคุณรู้ว่า:

  • สิ่งที่ทำให้เกิด OOME โดยเฉพาะ
  • สิ่งที่แอปพลิเคชันกำลังทำอยู่ในขณะนั้นและเป็นเรื่องปกติที่จะทิ้งการคำนวณนั้นและ
  • ที่ OOME พร้อมกัน (โดยประมาณ) ไม่สามารถเกิดขึ้นบนเธรดอื่นได้

มีแอปพลิเคชันที่สามารถทราบสิ่งเหล่านี้ได้ แต่สำหรับแอปพลิเคชันส่วนใหญ่คุณไม่สามารถทราบได้อย่างแน่นอนว่าความต่อเนื่องหลังจาก OOME นั้นปลอดภัย แม้ว่าจะ "ได้ผล" ในเชิงประจักษ์เมื่อคุณลองใช้

(ปัญหาคือต้องมีการพิสูจน์อย่างเป็นทางการเพื่อแสดงให้เห็นว่าผลที่ตามมาของ OOME ที่ "คาดการณ์ไว้" นั้นปลอดภัยและ OOME ที่ "ไม่คาดคิด" นั้นไม่สามารถเกิดขึ้นได้ภายใต้การควบคุมของ OOME)


ใช่ฉันเห็นด้วยกับคุณ. โดยทั่วไปแล้วมันเป็นความคิดที่ไม่ดี แต่ทำไมฉันมีความเป็นไปได้ที่จะจับมัน? :)
Denis Bazhenov

@dotsid - 1) เนื่องจากมีบางกรณีที่คุณควรจับมันและ 2) เนื่องจากการไม่จับ OOM จะส่งผลเสียต่อภาษาและ / หรือส่วนอื่น ๆ ของรันไทม์ Java
Stephen C

1
คุณพูดว่า: "เพราะมีหลายกรณีที่คุณควรจับ" นี่จึงเป็นส่วนหนึ่งของคำถามเดิมของฉัน มีกรณีใดบ้างที่คุณต้องการจับ OOME
Denis Bazhenov

1
@dotsid - ดูคำตอบที่แก้ไขของฉัน กรณีเดียวที่ฉันคิดได้ในการจับ OOM คือเมื่อคุณต้องทำสิ่งนี้เพื่อบังคับให้แอปพลิเคชันแบบมัลติเธรดออกในกรณีที่ OOM คุณอาจต้องการทำสิ่งนี้สำหรับประเภทย่อยทั้งหมดของError.
Stephen C

1
ไม่ใช่เรื่องของการจับ OOME คุณต้องกู้คืนจากพวกเขาด้วย เธรดจะกู้คืนได้อย่างไรหากควร (พูด) แจ้งเธรดอื่น ... แต่มี OOME? แน่นอนว่า JVM จะไม่ตาย แต่แอปพลิเคชันมีแนวโน้มที่จะหยุดทำงานเนื่องจากเธรดค้างรอการแจ้งเตือนจากเธรดที่เริ่มต้นใหม่เนื่องจากการจับ OOME
Stephen C

14

ใช่มีสถานการณ์ในโลกแห่งความเป็นจริง นี่คือของฉัน: ฉันต้องการประมวลผลชุดข้อมูลของรายการจำนวนมากในคลัสเตอร์ที่มีหน่วยความจำ จำกัด ต่อโหนด อินสแตนซ์ JVM ที่กำหนดจะต้องผ่านหลายรายการทีละรายการ แต่บางรายการมีขนาดใหญ่เกินกว่าที่จะประมวลผลในคลัสเตอร์: ฉันสามารถตรวจจับOutOfMemoryErrorและจดบันทึกได้ว่ารายการใดใหญ่เกินไป ต่อมาฉันสามารถเรียกใช้เฉพาะรายการขนาดใหญ่บนคอมพิวเตอร์ที่มี RAM มากขึ้น

(เนื่องจากเป็นการจัดสรรอาร์เรย์หลายกิกะไบต์เดียวที่ล้มเหลว JVM จึงยังคงใช้ได้ดีหลังจากตรวจพบข้อผิดพลาดและมีหน่วยความจำเพียงพอที่จะประมวลผลรายการอื่น ๆ )


คุณมีรหัสเช่นbyte[] bytes = new byte[length]? ทำไมไม่ตรวจสอบsizeในจุดก่อนหน้านี้?
Raedwald

1
เพราะสิ่งเดียวกันsizeจะดีกับหน่วยความจำมากขึ้น ฉันผ่านข้อยกเว้นเพราะในกรณีส่วนใหญ่ทุกอย่างจะดี
Michael Kuhn

10

มีหลายสถานการณ์ที่การจับ OOME เหมาะสม IDEA จับพวกเขาและเปิดกล่องโต้ตอบเพื่อให้คุณเปลี่ยนการตั้งค่าหน่วยความจำเริ่มต้น (แล้วออกเมื่อคุณทำเสร็จแล้ว) แอ็พพลิเคชันเซิร์ฟเวอร์อาจตรวจจับและรายงานได้ กุญแจสำคัญในการดำเนินการนี้คือการทำในระดับสูงในการจัดส่งเพื่อให้คุณมีโอกาสที่เหมาะสมในการมีทรัพยากรจำนวนมากในจุดที่คุณกำลังจับข้อยกเว้น

นอกเหนือจากสถานการณ์ IDEA ข้างต้นโดยทั่วไปการจับควรเป็นแบบ Throwable ไม่ใช่เฉพาะ OOM โดยเฉพาะและควรทำในบริบทที่อย่างน้อยเธรดจะถูกยุติในไม่ช้า

แน่นอนว่าส่วนใหญ่แล้วหน่วยความจำจะอดอยากและสถานการณ์ไม่สามารถกู้คืนได้ แต่มีหลายวิธีที่เหมาะสม


8

ฉันเจอคำถามนี้เพราะฉันสงสัยว่าควรจับ OutOfMemoryError ในกรณีของฉันหรือไม่ ฉันกำลังตอบที่นี่บางส่วนเพื่อแสดงอีกตัวอย่างหนึ่งเมื่อการจับข้อผิดพลาดนี้อาจสมเหตุสมผลกับใครบางคน (เช่นฉัน) และบางส่วนเพื่อดูว่ามันเป็นความคิดที่ดีในกรณีของฉันหรือไม่ (ด้วยฉันเป็นนักพัฒนา uber รุ่นเยาว์ฉันไม่สามารถทำได้ แน่ใจเกินไปเกี่ยวกับโค้ดบรรทัดเดียวที่ฉันเขียน)

อย่างไรก็ตามฉันกำลังทำงานกับแอปพลิเคชัน Android ซึ่งสามารถทำงานบนอุปกรณ์ต่างๆที่มีขนาดหน่วยความจำต่างกัน ส่วนที่อันตรายคือการถอดรหัสบิตแมปจากไฟล์และไม่เล่นในอินสแตนซ์ ImageView ฉันไม่ต้องการ จำกัด อุปกรณ์ที่ทรงพลังกว่านี้ในแง่ของขนาดของบิตแมปที่ถอดรหัสและไม่แน่ใจว่าแอปจะไม่ทำงานบนอุปกรณ์โบราณบางอย่างที่ฉันไม่เคยเจอด้วยหน่วยความจำต่ำมาก ดังนั้นฉันจึงทำสิ่งนี้:

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;
  }
}

ด้วยวิธีนี้ฉันจัดการเพื่อจัดหาอุปกรณ์ที่มีประสิทธิภาพมากขึ้นและน้อยลงตามความต้องการและความคาดหวังของผู้ใช้


1
อีกทางเลือกหนึ่งคือการคำนวณว่าคุณสามารถจัดการบิตแมปขนาดใหญ่ได้อย่างไรแทนที่จะพยายามแล้วล้มเหลว "ควรใช้ข้อยกเว้นสำหรับกรณีพิเศษ" - ฉันคิดว่ามีคนพูด แต่ฉันจะบอกว่าวิธีแก้ปัญหาของคุณดูเหมือนจะเป็นวิธีที่ง่ายที่สุดอาจไม่ใช่วิธีที่ดีที่สุด แต่อาจจะง่ายที่สุด
jontejj

ขึ้นอยู่กับตัวแปลงสัญญาณ ลองนึกภาพ bmp 10MB อาจส่งผลให้ฮีปมากกว่า 10MB เพียงเล็กน้อยในขณะที่ JPEG 10MB จะ "ระเบิด" เหมือนกันในกรณีของฉันที่ฉันต้องการแยกวิเคราะห์ XML ซึ่งอาจแตกต่างกันอย่างมากขึ้นอยู่กับความซับซ้อนของเนื้อหา
Daniel Alder

5

ใช่คำถามที่แท้จริงคือ "คุณจะทำอะไรในตัวจัดการข้อยกเว้น" คุณจะจัดสรรหน่วยความจำเพิ่มเติมเพื่อประโยชน์เกือบทุกอย่าง หากคุณต้องการทำงานวินิจฉัยบางอย่างเมื่อเกิด OutOfMemoryError คุณสามารถใช้-XX:OnOutOfMemoryError=<cmd>เบ็ดที่ HotSpot VM ให้มาได้ มันจะดำเนินการคำสั่งของคุณเมื่อเกิด OutOfMemoryError และคุณสามารถทำสิ่งที่มีประโยชน์นอกฮีปของ Java คุณต้องการป้องกันไม่ให้แอปพลิเคชั่นใช้หน่วยความจำจนหมดตั้งแต่แรกดังนั้นการหาสาเหตุจึงเป็นขั้นตอนแรก จากนั้นคุณสามารถเพิ่มขนาดฮีปของ MaxPermSize ได้ตามความเหมาะสม นี่คือตะขอ HotSpot ที่มีประโยชน์อื่น ๆ :

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

ดูรายชื่อทั้งหมดได้ที่นี่


มันแย่กว่าที่คุณคิดด้วยซ้ำ เนื่องจากOutOfMemeoryError สามารถโยนได้ทุกจุดในโปรแกรมของคุณ (ไม่เพียง แต่จากnewคำสั่งเท่านั้น) โปรแกรมของคุณจะอยู่ในสถานะที่ไม่ได้กำหนดเมื่อคุณตรวจจับการออก
Raedwald

5

ฉันมีแอปพลิเคชันที่ต้องการกู้คืนจากความล้มเหลวของ OutOfMemoryError และในโปรแกรมแบบเธรดเดียวจะใช้งานได้เสมอ แต่บางครั้งก็ใช้ไม่ได้ในโปรแกรมแบบมัลติเธรด แอปพลิเคชันนี้เป็นเครื่องมือทดสอบ Java อัตโนมัติที่ดำเนินการลำดับการทดสอบที่สร้างขึ้นตามความลึกสูงสุดที่เป็นไปได้ในคลาสทดสอบ ตอนนี้ UI ต้องมีความเสถียร แต่เครื่องมือทดสอบอาจใช้หน่วยความจำไม่เพียงพอในขณะที่ปลูกต้นไม้ของกรณีทดสอบ ฉันจัดการสิ่งนี้ด้วยสำนวนรหัสประเภทต่อไปนี้ในเครื่องมือทดสอบ:

บูลีน isOutOfMemory = false; // แฟล็กที่ใช้สำหรับการรายงาน
ลอง {
   SomeType largeVar;
   // ลูปหลักที่จัดสรรให้ largeVar มากขึ้นเรื่อย ๆ
   // อาจยุติตกลงหรือเพิ่ม OutOfMemoryError
}
catch (OutOfMemoryError เช่น) {
   // largeVar อยู่นอกขอบเขตขยะก็เช่นกัน
   System.gc (); // ล้างข้อมูล largeVar
   isOutOfMemory = จริง; // ตั้งค่าสถานะพร้อมใช้งาน
}
// ตั้งค่าสถานะการทดสอบโปรแกรมเพื่อรายงานการกู้คืน

สิ่งนี้ใช้ได้ทุกครั้งในแอปพลิเคชันแบบเธรดเดียว แต่ฉันเพิ่งใส่เครื่องมือทดสอบของฉันลงในเธรดผู้ปฏิบัติงานแยกต่างหากจาก UI ตอนนี้หน่วยความจำไม่เพียงพออาจเกิดขึ้นโดยพลการในเธรดใดเธรดหนึ่งและไม่ชัดเจนสำหรับฉันว่าจะจับมันได้อย่างไร

ตัวอย่างเช่นฉันมี OOME เกิดขึ้นในขณะที่เฟรมของ GIF แบบเคลื่อนไหวใน UI ของฉันถูกวนรอบโดยเธรดที่เป็นกรรมสิทธิ์ซึ่งสร้างขึ้นเบื้องหลังโดยคลาส Swing ที่ไม่สามารถควบคุมได้ ฉันเคยคิดว่าฉันได้จัดสรรทรัพยากรทั้งหมดที่จำเป็นไว้ล่วงหน้า แต่เห็นได้ชัดว่าแอนิเมเตอร์กำลังจัดสรรหน่วยความจำทุกครั้งที่ดึงภาพถัดไป หากใครมีความคิดเกี่ยวกับวิธีจัดการ OOME ที่ยกขึ้นในกระทู้ใด ๆฉันชอบที่จะได้ยิน


ในแอพที่มีเธรดเดียวหากคุณไม่ได้ใช้อ็อบเจ็กต์ใหม่ที่มีปัญหาอีกต่อไปซึ่งการสร้างทำให้เกิดข้อผิดพลาดสิ่งเหล่านี้อาจถูกรวบรวมไว้ในประโยค catch อย่างไรก็ตามหาก JVM ตรวจพบว่าอาจมีการใช้วัตถุในภายหลังจะไม่สามารถรวบรวมได้และแอปจะระเบิด ดูคำตอบของฉันในหัวข้อนี้
Mister Smith

4

OOME สามารถจับได้ แต่โดยทั่วไปจะไม่มีประโยชน์ขึ้นอยู่กับว่า JVM สามารถเก็บขยะบางวัตถุได้หรือไม่เมื่อถึงที่จับและจำนวนหน่วยความจำฮีปที่เหลือในเวลานั้น

ตัวอย่าง: ใน JVM ของฉันโปรแกรมนี้ทำงานจนเสร็จสมบูรณ์:

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");
    }  
}

อย่างไรก็ตามการเพิ่มบรรทัดเดียวในการจับจะแสดงให้คุณเห็นว่าฉันกำลังพูดถึงอะไร:

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");
    }
}

โปรแกรมแรกทำงานได้ดีเนื่องจากเมื่อถึงจุดจับ JVM จะตรวจพบว่ารายการจะไม่ถูกใช้อีกต่อไป (การตรวจจับนี้อาจเป็นการเพิ่มประสิทธิภาพในเวลาคอมไพล์) ดังนั้นเมื่อเราไปถึงคำสั่งพิมพ์หน่วยความจำฮีปได้รับการปลดปล่อยเกือบทั้งหมดดังนั้นตอนนี้เราจึงมีการซ้อมรบที่กว้างเพื่อดำเนินการต่อ นี่เป็นกรณีที่ดีที่สุด

อย่างไรก็ตามหากมีการจัดเรียงรหัสเช่นรายการllถูกใช้หลังจาก OOME ถูกจับแล้ว JVM จะไม่สามารถรวบรวมได้ สิ่งนี้เกิดขึ้นในตัวอย่างข้อมูลที่สอง OOME ที่ถูกเรียกโดยการสร้างแบบยาวใหม่จะถูกจับ แต่ในไม่ช้าเรากำลังสร้างวัตถุใหม่ (สตริงในSystem.out,printlnบรรทัด) และฮีปใกล้จะเต็มแล้วดังนั้น OOME ใหม่จึงถูกโยนทิ้งไป นี่เป็นสถานการณ์ที่เลวร้ายที่สุด: เราพยายามสร้างวัตถุใหม่เราล้มเหลวเราจับ OOME ได้ แต่ตอนนี้คำสั่งแรกที่ต้องใช้หน่วยความจำฮีปใหม่ (เช่นการสร้างวัตถุใหม่) จะทำให้ OOME ใหม่เกิดขึ้น ลองคิดดูว่าเราจะทำอะไรได้อีกบ้างในตอนนี้ที่เหลือความทรงจำเพียงเล็กน้อย?. อาจเป็นเพียงการออก ดังนั้นไร้ประโยชน์

ในบรรดาเหตุผลที่ JVM ไม่รวบรวมทรัพยากรสิ่งหนึ่งที่น่ากลัวมากคือทรัพยากรที่ใช้ร่วมกันกับเธรดอื่น ๆ ก็ใช้ประโยชน์จากมันเช่นกัน ใครก็ตามที่มีสมองสามารถมองเห็นได้ว่าการจับ OOME นั้นอันตรายเพียงใดหากแทรกในแอปที่ไม่ได้ทดลองใด ๆ

ฉันใช้ Windows x86 32bits JVM (JRE6) หน่วยความจำเริ่มต้นสำหรับแต่ละแอป Java คือ 64MB


จะเกิดอะไรขึ้นถ้าฉันทำll=nullในบล็อกจับ?
Naanavanalla

3

เหตุผลเดียวที่ฉันคิดได้ว่าทำไมการจับข้อผิดพลาด OOM อาจเป็นเพราะคุณมีโครงสร้างข้อมูลขนาดใหญ่ที่คุณไม่ได้ใช้อีกต่อไปและสามารถตั้งค่าเป็นโมฆะและเพิ่มหน่วยความจำ แต่ (1) นั่นหมายความว่าคุณกำลังใช้หน่วยความจำอย่างสิ้นเปลืองและคุณควรแก้ไขโค้ดของคุณแทนที่จะเดินกะเผลกตาม OOME และ (2) แม้ว่าคุณจะจับได้คุณจะทำอย่างไร? OOM สามารถเกิดขึ้นได้ทุกเมื่อโดยอาจทิ้งทุกอย่างไว้ครึ่งหนึ่ง


3

สำหรับคำถาม 2 ฉันเห็นวิธีแก้ปัญหาแล้วที่ฉันจะแนะนำโดย BalusC

  1. มีสถานการณ์คำจริงหรือไม่เมื่อจับ java.lang.OutOfMemoryError อาจเป็นความคิดที่ดี?

ฉันคิดว่าฉันเพิ่งเจอตัวอย่างที่ดี เมื่อแอ็พพลิเคชัน awt ส่งข้อความ OutOfMemoryError ที่ไม่ได้จับคู่จะแสดงบน stderr และการประมวลผลข้อความปัจจุบันจะหยุดลง แต่แอปพลิเคชันยังคงทำงานอยู่! ผู้ใช้อาจยังคงออกคำสั่งอื่นโดยไม่ทราบถึงปัญหาร้ายแรงที่เกิดขึ้นเบื้องหลัง โดยเฉพาะอย่างยิ่งเมื่อเขาไม่สามารถหรือไม่สังเกตข้อผิดพลาดมาตรฐาน ดังนั้นการจับข้อยกเว้นและการให้ (หรืออย่างน้อยก็แนะนำ) การรีสตาร์ทแอปพลิเคชันจึงเป็นสิ่งที่ต้องการ


3

ฉันมีสถานการณ์ที่การจับ OutOfMemoryError ดูเหมือนจะสมเหตุสมผลและดูเหมือนว่าจะได้ผล

สถานการณ์: ในแอพ Android ฉันต้องการแสดงบิตแมปหลาย ๆ บิตด้วยความละเอียดสูงสุดเท่าที่จะเป็นไปได้และฉันต้องการที่จะซูมได้อย่างคล่องแคล่ว

เนื่องจากการซูมที่คล่องแคล่วฉันจึงต้องการมีบิตแมปในหน่วยความจำ อย่างไรก็ตาม Android มีข้อ จำกัด ในหน่วยความจำซึ่งขึ้นอยู่กับอุปกรณ์และยากที่จะควบคุม

ในสถานการณ์นี้อาจมี OutOfMemoryError ขณะอ่านบิตแมป ที่นี่จะช่วยได้ถ้าฉันจับได้แล้วทำต่อด้วยความละเอียดต่ำกว่านี้


0
  1. ขึ้นอยู่กับว่าคุณนิยามว่า "ดี". เราทำเช่นนั้นในเว็บแอปพลิเคชัน buggy ของเราและทำงานได้เกือบตลอดเวลา (โชคดีที่ตอนนี้OutOfMemoryไม่เกิดขึ้นเนื่องจากการแก้ไขที่ไม่เกี่ยวข้อง) อย่างไรก็ตามแม้ว่าคุณจะจับมันได้ แต่ก็ยังอาจทำให้รหัสสำคัญบางอย่างเสียหาย: หากคุณมีหลายเธรดการจัดสรรหน่วยความจำอาจล้มเหลวในส่วนใดส่วนหนึ่ง ดังนั้นขึ้นอยู่กับแอปพลิเคชันของคุณยังมีโอกาส 10-90% ที่จะเสียโดยไม่สามารถย้อนกลับได้
  2. เท่าที่ฉันเข้าใจสแต็คขนาดใหญ่ที่คลี่คลายระหว่างทางจะทำให้การอ้างอิงจำนวนมากเป็นโมฆะและทำให้หน่วยความจำฟรีมากจนคุณไม่ควรสนใจเรื่องนั้น

แก้ไข: ฉันขอแนะนำให้คุณลองใช้ สมมติว่าเขียนโปรแกรมที่เรียกใช้ฟังก์ชันแบบวนซ้ำซึ่งจะจัดสรรหน่วยความจำเพิ่มเติม จับOutOfMemoryErrorดูว่าคุณสามารถดำเนินการต่อจากจุดนั้นได้อย่างมีความหมายหรือไม่ จากประสบการณ์ของฉันคุณจะสามารถทำได้แม้ว่าในกรณีของฉันมันเกิดขึ้นภายใต้เซิร์ฟเวอร์ WebLogic ดังนั้นอาจมีมนต์ดำบางอย่างเกี่ยวข้อง


-1

คุณสามารถจับอะไรก็ได้ภายใต้ Throwable โดยทั่วไปแล้วคุณควรจับเฉพาะคลาสย่อยของ Exception ยกเว้น RuntimeException (แม้ว่านักพัฒนาส่วนใหญ่จะจับ RuntimeException ด้วย ... แต่นั่นไม่เคยเป็นเจตนาของนักออกแบบภาษา)

ถ้าคุณจะจับ OutOfMemoryError คุณจะทำอะไรบนโลกนี้? VM ไม่อยู่ในหน่วยความจำโดยพื้นฐานแล้วสิ่งที่คุณทำได้คือออก คุณอาจไม่สามารถเปิดกล่องโต้ตอบเพื่อบอกพวกเขาได้ว่าคุณมีหน่วยความจำไม่เพียงพอเนื่องจากจะใช้หน่วยความจำ :-)

VM พ่น OutOfMemoryError เมื่อหน่วยความจำไม่เพียงพอ (ข้อผิดพลาดทั้งหมดควรบ่งบอกถึงสถานการณ์ที่ไม่สามารถกู้คืนได้) และไม่มีอะไรที่คุณสามารถทำได้เพื่อจัดการกับมัน

สิ่งที่ต้องทำคือค้นหาสาเหตุที่หน่วยความจำของคุณหมด (ใช้ profiler เช่นเดียวกับ NetBeans) และตรวจสอบให้แน่ใจว่าคุณไม่มีหน่วยความจำรั่วไหล หากคุณไม่มีหน่วยความจำรั่วให้เพิ่มหน่วยความจำที่คุณจัดสรรให้กับ VM


7
ความเข้าใจผิดที่โพสต์ของคุณทำให้เกิดผลกระทบคือ OOM ระบุว่า JVM ไม่อยู่ในหน่วยความจำ แต่จริงๆแล้วมันบ่งชี้ว่า JVM ไม่สามารถจัดสรรหน่วยความจำทั้งหมดที่ได้รับคำสั่ง นั่นคือถ้า JVM มีพื้นที่ 10B และคุณ 'สร้าง' วัตถุ 100B ขึ้นมาใหม่มันจะล้มเหลว แต่คุณสามารถหมุนและ 'สร้างใหม่' เป็นวัตถุ 5B ได้และไม่เป็นไร
Tim Bender

1
และถ้าฉันต้องการแค่ 5B พวกเขาทำไมฉันถึงขอ 10B? หากคุณทำการจัดสรรโดยอาศัยการลองผิดลองถูกแสดงว่าคุณทำผิด
TofuBeer

2
ฉันเดาว่าทิมหมายความว่าคุณยังสามารถทำงานบางอย่างได้แม้จะอยู่ในสถานการณ์ OutOfMemory ก็ตาม อาจมีหน่วยความจำเหลือเพียงพอที่จะเปิดกล่องโต้ตอบเช่น
Stephen Eilert
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.