OutOfMemoryするwarをPayaraに食わせてヒープダンプを吐かせる

-XX:+HeapDumpOnOutOfMemoryErrorのJVMオプションを試してみたかったのです。

war準備

こちら で 提供されているサンプルアプリケーションを使わせてもらいます。

hello.warをダウンロードして任意のフォルダに移動します。

$ wget https://javaee.github.io/glassfish/downloads/quickstart/hello.war
$ mkdir -p /tmp/volume/hello
$ mv hello.war /tmp/volume/hello/
$ chmod -R 777 /tmp/volume/
$ ll /tmp/volume/hello/
total 16
drwxrwxrwx 2 ubuntu ubuntu 4096 Mar 21 13:51 ./
drwxrwxrwx 3 ubuntu ubuntu 4096 Mar 21 13:50 ../
-rwxrwxrwx 1 ubuntu ubuntu 4102 Feb  5  2020 hello.war*

準備したディレクトリをマウントしてPayaraコンテナを起動します。

$ docker run --name payara -d -p 4848:4848 -p 8080:8080  -v /tmp/volume:/tmp/volume payara/server-full:6.2024.3-jdk21

コンテナ内でjarコマンド実行してwarを解凍します。

$ docker exec -it payara bash
payara@8462b1e630e7:~$ cd /tmp/volume/hello/
payara@8462b1e630e7:/tmp/volume/hello$ jar xvf hello.war 
  created: META-INF/
 inflated: META-INF/MANIFEST.MF
  created: WEB-INF/
  created: WEB-INF/classes/
 inflated: WEB-INF/classes/LocalStrings.properties
  created: images/
 inflated: WEB-INF/sun-web.xml
 inflated: WEB-INF/web.xml
 inflated: images/duke.waving.gif
 inflated: index.jsp
 inflated: response.jsp

不要なファイルを削除してindex.jspを以下の内容に書き換えます。

$ rm -rf images hello.war response.jsp 
$ vi index.jsp
$ cat index.jsp 
<%@ page import="java.util.ArrayList" %>
<html>
<head><title>Hello</title></head>
<body>
  <%
    new ArrayList(Integer.MAX_VALUE);
  %>
</body>
</html>

再度コンテナ内に入りwarを作成します。

$ docker exec -it payara bash
payara@8462b1e630e7:~$ cd /tmp/volume/hello/
payara@8462b1e630e7:/tmp/volume/hello$ ls -l
total 12
-rw-r--r-- 1 payara payara  153 Mar 21 14:06 index.jsp
drwxr-xr-x 2 payara payara 4096 Oct 18  2005 META-INF
drwxr-xr-x 3 payara payara 4096 Oct 18  2005 WEB-INF
payara@8462b1e630e7:/tmp/volume/hello$ jar cvf ../hello-oom.war .
added manifest
ignoring entry META-INF/
ignoring entry META-INF/MANIFEST.MF
adding: WEB-INF/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/(in = 0) (out= 0)(stored 0%)
adding: WEB-INF/classes/LocalStrings.properties(in = 120) (out= 102)(deflated 15%)
adding: WEB-INF/sun-web.xml(in = 387) (out= 265)(deflated 31%)
adding: WEB-INF/web.xml(in = 438) (out= 272)(deflated 37%)
adding: index.jsp(in = 153) (out= 126)(deflated 17%)

ひとつ上の階層にwarができていると思います。

$ ll /tmp/volume/
total 16
drwxrwxrwx  3 ubuntu ubuntu 4096 Mar 21 14:09 ./
drwxrwxrwt 14 root   root   4096 Mar 21 14:09 ../
drwxrwxrwx  4 ubuntu ubuntu 4096 Mar 21 14:06 hello/
-rw-r--r--  1 ubuntu ubuntu 1820 Mar 21 14:09 hello-oom.war

これでOOMするwarが作成できました。

Payaraの準備

先ほど起動したコンテナをそのまま使います。
Payaraにオプションを追加して、作成したwarをデプロイします。

$ docker exec -it payara asadmin -u admin create-jvm-options "-XX\:+HeapDumpOnOutOfMemoryError"
Do you trust the above certificate [y|N] -->y
Enter admin password for user "admin"> 
Created 1 option(s)
Command create-jvm-options executed successfully.
$ docker exec -it payara asadmin -u admin create-jvm-options "-XX\:HeapDumpPath\=\$\{com.sun.aas.instanceRoot\}/logs/heapdump.hprof"
Enter admin password for user "admin"> 
Created 1 option(s)
Command create-jvm-options executed successfully.
$ docker exec -it payara asadmin -u admin deploy /tmp/volume/hello-oom.war 
Enter admin password for user "admin"> 
Application deployed with name hello-oom.
Command deploy executed successfully.

JVMオプションを反映させるためにコンテナを再起動します。

$ docker restart payara

テスト

ブラウザでhttp://<コンテナを起動したサーバーのIP>:8080/helloにアクセスすると、
以下の画面になると思います。

payara_heapdump_01.png

オプションで指定したディレクトリにダンプファイルができていると思います。

$ docker exec -it payara bash
payara@4b81a2a50a00:~$ cd appserver/glassfish/domains/domain1/logs/
payara@4b81a2a50a00:~/appserver/glassfish/domains/domain1/logs$ ls -lh
total 310M
-rw------- 1 payara payara 310M Mar 23 08:01 heapdump.hprof
-rw-r--r-- 1 payara payara 9.6K Mar 23 07:58 server.log

コンテナのログを見ると以下のメッセージが出力されていると思います。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit
Dumping heap to /opt/payara/appserver/glassfish/domains/domain1/logs/heapdump.hprof ...
Heap dump file created [324412621 bytes in 1.607 secs]
[#|2024-03-23T08:01:26.500+0000|WARNING|Payara 6.2024.3|jakarta.enterprise.web|_ThreadID=84;_ThreadName=http-thread-pool::http-listener-1(3);_TimeMillis=1711180886500;_LevelValue=900;|
  StandardWrapperValve[jsp]: Servlet.service() for servlet jsp threw exception
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
	at java.base/java.util.ArrayList.<init>(ArrayList.java:156)
	at org.apache.jsp.index_jsp._jspService(index_jsp.java:55)
	at org.glassfish.wasp.runtime.HttpJspBase.service(HttpJspBase.java:68)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
	at org.glassfish.wasp.servlet.JspServletWrapper.service(JspServletWrapper.java:317)
	at org.glassfish.wasp.servlet.JspServlet.serviceJspFile(JspServlet.java:358)
	at org.glassfish.wasp.servlet.JspServlet.service(JspServlet.java:287)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
	at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1554)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:259)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:166)
	at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:757)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:577)
	at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:158)
	at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:372)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:239)
	at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:520)
	at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:217)
	at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:174)
	at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:153)
	at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:196)
	at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:88)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:246)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:178)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:118)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:96)
	at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:51)
	at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:510)
	at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:82)
	at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:83)
	at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:101)
|#]

ダンプファイルはMemory Analyzer で開けました。

payara_heapdump_02.png