1. ホーム

OutOfMemoryError: GC Overhead Limit Exceededエラー解決。

2022-02-22 07:59:44

簡単に説明すると Garbage Collection (GC) JVM 使われなくなったオブジェクトを再利用して、メモリを解放する処理。 GC Overhead Limit Exceeded error java.lang.OutOfMemoryError ファミリーであることを示す JVM のメモリが枯渇しています。次に、このエラーの原因となった java.lang.OutOfMemoryError: GC Overhead Limit Exceeded のエラーとその修正方法について説明します。

GC Overhead Limit Exceeded Errorの紹介

OutOfMemoryError java.lang.VirtualMachineError のサブクラスです。 JVM は、リソースの使用状況に問題がある場合にスローされます。より具体的には、このエラーは JVM を実行するのに時間がかかりすぎる。 GC で、ヒープメモリがわずかしか回収できない場合にスローされます。このため Oracle の公式ドキュメントでは、デフォルトで、もし Java プロセスで消費される 98% 実行時間が長くなる GC よりも少ないだけであり 2% of the time のヒープが回収された場合 JVM はこのエラーを投げます。言い換えれば、これはアプリケーションが利用可能なメモリをほとんど使い果たし、ガベージコレクタがそれを掃除するのに時間がかかりすぎて、複数回失敗したことを意味します。

この場合、ユーザーはアプリケーションからの応答が非常に遅くなり、通常は数ミリ秒で完了する操作が、このケースでは非常に長くかかることになります。 CPU はガベージコレクションされるため、他のタスクを実行することができません。

エラーの再発

次のコードを再現することができます。 java.lang.OutOfMemoryError: GC Overhead Limit Exceeded を以下のコードでエラーにします。

package com.galaxy.concurrency.jvm;

import java.util.HashMap;
import java.util.Map;
import java.util;

public class OutOfMemoryGCLimitExceed {

    public static void addRandomDataToMap() {
        Map<Integer, String> dataMap = new HashMap<>();
        Random r = new Random();
        while (true) {
            dataMap.put(r.nextInt(), String.valueOf(r.nextInt())));
        }
    }

    public static void main(String[] args) {
        addRandomDataToMap();
    }
}


このコードはシンプルで while に行き続けるデッドループ。 HashMap 乱数を追加していく。を実行した後 main メソッドを設定します。 JVM パラメータを -Xmx300m -XX:+UseParallelGC (その JVM ヒープが 300MB GC このアルゴリズムは ParallelGC ) を実行します。 main メソッドを呼び出すと java.lang.OutOfMemoryError: GC Overhead Limit Exceeded エラーになります。

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.util.HashMap.newNode(HashMap.java:1747)
	at java.util.HashMap.putVal(HashMap.java:631)
	at java.util.HashMap.put(HashMap.java:612)
	at com.galaxy.concurrency.jvm.OutOfMemoryGCLimitExceed.addRandomDataToMap(OutOfMemoryGCLimitExceed.java:13)
	at com.galaxy.concurrency.jvm.OutOfMemoryGCLimitExceed.main(OutOfMemoryGCLimitExceed.java:18)


を使っています。 MacOS オペレーティングシステムを指定します。 2-core 8G のハードウェア構成は、以下のとおりです。 JDK バージョンは 1.8 . テスト環境の違いから、もし java.lang.OutOfMemoryError: Java heap space エラーが発生した場合は -Xmx を再現できるように適切に調整してください。 java.lang.OutOfMemoryError: GC Overhead Limit Exceeded というエラーが発生します。異なるガベージコレクションアルゴリズムをより理解するために ( Garbage Collection Algorithms ) を参照するとよいでしょう。 Oracle s Javaガーベッジコレクションの基本 チュートリアルをご紹介します。

ソリューション

理想的な解決策は、メモリリークの可能性があるコードを調査することで、アプリケーションが抱える問題を発見することです。

  • アプリケーション内のどのオブジェクトがヒープスペースをほとんど占有しているか?(その What are the objects in the application that occupy large portions of the heap? )
  • アプリケーションの中でヒープの大部分を占めているオブジェクトは何ですか? ( In which parts of the source code are these objects being allocated? )

また、以下のような自動化されたグラフィックツールも使用できます。 JVisualVM は、その JConsole を含む、コードのパフォーマンス問題を検出するのに役立ちます。 java.lang.OutOfMemoryError .

最後の方法は JVM の起動時設定を変更してヒープサイズを大きくするか、または、ヒープサイズを変更するために JVM スタートアップのコンフィギュレーションでヒープサイズを増加させるか、または -XX:-UseGCOverheadLimit オプションで GC Overhead limit exceeded . 例えば、次のような JVM パラメータは Java アプリケーションが提供する 1GB ヒープ空間。

java -Xmx1024m com.xyz.TheClassName


以下は JVM パラメータは Java アプリケーションは 1GB ヒープスペースが増え、さらに -XX:-UseGCOverheadLimit をオフにするオプションがあります。 GC Overhead limit exceeded : は、その

java -Xmx1024m -XX:-UseGCOverheadLimit com.xyz.TheClassName


しかし -XX:-UseGCOverheadLimit オプションは問題を解決しません。 JVM を投げることになります。 java.lang.OutOfMemoryError: Java heap space というエラーが発生します。もしあなたが OutOfMemoryGCLimitExceed クラスのテスト結果は

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.lang.Integer.toString(Integer.java:403)
	String.valueOf(String.java:3099)
	at com.galaxy.concurrency.jvm.OutOfMemoryGCLimitExceed.addRandomDataToMap(OutOfMemoryGCLimitExceed.java:16)
	at com.galaxy.concurrency.jvm.OutOfMemoryGCLimitExceed.main(OutOfMemoryGCLimitExceed.java:21)



まとめると、実際のアプリケーションのコードにメモリリークがある場合、上記のような方法では問題を解決できないどころか、バグを先送りしてしまうことになります。したがって、アプリケーションのメモリ使用量を完全に見直す方が賢明です。

インラインでのインシデント解決プロセスと概要

例外ログ

最近、LINEでこの問題が発生したのですが、以下がその例です。 java.lang.OutOfMemoryError: GC overhead limit exceeded その時の例外ログです。

2019-04-03 10:48:21,253 [http-nio-8080-exec-121] ERROR c.u.n.s.c.c.GlobalExceptionHandler 44 - Server-side exception!
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1006)
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
  at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
  at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
  at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
  at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
  at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
  at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
  at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
  at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
  at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
  at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
  at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
  at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
  at org.apache.phoenix.schema.types.PDataType.isBytesComparableWith(PDataType.java:92)
  at org.apache.phoenix.schema.types.PDataType.coerceBytes(PDataType.java:832)
  at org.apache.phoenix.schema.types.PDataType.coerceBytes(PDataType.java:822)
  at org.apache.phoenix.compile.UpsertCompiler$4.execute(UpsertCompiler.java:1011)
  at org.apache.phoenix.jdbc.PhoenixStatement$2.call(PhoenixStatement.java:355)
  at org.apache.phoenix.jdbc.PhoenixStatement$2.call(PhoenixStatement.java:338)
  at org.apache.phoenix.call.CallRunner.run(CallRunner.java:53)
  at org.apache.phoenix.jdbc.PhoenixStatement.executeMutation(PhoenixStatement.java:336)
  at org.apache.phoenix.jdbc.PhoenixPreparedStatement.executeUpdate(PhoenixPreparedStatement.java:199)
  at com.ucar.nosql.spacex.yarn.plugins.metric.timeline.PhoenixHBaseAccessor.commitMetrics(PhoenixHBaseAccessor.java:188)
  at com.ucar.nosql.spacex.yarn.plugins.metric.timeline.PhoenixHBaseAccessor.commitMetricsFromCache(PhoenixHBaseAccessor.java:138)
  at com.ucar.nosql.spacex.yarn.plugins.metric.timeline.PhoenixHBaseAccessor.insertMetricRecordsWithMetadata(PhoenixHBaseAccessor. java:348)
  at com.ucar.nosql.spacex.yarn.plugins.metric.timeline.HBaseTimelineMetricsService.putMetrics(HBaseTimelineMetricsService.java:299)
  at com.ucar.nosql.spacex.yarn.modules.monitor.controller.SinkConsumerController.postMetrics(SinkConsumerController.java:71)
  at sun.reflect.GeneratedMethodAccessor88.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
  at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
  at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle( ServletInvocableHandlerMethod.java:102)
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod( RequestMappingHandlerAdapter.java:877)
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod( org.springframework.web.servlet.mvc.method.annotation.Re

トラブルシューティング

Linux の下で発生します。 OOM は、必ずしも Java サービスがメモリを大量に消費するためだけでなく、他のプログラムが大量のメモリを要求しているため、すべてのアプリケーションが物理メモリよりも多くのメモリを必要とする場合、そして Java サービスがメモリ不足であることと Linux オペレーティングシステムによって発見されます。 kill である Linux 物理メモリの過負荷によるクラッシュを回避するためのメモリ保護機構で、この機構は OOM Killer 理由は、この記事の最後にある参考文献に記載されています。 OOM Killer on Linux のセクションを参照してください。以前、仕事で遭遇したことがある ElasticSearch データストレージサービスや Fluentd ログ収集サービスが同じサーバーに展開されている場合は Fluentd によるメモリリーク ElasticSearch サービスは kill の場合、Laterは review アップ Fluentd というメモリリーク問題を解決するコードです。 ElasticSearch サービスを別途デプロイして解決しました。このサービスを別にデプロイしていることを確認した後、次に目を向けるのは JVM のメモリ構成です。このアプリケーションはアクセスが多くないので、オンラインサーバーのメモリは 4G でスタートし JDK に付属するコマンドツールは JVM コンフィギュレーションです。

# Find the process number of spacex.jar
sudo jps
#Find the jvm parameter, pid is the process number of spacex.jar
sudo jinfo -flags pid

JVM

Attaching to process ID 16022, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.121-b13 Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=62914560 -XX:+ManagementServer -XX:MaxHeapSize=1006632960 -XX:MaxNewSize= 335544320 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=20971520 -XX:OldSize=41943040 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC Command line: -Dcom.sun.management.jmxremote -Dcom.sun.management.snmp.port=8044 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun. management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=8045 -Djava.rmi.server.hostname=10.212.27.54 構成は以下の通りです。

-XX:MaxHeapSize=1006632960KB

ここで 960MB である。 JAVA_OPTS ブートスクリプトを確認したところ java が追加されていませんでした。 JAVA_OPTS は、start コマンドの内部で -Xms2048m -Xmx2048m ヒープメモリを java という変数がありますが、その変数は nohup java startコマンドを参照しているため、アプリケーションの起動後はデフォルトのメモリ構成が使用されます。 JAVA_OPTS="-server -Xms2048m -Xmx2048m -Xmn512m -Xss256k -XX:PermSize=256m -XX:MaxPermSize=256m -XX:SurvivorRatio=8 -XX:+ UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelRemarkEnabled -XX: CMSInitiatingOccupancyFraction=70 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${SPACEXLOG}/" cd /usr/local/frame/spacex nohup java -Dcom.sun.management.jmxremote -Dcom.sun.management.snmp.port=8044 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun. management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=8045 -Djava.rmi.server.hostname=$LOCAL_IP -jar ${SPACEXJAR } > ${SPACEXLOG}/stdout.log 2>&1 & 1行の違いです。

(1)修正前。

JAVA_OPTS="-server -Xms2048m -Xmx2048m -Xmn512m -Xss256k -XX:PermSize=256m -XX:MaxPermSize=256m -XX:SurvivorRatio=8 -XX:+ UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelRemarkEnabled -XX: CMSInitiatingOccupancyFraction=70 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${SPACEXLOG}/"
cd /usr/local/frame/spacex
nohup java ${JAVA_OPTS} -Dcom.sun.management.jmxremote -Dcom.sun.management.snmp.port=8044 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.ssl=false Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=8045 -Djava.rmi.server.hostname=$LOCAL_IP -jar ${ SPACEXJAR} > ${SPACEXLOG}/stdout.log 2>&1 &


(2) 改造後。

jps

イベント後の対策とまとめ

1. 修正スクリプトが完了し、本番環境でサービスを開始した後に jinfo JVM コマンドで表示します。 1-3 パラメータは有効です

2. サービスを開始するスクリプトは、コマンドのすべての行と関連する変数が有効であることを確認するために、完全かつ厳密にテストする必要があります。

参考文献

OOM

1. インテキスト Oracle セクションは OutOfMemoryError: GCオーバーヘッドの制限を超えました 記事

2. OOM の公式サマリーです。 OOM 例外の発生と処理 OutOfMemoryError例外を理解する

LinuxでのOOMキラー

1. LinuxにおけるOOM Killerの理解と設定

2. LinuxMM: OOM_Killer