LoginSignup
2
4

More than 5 years have passed since last update.

Javaアプリのヒープメモリを定期的にチェックする

Last updated at Posted at 2016-04-06

リソースを有効に使うために

有限のリソースを有効に使うために、Javaアプリに割り当てるヒープメモリはどの程度がいいのだろう?と思い、まずは実際に使用している状況を簡単に確認してみようと思います。

JMXを有効にする

Javaアプリのこういったリソース情報を得るにはJMXを使うのがいいようなので、まずはJMXを有効にします。JMXを有効にするためにはjava実行時のオプションに次の引数を渡せばいいようです。

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=12345
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

なお、今回はセキュリティは考慮してません。本番で使う場合は検討する必要があります。

JMX APIにアクセスする

早速JMX APIにアクセスしてみましょう。
GUIであれば、Java SDKについている jconsole を使うのが手っ取り早いようです。
が、定期的にヒープの情報をファイルに落とし、あとから傾向を掴みたいときはこの方法は不便です。
なので、コマンドラインで実行できるツールを探していたところ、 cmdline-jmxclient なるものがあることがわかりました。実行方法・結果は以下のとおりです。

java -jar cmdline-jmxclient.jar - ターゲットサーバー:ポート番号 Bean名 コマンド名

04/06/2016 22:05:00 +0900 org.archive.jmx.Client HeapMemoryUsage:
committed: 270008320
init: 268435456
max: 477626368
used: 65294544

結果が複数行にわたって出てきてしまうため、これは個人的にちょっと使いにくいです。1行で1日1ファイルに書き出してもらう形にするため、PowerShellを使って整形することにしました。下記は上記コマンドを使って、複数台のサーバーにJMX APIでアクセスし、ヒープ情報を取得・ファイルに保存するスクリプトです。なお、このコマンドの実行結果はなぜか標準エラー出力に出力されるため、スクリプトでも標準エラー出力を見るようになっています。

Get-Heap.ps1
$date = (Get-Date -Format d) -replace "/", ""
$jmxclient = "DRIVE:\PATH\TO\jmx\lib\cmdline-jmxclient-0.10.3.jar"
$java = "C:\Program Files\Java\jdk1.8.0_xx\bin\java.exe"

@("SERVER1", "SERVER2", "SERVER3") | ForEach-Object {
    $logfilepath = "DRIVE:\PATH\TO\jmx\logs\YOUR_LOG_NAME.$_.$date.tsv"
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $java
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = "-jar " + $jmxclient + " - " + "$_" + ":12345 java.lang:type=Memory HeapMemoryUsage"
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    $stdout = $p.StandardOutput.ReadToEnd()
    $stderr = $p.StandardError.ReadToEnd()

    $arr = [Collections.ArrayList]($stderr -replace "(committed: |init: |max: |used: )", "" -split "`n")
    $arr.RemoveAt(0)
    $time = (Get-Date -Format s)
    $result = $arr.ToArray() -join "`t"
    if (-not (Test-Path $logfilepath)) {
        "timestamp`tcommitted`tinit`tmax`tused" | Out-File $logfilepath -Append -Encoding default
    }
    "$time`t$result" | Out-File $logfilepath -Append -Encoding default
}

Rubyスクリプトも置いておきます。

get_heap.rb
require "open3"

DATE = Time.now.strftime("%Y%m%d")
LOGDIR = "DRIVE:/PATH/TO/jmx/logs/"

["SERVER1", "SERVER2", "SERVER3"].each do |server|
  LOGFILE = "#{LOGDIR}YOUR_LOG_NAME.#{server}.#{DATE}.tsv"
  JMXCLIENT = "DRIVE:/PATH/TO/jmx/lib/cmdline-jmxclient-0.10.3.jar"

  o, e, s = Open3.capture3("java -jar #{JMXCLIENT} - #{server}:7989 java.lang:type=Memory HeapMemoryUsage")
  timestamp = Time.now.strftime("%Y/%m/%d %H:%M:%S")
  result = e.gsub(/(committed: |init: |max: |used: )/,"").split("\n")
  result.shift
  file_exists = File.exists?(LOGFILE)
  File.open(LOGFILE, "a") do |f|
    f.puts "timestamp\tcommitted\tinit\tmax\tused" unless file_exists
    f.puts "#{timestamp}\t#{result.join("\t")}"
  end
end

定期的に実行する

上記をタスクスケジューラーで定期的に実行するため、バッチファイルにします。

Get-Heap.bat
powershell Get-Heap.ps1

タスクスケジューラーに登録します。
普通にGUIから登録してもいいのですが、パスワードを聞かれたりシて面倒なので、PowerShellで登録も行います。

Set-TaskScheduler.ps1
$STPrin = New-ScheduledTaskPrincipal -UserId "LOCALSERVICE" -LogonType ServiceAccount
$Action = New-ScheduledTaskAction -Execute "YOUR ACTION HERE"
$Trigger = New-ScheduledTaskTrigger -Daily -At (Get-Date)
$Task = Register-ScheduledTask -TaskName "YOUR TASK NAME" -Trigger $Trigger -Action $Action -Principal $STPrin
$Task.Triggers.Repetition.Duration = "P1D"
$Task.Triggers.Repetition.Interval = "PT1M"
$Task | Set-ScheduledTask

これで毎日1分間隔でヒープ情報を取ることができるようになりました。
リソース等管理系に関する情報が簡単に取れる仕組みが標準であるというのはうれしいですね。

(補足)
毎日実行するとファイルが日付分増えていきます。
Linuxであれば簡単に改廃できますが、Windowsの場合はちょっとした工夫が必要です。私はよく下記バッチを使って改廃を行なっています。

@echo off
REM --- Set Parameters
SET LOG_PATH=DRIVE:_PATH_TO_LOGFOLDER_
REM --- Days to keep logs
SET LOG_GEN=30

REM --- DELETE AN OLD LOGFILE
for /f "skip=%LOG_GEN%" %%A in ('dir /b /o-n "%LOG_PATH%"') do del /q %LOG_PATH%%%A

参考

今回もまたいろんなサイトを参考にさせていただきました。この場を借りてお礼申し上げます。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4