Serie virtueller Maschinen: Speicherüberlauf OOM und Lösungen

Schaffen Sie weiter, beschleunigen Sie das Wachstum! Dies ist der erste Tag meiner Teilnahme an der „Nuggets Daily New Plan · June Update Challenge“, klicken Sie hier, um die Details der Veranstaltung anzuzeigen

OutOfMemoryError (kurz OOM) bereitet Programmierern Kopfzerbrechen, denn diese Art von Problem tritt normalerweise auf, wenn der Speicherplatz zur Neige geht und das Programm nicht genügend Speicherplatz zur Verfügung hat. In Java-Programmen gibt es viele Gründe für einen Speicherüberlauf.Häufige sind Heap-Speicherüberlauf, direkter Speicherüberlauf und permanenterBereichs-/Metaspace-Überlauf.

Haufen überlaufen

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
复制代码

Heap-Überlauf ist der häufigste. Der Heap ist ein wichtiger Platz in Java. Eine große Anzahl von Objekten in Java werden direkt auf dem Heap allokiert (siehe Speicherallokation ). Wenn eine große Anzahl von Objekten Heap-Speicherplatz belegt und stark referenziert wird, sodass sie niemals wiederverwendet werden können -Xmx, tritt ein Überlauf auf, wenn die Summe der Größe aller Objekte größer ist als der durch den Parameter angegebene Wert.

Es kann viele Gründe für einen Überlauf geben, hier sind die häufigsten Gründe:

  • Das Dienstprojekt enthält zu viele Codeklassen, und der Heapspeicher reicht nicht aus, und beim Start kann ein Heapüberlauffehler auftreten.
  • Es gibt Schleifen oder Endlosschleifen im Code, was zu zu vielen Entitätsobjekten führt.
  • Beim Abfragen der Datenbank wird eine große Datenmenge gleichzeitig abgefragt.
  • Der Parameter -Xmx für Dienststart ist zu klein eingestellt, das ist der erste Punkt.

Beispielsweise fügen wir weiterhin Objekte zu ArrayList hinzu und können sie nicht recyceln, was zu einem Heap-Überlauf führt

/**
* 启动参数限制最大堆和最小堆:-Xms10m -Xmx10m
*/
public class Test2 {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();
        for (int i=0;i<10;i++){
            list.add(new byte[1024*1024]);
        }
    }
}
复制代码

Fehler beim Start:

com.ajisun.coding.ajisunmybatis.Test2
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at com.ajisun.coding.ajisunmybatis.Test2.main(Test2.java:23)
复制代码

Das in dem Fehler Java heap spaceweist auf einen Überlauf des Heap-Speichers hin.

Wie man damit umgeht

  • Es wird bevorzugt überprüft, ob der Code Schleifen oder Endlosschleifen hat und ob er kontinuierlich Objekte erstellen kann.
  • Überprüfen Sie, ob die Startparameter -Xmxund -Xmsder eingestellte Heap-Speicher zu klein sind, um alle Klassen im Dienst zu laden, und Sie können sie entsprechend erhöhen.
  • Überprüfen Sie, ob der Code eine Datenbankabfrage enthält, keine Paginierung, um viele Daten auf einmal zurückzugeben.
  • Sie können auch mithilfe von MAT- oder VisualVM-Tools analysieren , um Objekte zu finden, die viel Heap-Speicherplatz beanspruchen, und dann angemessene Optimierungen vornehmen.

direkter Speicherüberlauf

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
复制代码

Dieses Problem tritt im Allgemeinen relativ selten auf, und der Direktspeicher ist nicht Teil des Laufzeitdatenbereichs.

Java中NIO(New IO)是支持直接使用直接内存的,可以直接获取一块堆外空间使用,而这块空间是直接向操作系统申请的。直接内存的申请速度一般比堆内存慢,但是其访问速度要快于堆内存,所以如果存在可复用且经常被访问的空间,使用直接内存可以提高系统的性能。但是直接内存没有被Java完全托管,使用不当容易出现溢出的问题。

Java中的DirectByteBuffer就是直接内存申请的,使用unsafe的native方法allocateMemory

配置启动参数-XX:MaxDirectMemorySize设置直接内存的最大值,如果不配置此参数则默认最大可用直接内存是-Xmx的值

/**
* 启动参数:-XX:MaxDirectMemorySize=5m
*/
public class Test2 {
    public static void main(String[] args) {
    ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*6);
    }
}
复制代码

运行出现错误Direct buffer memory

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
  at java.nio.Bits.reserveMemory(Bits.java:694)
  at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
  at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
复制代码

如何处理:

  • 检查程序中使用直接内存的代码是否恰当。
  • 检查参数-Xmx和-XX:MaxDirectMemorySize 的大小是否合理,可以根据实际情况调整其大小。

线程过多导致OOM

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
复制代码

这个错误是程序中创建的线程过多,而线程需要的空间却不够了。

我们运行程序创建的线程都是需要一定内存的,而机器的内存大小是一定的,当线程的数量过多的时候,就会导致OOM。

如下用死循环不断的创建新的线程。

public class Test2 {
    public static void main(String[] args) {
        while(true){
            new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(10000000);
                    } catch(InterruptedException e) { }
                }
            }).start();
        }
    }
}
复制代码

运行出现异常,说明系统创建的线程数量已经到达最大值。

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
  at java.lang.Thread.start0(Native Method)
  at java.lang.Thread.start(Thread.java:717)
  at com.ajisun.coding.ajisunmybatis.Test2.main(Test2.java:39)
复制代码

如何处理:

  • 首先检查代码,是否有优化的空间,减少不必要的线程创建,或者使用线程池复用线程。
  • 减少堆空间的内存,这样操作系统可以预留更多的内存用于线程创建。
  • 减少每个线程内存空间的占用,使用-Xss可以指定栈空间的大小(注意:太小会出现栈溢出)。
  • 如果真的需要的线程特别多,但是受到操作系统的限,这种需要修改操作系统的最大线程数。

永久代/元空间溢出

Java.lang.outofMemoryError:PermGen Space( 1.8之前)

或者

java.lang.OutOfMemoryError: Metaspace( 1.8之后)

这种错误是永久代或者元空间溢出,在jdk1.8之前会出现这种错误,之后hotspot用元空间代替了永久代来存储class信息。如果一个系统在不断的创建新的类(不是对象实例),那么最终会导致元空间溢出的。

如何处理:

  • 增加元空间的大小,设置其对应参数的值 -XX:MaxMetaspaceSize=512m
  • 减少系统需要的类的数量,检查是否有不需要的类并且清除掉。
  • 使用ClassLoader合理的装载各个类,并定期进行回收。

GC效率低下引起的OOM

java.lang.OutOfMemoryError:GC over head limit exceeded
复制代码

这个错误比较少见。

Java中主要特点就是垃圾回收,而GC是内存回收的关键,如果效率低下,会严重影响系统的整体性能。如果系统的堆空间太小,那么GC所占的时间就会比较多,并且回收所释放的内存也比较少。根据GC占用系统时间以及内存释放的大小,可以评估出GC的效率,一旦虚拟机认为GC的效率过低,就可能直接抛出OOM异常。

一般情况下,虚拟机会检查以下几种情况来判断效率是否低下:

  • 花在GC上的时间是否超过了98%。
  • 老年代释放的内存是否小于2%。
  • eden 区释放的内存是否小于2%。
  • 是否连续最近5次GC都出现了上述情况(是同时出现,不是出现一个)。

只有满足上面的所有条件才会出现上面的OOM

如何处理:

  • 可以通过开关-XX:-UseGCOverheadLimit禁止OOM的产生。这样就会出现堆溢出的错误,然后根据堆溢出的问题来处理。
  • 优化代码,检查新生代,老年代中对象是否正常,是否有过多的对象无法释放。
  • dump内存,检查是否存在内存泄露,如果没有,加大内存。

本文讲解了几种内存溢出的情况,以及相对应的解决思路。其中常见的错误是堆溢出和线程过多的OOM。

原文地址


我是纪先生,用输出倒逼输入而持续学习,持续分享技术系列文章,以及全网值得收藏好文,欢迎关注公众号,做一个持续成长的技术人。

个人网站

JVM虚拟机系列历史文章

1. 虚拟机系列:jvm运行时堆内存如何分代;

2. 虚拟机系列:jvm中的垃圾回收算法;

3. 虚拟机系列:jvm运行时数据区域;

4. 虚拟机系列:JVM中对象的创建,内存布局和访问定位;

5. 虚拟机系列:JVM中的垃圾收集器;

6. Reihe virtueller Maschinen: Speicherzuordnung in JVM;

7. Serie virtueller Maschinen: Verstehen der Protokolle und Protokollparameter virtueller Maschinen;

8. Reihe virtueller Maschinen: grundlegende Tools zur Überwachung der Leistung virtueller Maschinen;

9. Serie virtueller Maschinen: grundlegendes Tool zur Überwachung der Leistung virtueller Maschinen – jstat;

10. Virtuelle Maschinenserie: Leistungsüberwachungs-Visualisierungstool – JConsole;

11. Serie virtueller Maschinen: grafisches Überwachungstool – VisualVM;

おすすめ

転載: juejin.im/post/7102976491608080392