ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

2017-05-03

Java: CMS GC is endless

メモリが空いているのに CMS GC が掛かり続けることがある。といっても Full GC ではないので、CPU と GC ログを食うだけで運用上の問題はないのだが、はっきりした理由が分からないと安全だと言い切ることもできない。私もこれまでに何度か調べてきたが、ずっと原因が分からずにいた。

今は少しググれば有用な情報がたくさん得られる。いい時代になったもんだ。これらの情報が 2010 年以前にあれば、私もどれだけ楽ができたか知れない。しかし、これらの情報は今回の私の問題を解決しない。例えば nekop 氏によると、

CMSが開始されるトリガーは二つある。ひとつはOld領域の利用率がCMSInitiatingOccupancyFractionに到達した場合。もうひとつは今CMS走らせないと先にヒープ埋まっちゃうよね、という統計判断を元にしたトリガー。

CMSInitiatingOccupancyFraction の初期値は 92%。しかし繰り返すが、メモリは十分に空いている。ヒープを 50% しか使用していないのに、CMS GC が掛かり続けることの説明にはならない。じゃあもう 1 つの「統計判断」とは何なのか。

後者の統計については細かい話になるので省略する。知りたい人はソース嫁。

orz。うん、まあ、ね。私もかれこれ 5 年以上この現象を見てきているので、そろそろ億劫がらずにコードを読んでみる時期なのかも知れないな。

現場で動いているのは Java 6 なのだが、ソースコードを見つけられなかったので Java 7 を見てみる。しかしコードを見たからって、そんな簡単に解決するんだったら世話ないっての。

・・・1 時間後。

解決しちゃった。 :-D

bool CMSCollector::shouldConcurrentCollect() {

  // ...snip...

  // Otherwise, we start a collection cycle if either the perm gen or
  // old gen want a collection cycle started. Each may use
  // an appropriate criterion for making this decision.
  // XXX We need to make sure that the gen expansion
  // criterion dovetails well with this. XXX NEED TO FIX THIS
  if (_cmsGen->should_concurrent_collect()) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print_cr("CMS old gen initiated");
    }
    return true;
  }

  // ...snip...

  if (CMSClassUnloadingEnabled && _permGen->should_concurrent_collect()) {
    bool res = update_should_unload_classes();
    if (res) {
      if (Verbose && PrintGCDetails) {
        gclog_or_tty->print_cr("CMS perm gen initiated");
      }
      return true;
    }
  }
  return false;
}

重要なのは後半の部分。つまり、通常のヒープ領域だけでなく、Permanent 領域も CMS のトリガーになる ということ。この閾値は CMSInitiatingPermOccupancyFraction で、やはり初期値は 92%。

これで全てに納得がいった。これはつまり、Permanent 領域の使用量を把握して MaxPermSize を最適化しているほど、この現象に遭いやすくなるということだ。そういう場合、この値は限りなく 100% に近づけるのが正しい。(でなければ MaxPermSize を増やす)

-XX:CMSInitiatingPermOccupancyFraction=99

CMS でも、どうせ偶に concurrent mode failure や promotion failed 由来の Full GC は起こる(このとき Permanent 領域も GC される)し、Permanent 領域の変動がよほど大きいアプリケーションでもなければ、これで良いと思う。

しかしこの Permanent 領域の CMS トリガーは、もっと有名になって良いと思う。少なくとも、CMSInitiatingOccupancyFraction を出す際は、合わせて CMSInitiatingPermOccupancyFraction についても触れるべき。

2014-02-24

Java: Import a certificate with `keytool'

ここ最近、Java のセキュリティ名目での やんちゃ ぶりには、振り回されている人も多いのではないだろうか。私も、Java 7 Update 40 以降でアプレット署名に自己証明書が使えなくなり、その対応に煩わされた。

クライアント PC に証明書をインポートする作業は、keytool で自動化できる。自己証明書の時はそれで問題なかったのだが、正規の証明書に変えた途端、上手くいかなくなった。具体的には、証明書をインポートしてもアプレット実行時に出てくるダイアログを抑止できない。

証明書の作り方に問題があるのかも知れないが、生憎とそこは別の人の仕事。まずは渡された証明書で何とかする方法を探してみる。(恐らくは証明書の Common Name が怪しいと睨んでいるが、未検証)

私を混乱させたのは下記の挙動。

  1. Java コントロールパネルから証明書をインポートしても、アプレット実行時にダイアログが出てきてしまう。
  2. ダイアログで「次回から表示しない」を選択して実行すると、ダイアログは二度と出てこない。
  3. (1)と(2)についてそれぞれ証明書をエクスポートすると、両者は完全に一致する。

つまりエクスポートした証明書は全く同じなのに、ダイアログが出る or 出ない、という違いが出る。インポート方法を色々と試してみたが、結局(2)以外でダイアログを抑止することはできなかった。最後に、ダメ元で気になっていたことを試してみた。実は(1)と(2)では、1 つだけ違いがある。それがキーストア中の「別名」(alias)表示だ。これも keytool で見ることができる。

Java コントロールパネルから証明書をインポートした場合:

別名: deploymentusercertnullnullnulljava.util.random@1a36121

ダイアログで「次回から表示しない」を選択した場合:

別名: deploymentusercert$tsflag$loc=http//example.com:80##docbase:http//example.com:80java.util.random@10acb9b

後者に、怪しげな呪文が見て取れる。ダメ元で後者の「別名」でインポートしてみると・・・、

やったよ、ビンゴ! :-D

もうね、何でこんな仕様なのかと。確かに前述のダイアログには「上記の発行者と場所」とあったが、まさか「場所」をこんな方法で覚えるとか難度が高過ぎだろう。

色々と試した結果、「別名」は次の形式が必要なようだ。(Java 7 Update 51 にて確認)

  • $tsflag$loc=http//example.com:80##docbase:http//example.com:80
  • 「http//example.com:80」は適宜変更。ポートは省略不可。SSL なら「https//example.com:443」とする。
  • パースに問題なさそうな文字列であれば、先頭・末尾に追加可能。途中に入れることも可能だが、やらない方が無難だと思う。

以上を踏まえて、証明書インポート BAT の例。

importcert.bat:

@echo off
setlocal

set SITE_URL=http//example.com:80
set CERT_FILE=%~dp0mycert.cer
set KS_EMPTY_FILE=%~dp0trusted.certs.empty
set KS_ALIAS=deploymentusercert$tsflag$loc=%SITE_URL%##docbase:%SITE_URL%

set JAVA_VERSION=7
set JAVA_HOME=%ProgramFiles(x86)%\Java\jre%JAVA_VERSION%
if not exist "%JAVA_HOME%" (
  set JAVA_HOME=%ProgramFiles%\Java\jre%JAVA_VERSION%
)
set keytool=%JAVA_HOME%\bin\keytool.exe

set LocalLow=%USERPROFILE%\AppData\LocalLow
if not exist "%LocalLow%" (
  set LocalLow=%AppData%
)
set ks_dir=%LocalLow%\Sun\Java\Deployment\security
set ks_file=%ks_dir%\trusted.certs

set result=1

if not exist "%keytool%" (
  echo ERROR: not found: "%keytool%"
  goto exit
)
if not exist "%CERT_FILE%" (
  echo ERROR: not found: "%CERT_FILE%"
  goto exit
)
if not exist "%ks_file%" (
  mkdir "%ks_dir%" 2>NUL
  copy /v "%KS_EMPTY_FILE%" "%ks_file%"
  if not exist "%ks_file%" (
    echo ERROR: not found: "%ks_file%"
    goto exit
  )
)

"%keytool%" -list ^
            -alias "%KS_ALIAS%" ^
            -keystore "%ks_file%" ^
            -storepass "" >NUL
if not errorlevel 1 (
  echo already imported.
  goto success
)

"%keytool%" -importcert -v ^
            -alias "%KS_ALIAS%" ^
            -file "%CERT_FILE%" ^
            -keystore "%ks_file%" ^
            -storepass "" ^
            -noprompt
set result=%ERRORLEVEL%
if %result% neq 0 (
  echo ERROR: keytool.exe: code=%result%
  goto exit
)

:success
set result=0

:exit
if %result% equ 0 (
  echo OK
) else (
  echo Failed
)
pause
exit /b %result%

実行には上記 BAT に加え、下記ファイルが必要。

  • mycert.cer (インポートする証明書)
  • trusted.certs.empty (空のキーストアファイル)

詳細は BAT を解読して貰うとして、ファイル「trusted.certs.empty」については説明が必要だと思う。

keytool は、キーストアファイルが存在しなければ新たにファイルを作成する。しかしこの時、何故かパスワードを強要してくる(-storepass "" は効かない)ため、パスワードなしキーストアを作ることができない。一方 Java コントロールパネルから証明書をインポートすると、パスワードなしキーストアが作成される。つまり、Java コントロールパネルはパスワードなしキーストアを使うくせに、keytool からはそれを作ることができないのだ。もうほんと、この仕様を作った奴はタヒねと言いたい。

trusted.certs.empty は、予め作成した空のパスワードなしキーストアだ。Java コントロールパネルで適当な証明書をインポート → 削除すれば作成できる(ファイル場所は %ks_file% を参照)。私が確認した限り、Windows XP と Windows 8 とでファイルは完全に一致したので、今後の互換性も問題ないだろう。

下記は、インポート済み証明書を一覧表示する BAT の例。「別名」もこれで確認できる。

listcerts.bat:

@echo off
setlocal

set JAVA_VERSION=7
set JAVA_HOME=%ProgramFiles(x86)%\Java\jre%JAVA_VERSION%
if not exist "%JAVA_HOME%" (
  set JAVA_HOME=%ProgramFiles%\Java\jre%JAVA_VERSION%
)
set keytool=%JAVA_HOME%\bin\keytool.exe

set LocalLow=%USERPROFILE%\AppData\LocalLow
if not exist "%LocalLow%" (
  set LocalLow=%AppData%
)
set ks_dir=%LocalLow%\Sun\Java\Deployment\security
set ks_file=%ks_dir%\trusted.certs

set result=1

if not exist "%keytool%" (
  echo ERROR: not found: "%keytool%"
  goto exit
)
if not exist "%ks_file%" (
  echo ERROR: not found: "%ks_file%"
  goto exit
)

"%keytool%" -list -v ^
            -keystore "%ks_file%" ^
            -storepass ""
set result=%ERRORLEVEL%
if %result% neq 0 (
  echo ERROR: keytool.exe: code=%result%
)

:exit
pause
exit /b %result%

今回はこれで解決だが、問題は、今後の互換性は保証されない ということ。

事実、Java 7 Update 45 → 51 で「別名」の形式が変わった。具体的には、u51 で「##docbase:...」が増えた。このせいで、u45 で証明書をインポートしていても、u51 にアップデートすると再びダイアログが出てきてしまう。ここまで来ると、分かってて嫌がらせをしているとしか思えない。(u51 アップデート時に出てくるダイアログで「セキュリティ・プロンプトの復元」に初期でチェックが入っているのも、これを隠すための陰謀だと思っている)

そろそろ、世界中の IT エンジニアは Java の横暴に NO! って言っても良い頃だと思う。

参考:


2014-04-28 追記

Java 7 Update 55 にて、更にマジックワード「##from」が増えた模様。

  • $tsflag$loc=http//example.com:80##docbase:http//example.com:80##from:http//example.com:80

2013-12-30

Struts2: ScopeInterceptor: Deadlock in session lock

もう数年前の話になる。Web アプリケーションの負荷テストをすると稀にエラーになることがあった。リクエストから 30 秒後にエラーになり、ログには「Deadlock in session lock」と出る。この文字列から ScopeInterceptor に辿り着き、コードを読んで不具合と判断、特に使っていなかった ScopeInterceptor を外すことで対処した。

当時は同様の事例が見つからなかったが、最近になって検索すると次が出てきた。

コードを読んだだけで動作は未確認だが、最新版でも直っていないようだ。

確か ScopeInterceptor は初期設定で組み込まれているはずで、負荷テストをやれば普通に当たりそうに思うのだが、何故これまで問題になっていないのか謎。

2012-04-05

RHEL6: JDK: /lib/ld-linux.so.2: bad ELF interpreter

RHEL6 に JDK をインストールしようとすると、エラーが発生。

[rhel6]# ./jdk-6u31-linux-i586.bin
Unpacking...
Checksumming...
Extracting...
./jdk-6u31-linux-i586.bin: ./install.sfx.10908: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
Failed to extract the files.  Please refer to the Troubleshooting section of the Installation Instructions on the download page for more information.

Google 先生によると、ld-linux.so.2 は libstd++ パッケージに入っているらしい。

[rhel6]# rpm -q libstdc++
libstdc++-4.4.6-3.el6.x86_64

既に入ってますが。RHEL5 ではどうだったかというと、

[rhel5]# rpm -q libstdc++
libstdc++-4.1.2-46.el5
libstdc++-4.1.2-46.el5

ああ、分かった。64bit OS に 32bit JDK を入れようとしたからだ。つまり、

  • 64bit RHEL に 32bit JDK を入れる場合、32bit libstdc++ パッケージが必要。
  • 64bit RHEL5 には 32bit libstdc++ が入っているが、64bit RHEL6 には入っていない。

ならば RHEL6 DVD から入れれば解決・・・、

[rhel6]# rpm -ivh libstdc++-4.4.6-3.el6.i686.rpm
warning: libstdc++-4.4.6-3.el6.i686.rpm: Header V3 RSA/SHA256 Signature, key ID fd431d51: NOKEY
error: Failed dependencies:
        ld-linux.so.2 is needed by libstdc++-4.4.6-3.el6.i686
        ld-linux.so.2(GLIBC_2.3) is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6 is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6(GLIBC_2.0) is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6(GLIBC_2.1) is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6(GLIBC_2.1.3) is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6(GLIBC_2.2) is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6(GLIBC_2.3) is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6(GLIBC_2.3.2) is needed by libstdc++-4.4.6-3.el6.i686
        libc.so.6(GLIBC_2.4) is needed by libstdc++-4.4.6-3.el6.i686
        libgcc_s.so.1 is needed by libstdc++-4.4.6-3.el6.i686
        libgcc_s.so.1(GCC_3.0) is needed by libstdc++-4.4.6-3.el6.i686
        libgcc_s.so.1(GCC_3.3) is needed by libstdc++-4.4.6-3.el6.i686
        libgcc_s.so.1(GCC_4.2.0) is needed by libstdc++-4.4.6-3.el6.i686
        libgcc_s.so.1(GLIBC_2.0) is needed by libstdc++-4.4.6-3.el6.i686
        libm.so.6 is needed by libstdc++-4.4.6-3.el6.i686
        libm.so.6(GLIBC_2.0) is needed by libstdc++-4.4.6-3.el6.i686

依存関係が うぜえ。速攻で諦めて、素直に 64bit JDK を入れることにした。

JDK はサイズが大きいから自作インストールメディアに 32bit 版と 64 bit 版の両方を入れるのが嫌だったが、諦めた。現場 SE が自力で libstdc++ を入れられると期待するほど、私はお人好しじゃない。


2017-05-07 追記

今まで放ったらかしにしておいて申し訳ないが、このエントリは嘘です

Google 先生によると、ld-linux.so.2 は libstd++ パッケージに入っているらしい。

当時のことを思い出せるはずもないが、実際に JDK が動作するシステムで下記のように調べれば、

# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 6.8 (Santiago)

# rpm -q --whatprovides /lib64/ld-linux-x86-64.so.2
glibc-2.12-1.192.el6.x86_64

ld-linux.so.2 は glibc パッケージに入っていることが分かる。glibc であれば、依存関係は(私の環境では) nss-softokn-freebl だけになる。それに OS メディアがあるなら、複雑な依存関係であったとしても、yum に解決してもらえる。

# yum -c /media/media.repo --setopt InstallMedia.baseurl=file:///media install glibc.i686
Setting up Install Process
InstallMedia                                             | 4.1 kB     00:00 ...
InstallMedia/primary_db                                  | 3.1 MB     00:03 ...
Resolving Dependencies
--> Running transaction check
---> Package glibc.i686 0:2.12-1.192.el6 will be installed
--> Processing Dependency: libfreebl3.so for package: glibc-2.12-1.192.el6.i686
--> Processing Dependency: libfreebl3.so(NSSRAWHASH_3.12.3) for package: glibc-2.12-1.192.el6.i686
--> Running transaction check
---> Package nss-softokn-freebl.i686 0:3.14.3-23.el6_7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package                 Arch      Version              Repository         Size
================================================================================
Installing:
 glibc                   i686      2.12-1.192.el6       InstallMedia      4.4 M
Installing for dependencies:
 nss-softokn-freebl      i686      3.14.3-23.el6_7      InstallMedia      157 k

Transaction Summary
================================================================================
Install       2 Package(s)

Total download size: 4.5 M
Installed size: 14 M
Is this ok [y/N]: y
Downloading Packages:
--------------------------------------------------------------------------------
Total                                           4.7 MB/s | 4.5 MB     00:00
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing : glibc-2.12-1.192.el6.i686                                    1/2
  Installing : nss-softokn-freebl-3.14.3-23.el6_7.i686                      2/2
  Verifying  : nss-softokn-freebl-3.14.3-23.el6_7.i686                      1/2
  Verifying  : glibc-2.12-1.192.el6.i686                                    2/2

Installed:
  glibc.i686 0:2.12-1.192.el6

Dependency Installed:
  nss-softokn-freebl.i686 0:3.14.3-23.el6_7

Complete!

2012-02-12

Java: OmitStackTraceInFastThrow

あるとき開発者からこんなことを聞かれた。

「Java のスタックトレースが出なくなることがあるんだけど、リモートデバッグを始めると出るようになるんだよ。そしてリモートデバッグを止めると、また出なくなるの。そういうの、何か知らない?」

最初、「この人は何を言ってるんだろう? :-o」と思っていたが、念のため調べてみると・・・、

本当にあったー!

OmitStackTraceInFastThrow が原因らしい。こいつはデフォルトで ON になっているので、OFF にするにはオプション -XX:-OmitStackTraceInFastThrow を使う。

昔 Java のオプションを決めるときに散々調べたけど、このオプションは見なかったなー。GC 関連のオプションは飽きるほど見たけど。でもこれはもっと有名になっても良いオプションだと思う。

サーバ VM のコンパイラではすべての「コールド」組み込み例外に対する的確なスタックバックトレースを提供しています。このような例外が数回スローされると、パフォーマンス向上のため、メソッドが再コンパイルされることがあります。再コンパイルの後、コンパイラはスタックトレースを提供しない事前割り当て済みの例外を使用して、より速い方法を選択できます。事前割り当て済みの例外を全く使用しないようにするには新しいフラグ -XX:-OmitStackTraceInFastThrow を使用します。