先日CodeCacheによる性能問題が発生した、原因を突き止めるには時間が掛かりました。 そのわけは
よく言われているJVM性能に影響する要素を一通り確認しても、問題の特出が出来なかった。
今回の障害ケースは以下のような特徴があります。
この現象はメモリリークによるGC時間が伸びるのような性能劣化ケースと似ている。
しかしGCによるスローダウンは波があるはず、かつ特定画面が遅いとは限らないでしょう。
つまり、CodeCacheによる性能劣化は原因を気づきにくい場合があります。
最終的Flight RecorderのJITコンパイラー記録から問題がCodeCacheにあることを推測した。
OpenJDKご使用の場合 -XX:+PrintCompilation
JVM起動フラグでJITコンパイルログから同
じことも確認できる。
スローダウン前に取れた記録
性能劣化時間帯で取れた記録
ご覧の通り、性能劣化時間帯にJITコンパイラーが動いていないようだ。 CodeCache領域の使用量を確認すると、空きがないことも確認されました。 ここで問題はCodeCacheが足りないと推測出来るでしょう。ただ単に推測に過ぎないので、 根拠付けるためにテスト用アプリケーションをデプロイしてJITコンパイラーの挙動を確認しました。 やはりJITコンパイラーが動いていないのだ。
CodeCacheについてJava Magazineに BEN EVANS さんの記事が分かりやすいと思います。
以下は自分のまとめです。
フラグ | 説明 | JDKバージョン |
---|---|---|
-XX:+PrintCodeCache | JVM終了時に出力する | JDK 8から利用可能 |
-XX:+PrintCodeCacheOnCompilation | JITコンパイル動作時に出力する | JDK 8から利用可能 |
OpenJDK8で -XX:+PrintCodeCache
フラグの出力サンプル
$ java -XX:+PrintCodeCache -version openjdk version "1.8.0_51" OpenJDK Runtime Environment (build 1.8.0_51-b16) OpenJDK 64-Bit Server VM (build 25.51-b03, mixed mode) CodeCache: size=245760Kb used=1057Kb max_used=1068Kb free=244702Kb bounds [0x00007f79a5000000, 0x00007f79a5270000, 0x00007f79b4000000] total_blobs=220 nmethods=2 adapters=135 compilation: enabled
各項目について
項目名 | 値 | 説明 |
---|---|---|
size | 245760Kb | CodeCache領域予約サイズ(最大サイズ) |
used | 1057Kb | 現在の使用量 |
max_used | 1068Kb | 最高水標(High Water Mark) |
free | 244702Kb | 未使用分のサイズ |
bounds | CodeCache領域のメモリアドレス | |
total_blobs | ★TODO調査 | |
nmethods | ★TODO調査 | |
adapters | ★TODO調査 | |
compilation | enabled | JITコンパイラーの状態 |
size
の値は -XX:ReservedCodeCacheSize
オプションで指定可能です。compilation
の値がJITコンパイラーの状態を示しているため、重要な指標と考えます。
OpenJDK8で -XX:+PrintCodeCacheOnCompilation
フラグの出力サンプル
$ java -XX:+PrintCodeCacheOnCompilation -jar sample.jar CodeCache: size=245760Kb used=1022Kb max_used=1031Kb free=244737Kb CodeCache: size=245760Kb used=1056Kb max_used=1065Kb free=244703Kb CodeCache: size=245760Kb used=1060Kb max_used=1070Kb free=244699Kb CodeCache: size=245760Kb used=1067Kb max_used=1072Kb free=244692Kb CodeCache: size=245760Kb used=1069Kb max_used=1072Kb free=244690Kb (中略)
この出力からrunning状態のJVMのCacheCode領域の変化を見ることができる。
TODO: 図形にplotする
JConsoleと同じくMBeanで確認できる、MEMORY POOL VIEW プラグインで視覚的なビューでも確 認が可能です。しかし、MEMORY POOL VIEWプラグインはプラグインセンターから直接ダウンロー ドが出来ず、手動でダウンロードし、インストールが必要となります。
-XX:NativeMemoryTracking=detail
でJVMの起動すると、jcmdからNativeメモリの割り当て状
況を追跡することができるようになります。
以下summaryモードの出力に20〜22行がCodeCache割り当ての概要となります。
$ jcmd $JAVA_PID VM.native_memory summary 1|29354: 2| 3|Native Memory Tracking: 4| 5|Total: reserved=1366991KB, committed=347155KB 6| 7|- Java Heap (reserved=1069056KB, committed=102400KB) 8| (mmap: reserved=1069056KB, committed=102400KB) 9| 10|- Class (reserved=661KB, committed=661KB) 11| (classes #5558) 12| (malloc=661KB, #4626) 13| 14|- Thread (reserved=41318KB, committed=41318KB) 15| (thread #40) 16| (stack: reserved=41120KB, committed=41120KB) 17| (malloc=121KB, #167) 18| (arena=77KB, #80) 19| 20|- Code (reserved=50209KB, committed=2825KB) 21| (malloc=289KB, #1084) 22| (mmap: reserved=49920KB, committed=2536KB) 23| 24|- GC (reserved=179288KB, committed=173556KB) 25| (malloc=139596KB, #776) 26| (mmap: reserved=39692KB, committed=33960KB) 27| 28|- Compiler (reserved=130KB, committed=130KB) 29| (malloc=32KB, #68) 30| (arena=98KB, #2) 31| 32|- Internal (reserved=1646KB, committed=1582KB) 33| (malloc=1582KB, #1631) 34| (mmap: reserved=64KB, committed=0KB) 35| 36|- Symbol (reserved=8541KB, committed=8541KB) 37| (malloc=6551KB, #66839) 38| (arena=1990KB, #1) 39| 40|- Memory Tracking (reserved=5169KB, committed=5169KB) 41| (malloc=5169KB, #166) 42| 43|- Tracing (reserved=10436KB, committed=10436KB) 44| (malloc=10436KB, #91) 45| 46|- Pooled Free Chunks (reserved=539KB, committed=539KB) 47| (malloc=539KB)
detailモードに”reserved xxxxxKB for Code”部分がCodeCacheの割り当て詳細となります。
(中略) [0x00007f2691000000 - 0x00007f2694000000] reserved 49152KB for Code from [ReservedSpace::initialize(unsigned long, unsigned long, bool, char*, unsigned long, bool)+0x266] [0x00007f2691000000 - 0x00007f2691270000] committed 2496KB from [VirtualSpace::expand_by(unsigned long, bool)+0x1c9] (中略) [0x00007f26985af000 - 0x00007f269866f000] reserved 768KB for Code from [ReservedSpace::initialize(unsigned long, unsigned long, bool, char*, unsigned long, bool)+0x266] [0x00007f26985af000 - 0x00007f26985b9000] committed 40KB from [VirtualSpace::expand_by(unsigned long, bool)+0x1c9] (中略)
通常CodeCahe領域が溢れた時にJVMの標準出力に警告ログが出される。しかしJDK7一部バージョ ンではこのログが出力されない場合があります。
Java HotSpot(TM) Server VM warning: CodeCache is full. Compiler has been disabled. Java HotSpot(TM) Server VM warning: Try increasing the code cache size using -XX:ReservedCodeCacheSize=
事例
未完成続く…
以下は未整理のネタ CodeCacheMinimumFreeSpace I2C/C2I adapters 用の予約スペース
jstat -gc
でJVMのメモリ使用状況を確認する際によく使うのですが、少し見づらいと感じま
したので、整形用のオレオレスクリプトを作成しました。
jstat出力見づらい要因として
デフォルトの出力形式は以下の通り
$ jstat -gc 10901 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 55296.0 55296.0 0.0 0.0 334336.0 157179.1 890368.0 23266.0 35748.0 32020.9 5248.0 4068.9 3 0.124 2 0.195 0.320
次は自作スクリプトで整形後の出力形式(JDK8の場合)
--------------------------------------------- S0 領域 | 54.00 / 0.00 (MB) | 0.00% S1 領域 | 54.00 / 0.00 (MB) | 0.00% Eden領域 | 326.50 / 170.63 (MB) | 52.26% Old 領域 | 22.72 / 869.50 (MB) | 2.61% Meta領域 | 31.27 / 34.91 (MB) | 89.57% CCPS領域 | 3.97 / 54.00 (MB) | 77.53% --------------------------------------------- YGC 回数 | 3 回 YGC 時間 | 0.12 秒 FGC 回数 | 2 回 FGC 時間 | 0.20 秒 ---------------------------------------------
コードはgithubに公開しています。
出力加工機能以外、次の便利な機能も提供しています。
CentOS 6.6でyum更新したら404エラーとなって更新できなくなった。
# yum update 読み込んだプラグイン:fastestmirror 更新処理の設定をしています Loading mirror speeds from cached hostfile * base: ftp.iij.ad.jp * extras: ftp.iij.ad.jp * updates: centos.usonyx.net http://ftp.iij.ad.jp/pub/linux/centos/6.6/os/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 22 - "The requested URL returned error: 404 Not Found" 他のミラーを試します。 http://ftp.jaist.ac.jp/pub/Linux/CentOS/6.6/os/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 22 - "The requested URL returned error: 404 Not Found" 他のミラーを試します。 http://ftp.nara.wide.ad.jp/pub/Linux/centos/6.6/os/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 22 - "The requested URL returned error: 404 Not Found" 他のミラーを試します。 http://ftp.riken.jp/Linux/centos/6.6/os/x86_64/repodata/repomd.xml: [Errno 14] PYCURL ERROR 22 - "The requested URL returned error: 404 Not Found" 他のミラーを試します。
ブラウザからミラーサイトを直接確認するとreadmeファイルしかありませんでした。
readmeの内容は下記となります。
$ curl http://ftp.iij.ad.jp/pub/linux/centos/6.6/readme This directory (and version of CentOS) is deprecated. For normal users, you should use /6/ and not /6.6/ in your path. Please see this FAQ concerning the CentOS release scheme: https://wiki.centos.org/FAQ/General If you know what you are doing, and absolutely want to remain at the 6.6 level, go to http://vault.centos.org/ for packages. Please keep in mind that 6.0, 6.1, 6.2, 6.3, 6.4 , 6.5 and 6.6 no longer gets any updates, nor any security fix's.
簡単に言うと更新が止まった旧バージョンのパッケージは http://vault.centos.org/
ドメイン下に移管された。
というわけでyumリポジトリのURLを書き換えれば問題が解消される。
$ sudo sed -i -e "s|mirror\.centos\.org/centos/\$releasever|vault\.centos\.org/6.6|g" /etc/yum.repos.d/CentOS-Base.repo $ sudo sed -i -e "s|#baseurl=|baseurl=|g" CentOS-Base.repo $ sudo sed -i -e "s|mirrorlist=|#mirrorlist=|g" CentOS-Base.repo # yum update 読み込んだプラグイン:fastestmirror 更新処理の設定をしています Loading mirror speeds from cached hostfile base | 3.7 kB 00:00 extras | 3.4 kB 00:00 updates | 3.4 kB 00:00 (中略)
本記事のOpenJDK障害は次の環境で確認しています。
$ java -version openjdk version "1.8.0_60" OpenJDK Runtime Environment (build 1.8.0_60-b24) OpenJDK 64-Bit Server VM (build 25.60-b23, mixed mode) $ uname -a Linux mimi 4.1.6-1-ARCH #1 SMP PREEMPT Mon Aug 17 08:52:28 CEST 2015 x86_64 GNU/Linux
$ /usr/lib/jvm/java-8-openjdk/bin/jinfo -sysprops 22286 Attaching to process ID 22286, please wait... Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$LinuxDebuggerLocalWorkerThread.execute(LinuxDebuggerLocal.java:163) at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal.attach(LinuxDebuggerLocal.java:278) at sun.jvm.hotspot.HotSpotAgent.attachDebugger(HotSpotAgent.java:671) at sun.jvm.hotspot.HotSpotAgent.setupDebuggerLinux(HotSpotAgent.java:611) at sun.jvm.hotspot.HotSpotAgent.setupDebugger(HotSpotAgent.java:337) at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:304) at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140) at sun.jvm.hotspot.tools.Tool.start(Tool.java:185) at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118) at sun.jvm.hotspot.tools.JInfo.main(JInfo.java:138) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at sun.tools.jinfo.JInfo.runTool(JInfo.java:108) at sun.tools.jinfo.JInfo.main(JInfo.java:76) Caused by: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal.attach0(Native Method) at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal.access$100(LinuxDebuggerLocal.java:62) at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$1AttachTask.doit(LinuxDebuggerLocal.java:269) at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$LinuxDebuggerLocalWorkerThread.run(LinuxDebuggerLocal.java:138)
同じ障害に遭遇した方々
straceで見たら、 ptrace システムコールのPTRACE_ATTACHリクエストで実行中のJavaプロセス
をアタッチしようとするところで Operation not permitted
理由に怒られた。
|$ strace -ff /usr/lib/jvm/java-8-openjdk/bin/jinfo -sysprops 22286 |(中略) |[pid 24277] close(7) = 0 **|[pid 24277] ptrace(PTRACE_ATTACH, 22286, 0, 0) = -1 EPERM (Operation not permitted) |[pid 24277] futex(0x7fef94008854, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x7fef94008850, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1 |[pid 24263] <... futex resumed> ) = 0 |[pid 24277] futex(0x7fef9414b154, FUTEX_WAIT_PRIVATE, 3, NULL <unfinished ...> |[pid 24263] futex(0x7fef94008828, FUTEX_WAKE_PRIVATE, 1) = 0 |[pid 24263] write(2, "Error attaching to process: ", 28Error attaching to process: ) = 28 |[pid 24274] futex(0x7fef940bb654, FUTEX_WAIT_BITSET_PRIVATE, 57, {29187, 107955715}, ffffffff <unfinished ...> **|[pid 24263] write(2, "sun.jvm.hotspot.debugger.Debugge"..., 71sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process) = 71 |[pid 24263] write(2, "\n", 1
これはLinuxカーネルのセキュリティモジュール yama の制限に引掛かったのが原因です。 下記手順でこの制限を解除すれば、エラーが解消されます。
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
yamaによるptrace制限に関して下記が分かやすいと思います。
https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace_Protection
もっと簡単な対応方法として、sudoでrootユーザで実行すれば良いでしょう。
$ /usr/lib/jvm/java-8-openjdk/bin/jinfo -sysprops 22286 Attaching to process ID 22286, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.60-b23 Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at sun.tools.jinfo.JInfo.runTool(JInfo.java:108) at sun.tools.jinfo.JInfo.main(JInfo.java:76) Caused by: java.lang.InternalError: Metadata does not appear to be polymorphic at sun.jvm.hotspot.types.basic.BasicTypeDataBase.findDynamicTypeForAddress(BasicTypeDataBase.java:278) at sun.jvm.hotspot.runtime.VirtualBaseConstructor.instantiateWrapperFor(VirtualBaseConstructor.java:102) at sun.jvm.hotspot.oops.Metadata.instantiateWrapperFor(Metadata.java:68) at sun.jvm.hotspot.memory.SystemDictionary.getSystemKlass(SystemDictionary.java:127) at sun.jvm.hotspot.runtime.VM.readSystemProperties(VM.java:879) at sun.jvm.hotspot.runtime.VM.getSystemProperties(VM.java:873) at sun.jvm.hotspot.tools.SysPropsDumper.run(SysPropsDumper.java:44) at sun.jvm.hotspot.tools.JInfo.run(JInfo.java:94) at sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:260) at sun.jvm.hotspot.tools.Tool.start(Tool.java:223) at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118) at sun.jvm.hotspot.tools.JInfo.main(JInfo.java:138) ... 6 more
原因はよくわからないのですが、twitter上で流れた情報 によるとJVMのdebuginfoを入れれば 問題が回避されるようです。実際に確認したところで確かに回避出来た、しかしdebuginfo禁止 の商用環境やdebuginfoが簡単に導入出来ないdistroには寂しいでしょう。
yumが使える環境は、次のように debuginfo-install
で簡単にインストールが出来ます。
$ sudo debuginfo-install java-1.8.0-openjdk-devel
$ /usr/lib/jvm/java-8-openjdk/bin/jmap -heap 22286 Attaching to process ID 22286, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.60-b23 using thread-local object allocation. Parallel GC with 4 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 1367343104 (1304.0MB) NewSize = 455606272 (434.5MB) MaxNewSize = 455606272 (434.5MB) OldSize = 911736832 (869.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at sun.tools.jmap.JMap.runTool(JMap.java:201) at sun.tools.jmap.JMap.main(JMap.java:130) Caused by: java.lang.RuntimeException: unknown CollectedHeap type : class sun.jvm.hotspot.gc_interface.CollectedHeap at sun.jvm.hotspot.tools.HeapSummary.run(HeapSummary.java:144) at sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:260) at sun.jvm.hotspot.tools.Tool.start(Tool.java:223) at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118) at sun.jvm.hotspot.tools.HeapSummary.main(HeapSummary.java:49) ... 6 more
このエラーもdebuginfoをインストールすれば、回避出来る。
↓OpenJDK7でバグ報告された記録、残念ながら WONTFIX 状態でした。
この問題はブラウザのマルチプロファイル機能を利用すればを解決する。
例えばFirefoxに --new-instance
オプションを付けて起動時すると下記のようなプロファイ
ル選択ダイアログが表示されて、「Create Profile」で新しいプロファイルを持ちたFirefoxが
起動される。
自分の環境では下記のdesktopファイルを追加することでランチャーからいつも起動出来るよう にしています。
.local/share/applications/myfirefox.desktop
[Desktop Entry] Name=MyFirefox GenericName=Web Browser GenericName[ja]=ウェブ・ブラウザ Comment[ja]=ウェブを閲覧します Exec=firefox --new-instance %u Terminal=false Type=Application Icon=firefox Categories=Network;WebBrowser; MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp; StartupNotify=true Actions=NewTab;NewWindow;NewPrivateWindow; [Desktop Action NewTab] Name[ja]=新しいタブ [Desktop Action NewWindow] Name[ja]=新しいウィンドウ [Desktop Action NewPrivateWindow] Name[ja]=新しいプライベートウィンドウ]]>
以前から percol の流行りに気になっていたが、python製のため導入が面倒さそうなので保留 しました。最近、percolを元にgolangで書かれたpecoが出てきて導入しやすくなったと感じて 手を入れました。
具体なイメージはgithubの README が分かりやすい
一番簡単なのは peco
バイナリを PATH
に通る。
$ wget https://github.com/peco/peco/releases/download/v0.3.3/peco_linux_amd64.tar.gz $ tar xzvf peco_linux_amd64.tar.gz $ tree peco_linux_amd64 peco_linux_amd64 ├── Changes ├── README.md └── peco 0 directories, 3 files $ export PATH=`pwd`/peco_linux_amd64:$PATH $ peco --version peco: v0.3.3
下記内容を ~/.bashrc
に追加する
# 重複履歴を無視 export HISTCONTROL=ignoreboth:erasedups # 履歴保存対象から外す export HISTIGNORE="fg*:bg*:history*:wmctrl*:exit*:ls -al:cd ~" # コマンド履歴にコマンドを使ったの時刻を記録する export HISTTIMEFORMAT='%Y%m%d %T ' export HISTSIZE=10000 # settings for peco _replace_by_history() { local l=$(HISTTIMEFORMAT= history | cut -d" " -f4- | tac | sed -e 's/^\s*[0-9]* \+\s\+//' | peco --query "$READLINE_LINE") READLINE_LINE="$l" READLINE_POINT=${#l} } bind -x '"\C-r": _replace_by_history' bind '"\C-xr": reverse-search-history'
これで、 Ctrl-r
で起動されたBash履歴検索がpecoインタフェースに変わる。
元の検索インタフェースは Ctrl-x r
で起動することも出来る。
$ mkdir ~/.config/peco $ touch ~/.config/peco/config.json $ cat <<_EOT_ > ~/.config/peco/config.json { "Keymap": { "C-p": "peco.SelectPrevious", "C-n": "peco.SelectNext", "C-g": "peco.Cancel", "C-v": "peco.SelectNextPage", "C-@": "peco.ToggleSelectionAndSelectNext" } } _EOT_
ところで先週Windows端末再セットアップしたため、Sambaサーバに接続時にパスワードが聞か れた時が、すっかり忘れましたのでSamba側でパスワードリセットをしました。
$ sudo pdbedit -a -u test001 new password: retype new password:
$ pdbedit -L tdbsam_open: Failed to open/create TDB passwd [/var/lib/samba/private/passdb.tdb] tdbsam_getsampwnam: failed to open /var/lib/samba/private/passdb.tdb! User Search failed!
https://docs.docker.com/articles/systemd/
ここでは Acrh Linx で確認された手順を記録する。
社内はフォワードプロキシ経由で外と繋ぐなので、デフォルトでは通らない ;-(
$ docker search oraclelinux Error response from daemon: Get https://index.docker.io/v1/search?q=oraclelinux: dial tcp 54.174.226.171:443: no route to host
/etc/systemd/system/<サービスID>.d/
配下に環境変数の上書きファイルを定義し、サービ
スをリロードするだけです。
$ sudo mkdir /etc/systemd/system/docker.service.d $ sudo touch /etc/systemd/system/docker.service.d/http-proxy.conf $ sudo nano /etc/systemd/system/docker.service.d/http-proxy.conf [Service] Environment="HTTP_PROXY=http://*********************:8081" Environment="HTTPS_PROXY=http://*********************:8081" $ sudo systemctl daemon-reload $ sudo systemctl restart docker $ docker search oraclelinux NAME DESCRIPTION STARS OFFICIAL AUTOMATED oraclelinux Oracle Linux is an open-source operating s... 46 [OK] oracle/oraclelinux Oracle Linux is an open-source operating s... 16 [OK] tvierling/oraclelinux Oracle Linux base images, yum-updated to l... 2 [OK] centminmod/oraclelinux65base Oracle Linux 6.5 base 1.57GB image 1 avmiller/oraclelinux Personal Oracle Linux Test Images (Non-pro... 0 kiwenlau/oraclelinux 0 talberto/oraclelinux 0 ksasi/oraclelinux 0 saltfactory/oraclelinux Oracle Linux 0 arpagaus/oraclelinux 0 joseperez/oraclelinux-lamp oraclelinux 7.0 con mariadb, apache y php. 0 jinyan/oraclelinux Oracle Linux 6.6 0 hootjr/oraclelinux 0 poisoncreed/oraclelinux-base-with-httpd 0 bdpzone/bdporaclelinux6 Big Data Partnership Development Environme... 0 [OK] dyoung522/oracle-ruby OracleLinux running Ruby 0 [OK] bdpzone/bdporaclelinux7 Big Data Partnership Development Environme... 0 [OK] mlechner/oraclelinux7 GIS tools on Oracle Linux 7 0 tehmul/oraclelinux6-tc Oracle Linux 6+Oracle Server JRE 8+ Apache... 0 calaniz/oraclelinux 0 dyoung522/oraclelinux-dev OracleLinux with development environment 0 [OK] hedlund/oraclelinux 0 [OK] florentbenoit/oraclelinux-jdk7 0 [OK]]]>
mewで複数メールアカウントで扱うときにSummary モードにおいて C
で切り替えることが出
来るので非常に便利です。ただし、起動時にdefaultアカウントの選択機能が持っていないので、
mew-init-hookで拡張してみました。
;; アカウント1 (setq my-mew-config-1 (list '(proto "+") '(name "******") '(user "******") ; メールアドレスの@から左部分 '(mail-domain "******") ; メールアドレスの@から右部分 '(smtp-server "******") ; 送信用サーバアドレス '(smtp-port "1025") ; '(smtp-user "******") ; 送信用ユーザid '(pop-server "******") ; 受信用サーバアドレス '(pop-port "1110") ; '(pop-user "******") ; 受信用ユーザid '(pop-delete t) ; メール受信後サーバー側のメッセージを7日を保持する '(pop-auth pass) ; pop3 で受信時の認証方式、指定しない場合はapop方式が使用される '(pop-size 0) ; メールの上限サイズ。0 の場合は上限なし。 '(pop-header-only t) ; [c-u s]でヘッダの みのコピー '(dcc "******") ; 返信時に自分のアドレスをdccに入れる、dccは送信したメールのヘッダーに表示されない '(fcc "+sent") ; 送信したメールの保存先 '(smime-signer "******") ; 電子署名で使用する証明書id '(protect-privacy-always nil) '(privacy-method smime) ; 電子署名タイプの指定 '(draft-privacy-method smime) '(protect-privacy-always-type smime-signature) )) ;; アカウント2 (setq my-mew-config-2 (list '(proto "%") '(name "******") '(user "******") '(mail-domain "gmail.com") '(dcc "*******@gmail.com") '(protect-privacy-always nil) '(ssl-verify-level 0) ;; smtpサーバー '(smtp-user "*******@gmail.com") '(smtp-auth t) '(smtp-ssl t) '(smtp-server "smtp.gmail.com") '(smtp-ssl-port "465") ;; imapを使用する場合 '(inbox-folder "%gmail") '(imap-user "******@gmail.com") '(imap-size 5242880) ; 5m以内即時受信する '(imap-auth t) '(imap-ssl t) '(imap-ssl-port "993") '(imap-server "imap.gmail.com") )) ;;; アカウント3 (setq my-mew-config-3 (list '(proto "+") ;; (中略) )) ;;; アカウント4 (setq my-mew-config-4 (list '(proto "+") ;; (中略) )) (setq my-mew-account-list (list (append (list 'account1) my-mew-config-1) (append (list 'account2) my-mew-config-2) (append (list 'account3) my-mew-config-3) (append (list 'account4) my-mew-config-4))) (require 'dash) ;; アカウント選択処理 (defun my-mew-select-account () (let* ((account-name-list (mapcar (lambda (x) (pp-to-string (car x))) my-mew-account-list)) (selected-account (completing-read "mew account: " account-name-list nil t))) (message selected-account) (setq mew-config-alist (append (list (append (list 'default) (cdr (car (-filter (lambda (mew-config) (string= selected-account (pp-to-string (car mew-config)))) my-mew-account-list))))) (-filter (lambda (mew-config) (not (string= selected-account (pp-to-string (car mew-config))))) my-mew-account-list))))) ;; 起動時に走らせる (add-hook 'mew-init-hook 'my-mew-select-account)
デモ
]]>
/proc/<pid>/smaps
プロセスメモリマッピングファイルからjavaスレッドのスタック使用状
況をいい感じに出力するスクリプトを作りました。
/proc/<pid>/smaps にスタックセグメントが次のように [stack:スレッドID]
ラベルが出
力されていること(Linux 3.4 以降)
7f3984233000-7f3984331000 rw-p 00000000 00:00 0 [stack:2988] Size: 1016 kB Rss: 92 kB Pss: 92 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 92 kB Referenced: 92 kB Anonymous: 92 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB VmFlags: rd wr mr mw me ac
スクリプトの元ネタは以前の記事を参考してください。 jvmのスタックサイズについて
#!/bin/sh ########################################################################### # jvm_stacksize.sh - take a jvm stack size snapshot # # Authors: Akira Wakana <jalen.cn@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses # # Usage: # $jvm_stacksize.sh <JVM ProcessID> ########################################################################### PID=$1 ps ${PID} | grep [j]ava > /dev/null || { echo "-----------------------------------------------------------------------" ps -ef | grep [j]ava echo "-----------------------------------------------------------------------" echo -n "please input the java process id: " read PID } printf "[ PID ]\t[StackSize(kB)]\t[GuardPage(kB)]\t[UsedSize(kB)]\t[Thread Name]\n" # jstackの出力結果からスレッドIDと名前を抽出する jstack ${PID} | grep nid | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/" | sort | while read line do # スレッドIDを切り出す pid_hex=`echo "${line}" | awk -F"," '{print $1}'` # スレッド名を切り出す thread_name=`echo "${line}" | awk -F"," '{print $2}'` # スレッドIDを10進数に変換 pid=`printf '%d\n' ${pid_hex}` # /proc/<pid>/smaps ファイルからスタックサイズ、ガードページサイズを取得する # 次のsmaps出力サンプルに★コメントの部分は取得詳細ロジックとなります。 # # 7ff751076000-7ff751079000 ---p 00000000 00:00 0 # Size: 12 kB ★説明: StackRedPages(4Kb) + StackYellowPages(8Kb) = 12Kb # Rss: 0 kB # Pss: 0 kB # Shared_Clean: 0 kB # Shared_Dirty: 0 kB # Private_Clean: 0 kB # Private_Dirty: 0 kB # Referenced: 0 kB # Anonymous: 0 kB # AnonHugePages: 0 kB # Swap: 0 kB # KernelPageSize: 4 kB # MMUPageSize: 4 kB # Locked: 0 kB # VmFlags: mr mw me ac ★説明:書き込み不可 # 7ff751079000-7ff751177000 rw-p 00000000 00:00 0 [stack:21275] # Size: 1016 kB ★説明: ここからNormal Stackページ # Rss: 108 kB # Pss: 108 kB # Shared_Clean: 0 kB # Shared_Dirty: 0 kB # Private_Clean: 0 kB # Private_Dirty: 108 kB # Referenced: 108 kB # Anonymous: 108 kB # AnonHugePages: 0 kB # Swap: 0 kB # KernelPageSize: 4 kB # MMUPageSize: 4 kB # Locked: 0 kB # VmFlags: rd wr mr mw me ac guard_page=`cat /proc/${PID}/smaps | grep -B16 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` stack_page=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` used_size=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Rss:" | awk '{print $2}'` stack_size=`expr ${guard_page} + ${stack_page}` printf "%7d\t%15s\t%15s\t%14s\t%s\n" "${pid}" "${stack_size}" "${guard_page}" "${used_size}" "${thread_name}" done
package test01; public class TestTask implements Runnable { @Override public void run() { add1(1); } private int add1(int a) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } return add2(a++); } private int add2(int a) { //add1 -> add2 --> add1 無限ループの状態で //スタックを食い潰す return add1(a--); } public static void main(String[] args) throws Exception { Thread t1 = new Thread(new TestTask()); Thread t2 = new Thread(new TestTask()); t1.start(); t2.start(); t1.join(); t2.join(); } }
[ PID ] [StackSize(kB)] [GuardPage(kB)] [UsedSize(kB)] [Thread Name] 16559 1044 12 116 main 16560 1112 4 92 GC task thread#0 (ParallelGC) 16561 1028 4 8 GC task thread#1 (ParallelGC) 16562 1028 4 8 GC task thread#2 (ParallelGC) 16563 1028 4 8 GC task thread#3 (ParallelGC) 16564 12336 4 56 VM Thread 16565 1028 12 88 Reference Handler 16566 1028 12 88 Finalizer 16567 1028 12 92 Signal Dispatcher 16568 1028 12 20 C2 CompilerThread0 16569 1028 12 8 C2 CompilerThread1 16570 1028 12 12 C1 CompilerThread2 16571 1028 12 8 Service Thread 16572 1028 4 8 VM Periodic Task Thread 16573 1028 12 428 Thread-0 16583 1028 12 228 Thread-1 16613 1028 12 12 Attach Listener
項目名 | 説明 |
---|---|
PID | JavaスレッドのプロセスID |
StackSize | Javaスタック+Nativeスタックのサイズ (kB) |
GuardPage | スタック保護ページ (kB) |
UsedSize | 実際使った物理メモリサイズ (kB) |
Thread Name | スレッド名 |
バッチリ!便利な道具が1つ増えました。
前の実装は watch
コマンドでスクリプトを繰り返し実行させスタックサイズの監視をしてい
たのですが、繰り返しサイクルで毎回jstackでスレッドダンプを取得しているため若干JVMにオー
バヘッドがかかる。特にスレッドを多数起動されたアプリケーションサーバの場合性能へ影響
が無視できないので、少しスクリプトの実装シナリオを変えて改善してみました。
基本コンセプトは
jstack
を1回のみ実行する、その結果(スレッドダンプ)をtmpファイルで保持するps -H <PID>
でスレッドIDを取得して、1のスレッドダンプからスレッド名を引きjstack
で新しいスレッドダンプを取る#!/bin/sh ########################################################################### # jvm_stacksize.sh - take jvm stack size snapshot # # Authors: Akira Wakana <jalen.cn@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # Usage: # $jvm_stacksize.sh <JVM ProcessID> ########################################################################### print_stacksize() { rm -rf ${tmpdir}/stacksize.txt printf "[ PID ]\t[StackSize(kB)]\t[GuardPage(kB)]\t[UsedSize(kB)]\t[Thread Name]\n" ps h -L --format=lwp ${PID} | grep -v "${PID}" | while read pid do # スレッドID pid_hex=`printf '%#x\n' $pid` # スレッド名を切り出す threadinfo=`cat ${threadtdump} | fgrep " nid=${pid_hex} " | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/"` if [ "${threadinfo}" == "" ]; then jstack ${PID} > ${threadtdump} threadinfo=`cat ${threadtdump} | fgrep " nid=${pid_hex} " | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/"` fi thread_name=`echo "${threadinfo}" | awk -F"," '{print $2}'` # # /proc/<pid>/smaps ファイルからスタックサイズ、ガードページサイズを取得する guard_page=`cat /proc/${PID}/smaps | grep -B16 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` stack_page=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Size:" | awk '{print $2}'` used_size=`cat /proc/${PID}/smaps | grep -A15 "stack:${pid}" | grep -e "^Rss:" | awk '{print $2}'` stack_size=`expr ${guard_page} + ${stack_page}` printf "%7d\t%15s\t%15s\t%14s\t%s\n" "${pid}" "${stack_size}" "${guard_page}" "${used_size}" "${thread_name}" done > ${tmpdir}/stacksize.txt sort -r -k4,4 ${tmpdir}/stacksize.txt } finally_func() { RET=$? if [ -d ${tmpdir} ]; then rm -rf ${tmpdir} fi exit ${RET} } trap finally_func EXIT PID=$1 ps ${PID} | grep [j]ava > /dev/null || { echo "-----------------------------------------------------------------------" ps -ef | grep [j]ava echo "-----------------------------------------------------------------------" echo -n "please input the java process id: " read PID } export PID export tmpdir=`mktemp -d` export threadtdump=${tmpdir}/${PID}.tdump jstack ${PID} > ${threadtdump} export -f print_stacksize watch "bash -c print_stacksize" rm -rf ${tmpdir}
一般的なアプリでは実行中スレッドの数が頻繁に変わらないので、これで満足しています。;-)
業務用の開発サーバ
項目 | スペック |
---|---|
CPU | Intel(R) Core(TM)2 Duo CPU E8400 3.00GHz |
Core数 | 2 |
Memory | 7G弱 |
OS | Red Hat Enterprise Linux Server release 5.8 (Tikanga) |
Middleware | java 1.7.0_75 |
Weblogic 10.3.5.0 | |
Oracle BPM Suite 11g (メモリを多めに割り当てた:6Gぐらい) | |
※他にものもの結構乗っている |
2015-06-24T18:08:32.220+0900: 5.2666840 2015-06-25T01:50:56.578+0900: 5.6046080 2015-06-25T04:50:52.234+0900: 6.0052120 2015-06-25T12:05:52.068+0900: 9.2609380 2015-06-25T13:20:37.953+0900: 5.0946760 2015-06-25T13:20:58.403+0900: 5.6533820 2015-06-25T20:24:49.357+0900: 7.3204730 2015-06-25T21:16:10.126+0900: 6.7909460 2015-06-25T22:50:58.756+0900: 5.8355060 2015-06-26T01:50:50.893+0900: 9.3892250 2015-06-26T04:50:53.219+0900: 13.1395190 2015-06-26T07:50:54.425+0900: 11.7807430 2015-06-26T07:51:07.269+0900: 5.2793300 2015-06-26T10:50:51.114+0900: 9.3924480 2015-06-26T13:17:06.286+0900: 6.2010290 2015-06-26T13:17:54.396+0900: 7.3440480 2015-06-26T13:34:05.326+0900: 7.6791300 2015-06-26T13:51:04.590+0900: 10.2367630 2015-06-26T14:00:00.516+0900: 5.7046080 2015-06-26T14:00:13.463+0900: 6.6349530 2015-06-26T14:23:00.180+0900: 5.6609030 2015-06-26T14:26:28.796+0900: 7.6549640 2015-06-26T14:27:00.169+0900: 11.8500770 2015-06-26T14:29:39.363+0900: 7.1499710 2015-06-26T14:30:24.067+0900: 5.6382340 2015-06-26T14:31:45.304+0900: 10.1924060 2015-06-26T14:32:00.774+0900: 9.6242400 2015-06-26T16:51:18.422+0900: 9.6966490 2015-06-26T16:51:45.363+0900: 6.2090530 2015-06-26T18:30:49.993+0900: 6.2954670 2015-06-26T18:38:00.437+0900: 8.8749850 2015-06-26T18:50:00.391+0900: 7.4192490 2015-06-26T18:54:00.271+0900: 6.4426840 2015-06-26T18:54:08.234+0900: 5.7452440 2015-06-27T01:50:50.387+0900: 5.8063930 2015-06-27T01:51:05.004+0900: 5.2115420 2015-06-27T04:50:58.781+0900: 12.9891770 2015-06-27T04:51:35.039+0900: 5.7282510 2015-06-27T07:50:51.112+0900: 13.1250670 2015-06-27T07:51:06.604+0900: 6.5727200 2015-06-27T07:51:29.483+0900: 5.3706130 2015-06-27T07:51:36.469+0900: 7.0014700 2015-06-27T07:51:49.055+0900: 7.4964390 2015-06-27T07:52:01.446+0900: 5.2537540 2015-06-27T08:01:04.920+0900: 21.8820470 2015-06-27T09:53:02.648+0900: 7.4830120 2015-06-27T10:50:52.478+0900: 12.4821770 2015-06-27T10:51:07.595+0900: 6.6430700 2015-06-27T10:51:34.192+0900: 5.4682220 2015-06-27T10:51:59.431+0900: 5.8981600 2015-06-27T13:50:54.701+0900: 13.1269190 2015-06-27T13:52:23.353+0900: 6.3293780 2015-06-27T16:46:06.634+0900: 7.5433970 2015-06-28T04:51:06.881+0900: 5.2944910 2015-06-28T13:50:55.136+0900: 7.2090390 2015-06-28T16:50:52.379+0900: 6.3410470 2015-06-28T17:01:07.773+0900: 6.1038880 2015-06-28T19:50:50.750+0900: 7.1304960 2015-06-28T19:50:59.538+0900: 14.1206920 2015-06-28T19:51:39.786+0900: 7.8585130 2015-06-28T20:10:05.988+0900: 5.1535220 2015-06-28T22:46:07.996+0900: 6.3876340 2015-06-28T22:50:56.687+0900: 5.4313050 2015-06-29T01:51:06.697+0900: 7.4033850 2015-06-29T05:24:56.775+0900: 6.1023960 2015-06-29T06:01:13.724+0900: 6.7346550 2015-06-29T07:16:11.399+0900: 21.2494760 2015-06-29T07:50:59.386+0900: 5.9733680 2015-06-29T10:11:13.076+0900: 10.1998490 2015-06-29T10:51:20.998+0900: 10.4616600 2015-06-29T11:02:06.230+0900: 6.6641290
図形にPlotしてみたら、ParNewが高いGCの時間帯があんまり規則がないので、定期イベントに よるものではないと判断した。
$ vmstat 5 60 1|procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------ 2| r b swpd free buff cache si so bi bo in cs us sy id wa st 3| 0 0 3379376 115268 9804 467464 12 9 107 34 1 1 8 2 89 1 0 4| 0 0 3379376 113584 9808 468660 0 0 237 41 512 719 4 1 93 1 0 5| 0 1 3377744 95484 9828 469060 749 0 825 15 567 793 5 1 78 16 0 6| 0 1 3369436 80108 9836 469092 3175 0 3175 44 471 759 1 1 50 49 0 7| 0 1 3360436 60896 9836 469072 3807 0 3807 3 451 776 0 1 49 49 0 8| 0 1 3352216 48620 9844 465784 3375 155 3375 158 450 744 0 1 50 49 0 9| 0 1 3347272 51844 9804 450912 2879 874 2879 878 568 739 1 2 49 49 0 10| 0 1 3339972 49380 9652 444336 2601 541 2601 558 551 697 0 1 47 52 0 11| 1 1 3336692 50752 9560 434856 2364 768 2364 768 605 692 1 1 49 49 0 12| 1 1 3332740 52108 9416 425016 2293 761 2293 791 604 682 1 1 49 49 0 13| 0 1 3325100 52224 9400 412236 3399 662 3399 675 564 730 0 1 50 49 0 14| 0 1 3320168 48248 9336 405168 2868 530 2868 533 513 717 0 1 49 49 0
5行目はFullGC実施のタイミングです。5行目以降si/so/si/boの数値が上がる。
swpd列の値を見れば原因は明白ですね、物理メモリが足らずJavaプロセスのメモリが大半スワー プアウトされた、FullGCを掛けるとオブジェクトの参照を検査するためにヒープ全体を舐める。 メモリから追い出されたヒープメモリをディスクから読み戻し、また読み戻した分の領域を確 報するため、相対的に使っていないメモリをディスクに追い出す処理が激しく繰り返した。
kill -9
で強制停止する
ただし、journaldにエラーログを通知する機能が持っていないため監視通知のし掛けが必要で す。
自宅のサーバは下記スクリプトでエラーログの通知機能を実現しています。
/etc/cron.hourly/journal_error
#!/bin/sh # 一時以内のエラーログを標準出力と/var/log/journal_errorファイルへ出力する journalctl -o short-iso -p err --since -1hours 2>/dev/null | tail -n+2 | tee -a /var/log/journal_error
journalctl コマンドを駆使して1時間以内のエラーログを標準出力に出力するスクリプト。
これをcronの時間単位ジョブディレクトリ /etc/cron.hourly
に登録する。
あとはcronのメール通知機能を有効化するだけです。
cronのメール通知先は /etc/cron.d/0hourly
の MAILTO
項目にて指定する。
$ cat /etc/cron.d/0hourly # Run the hourly jobs SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=<<メールの送信先をここに書く>> 1 * * * * root run-parts /etc/cron.hourly
Linuxシステム時刻に関わる知識やリソースのまとめです。
クロック種別 | 説明 |
---|---|
ハードウェアクロック | * マザーボード上のICによって提供される時計です。 |
* 通常は電池でバックアップして駆動されるので、電源をお落としても時計が進みます。 | |
* RTC(Real Time Clock)、BIOS、CMOSクロックとも呼ばれる。 | |
システムクロック | * Linux カーネルの内部に存在している時計で、 タイマ割り込みによって駆動されている |
* Linux システムは起動時に一度だけハードウェア・クロックを参照し、 システム・クロックを設定する。 | |
* 精度の高いクロック、1GHz以上のCPUの場合1クロックは1ナノ秒のなります。 | |
* 時刻は1970/01/01T00:00:00からの経過時間を秒単位/ナノ秒単位で保持される。 |
ハードウェアクロック持っていないボードも存在する。RaspberryPiボードはその一つです、 RTCが必要な場合、別途RTCモジュールを導入しなければいけません。2
RaspberryPiで hwclock
コマンドでハードウェアクロックを参照すると /dev/rtc
デバイ
スがない旨のメッセージが表示された。
$ sudo hwclock --debug hwclock from util-linux 2.26.2 hwclock: cannot open /dev/rtc: No such file or directory No usable clock interface found. hwclock: Cannot access the Hardware Clock via any known method.
RaspberryPiはNTPサーバから時刻同期のし掛けが必要です。そしないとシステムクロックが POSIXにおける紀元時刻(Epoch; 1970-01-01 00:00:00 +0000 (UTC))に設定されてしまう。
systemdに導入された timdatectl
コマンドでシステム時刻の各種表示形式を確認する。
$ timedatectl Local time: 土 2015-06-27 23:55:12 JST Universal time: 土 2015-06-27 14:55:12 UTC RTC time: 土 2015-06-27 14:55:12 Time zone: Asia/Tokyo (JST, +0900) NTP enabled: yes NTP synchronized: yes RTC in local TZ: no DST active: n/a
各時刻項目について
正式名称 | 説明 | |
---|---|---|
Universal time | 協定世界時 (UTC) | UTC は GMT (グリニッジ標準時, Greenwich Mean Time) とも言われます |
協定世界時は、国際度量衡局 (BIPM) が維持する時刻系であり、 | ||
協定された標準周波数と報時信号発射の基礎になっている。 | ||
Local time | 標準時 | ある国または広い地域が共通で使う地方時、 |
世界各地の標準時は協定世界時 (UTC) を基準として決めている。 | ||
例えば、日本標準時 (JST) は協定世界時より9時間進んでおり、 | ||
「+0900 (JST)」のように表示する。 | ||
DST | 夏時間 | daylight saving time |
1年のうち夏を中心とした期間に、太陽の出ている時間帯を有効に利用する目 | ||
的で、標準時を1時間進める制度、またはその進められた時刻のこと。 | ||
緯度が高く夏の日照時間が長い欧米諸国などで多く導入されている。10 | ||
Linuxシステムにタイムゾーンを指定すれば自動的にDST時間を表示してくれる。 | ||
RTC time | リアルタイムクロック | 前述のハードウェアクロックです。 |
コマンド名 | 概要 |
---|---|
date | システムクロックの表示/変更 |
hwclock | ハードウェアクロックの表示/変更 |
ntpdate | NTPサーバから時刻を同期化するクライアントツール |
timedatectl | 上記機能全て統括的に行えるツール |
adjtimex | Displays or sets the kernel time variable |
時刻の表示/調整/同期化などは timedatectl
で全て出来る。
★set-ntpオプションで有効/無効を切り替える $ sudo timedatectl set-ntp true ★状態確認 $ sudo timedatectl status Local time: 日 2015-06-28 00:19:26 JST Universal time: 土 2015-06-27 15:19:26 UTC RTC time: 土 2015-06-27 15:19:26 Time zone: Asia/Tokyo (JST, +0900) NTP enabled: yes ★NTP時刻同期化有効の状態 NTP synchronized: yes ★NTP時刻同期化済み RTC in local TZ: no DST active: n/a
上記コマンドより systemd-timesyncd.service
が有効化される。
/usr/lib/systemd/systemd-timesyncd
デーモンプロセスがNTPクライアントとして動作し定
期的にNTPサーバから時刻を同期化する。
$ systemctl status systemd-timesyncd.service ● systemd-timesyncd.service - Network Time Synchronization Loaded: loaded (/usr/lib/systemd/system/systemd-timesyncd.service; enabled; vendor preset: enabled) Active: active (running) since 土 2015-06-27 19:29:29 JST; 4h 57min ago Docs: man:systemd-timesyncd.service(8) Main PID: 8787 (systemd-timesyn) Status: "Using Time Server 213.239.154.12:123 (0.arch.pool.ntp.org)." CGroup: /system.slice/systemd-timesyncd.service └─8787 /usr/lib/systemd/systemd-timesyncd 6月 27 19:29:29 mimi systemd[1]: Starting Network Time Synchronization... 6月 27 19:29:29 mimi systemd[1]: Started Network Time Synchronization. 6月 27 20:45:20 mimi systemd-timesyncd[8787]: Timed out waiting for reply from 202.234.64.222:123 (0.arch.pool.ntp.org). 6月 27 20:45:30 mimi systemd-timesyncd[8787]: Timed out waiting for reply from 157.7.153.56:123 (0.arch.pool.ntp.org). 6月 27 20:45:40 mimi systemd-timesyncd[8787]: Timed out waiting for reply from 108.61.223.189:123 (0.arch.pool.ntp.org). 6月 27 20:45:51 mimi systemd-timesyncd[8787]: Timed out waiting for reply from 133.100.11.8:123 (0.arch.pool.ntp.org).
NTPサーバは /etc/systemd/timesyncd.conf
にて指定出来る。
Asia/Tokyoタイムゾーンの指定
★利用可能なゾーンの表示 $ timedatectl list-timezones Africa/Abidjan Africa/Accra Africa/Addis_Ababa Africa/Algiers Africa/Asmara Africa/Bamako Africa/Bangui Africa/Banjul Africa/Bissau Africa/Blantyre (中略) ★タイムゾーンの指定、/etc/localtimeのリンク先が変わる $ timedatectl set-timezone Asia/Tokyo ★タイムゾーンの確認 $ timedatectl status Local time: 日 2015-06-28 17:57:04 JST Universal time: 日 2015-06-28 08:57:04 UTC RTC time: 日 2015-06-28 08:57:04 Time zone: Asia/Tokyo (JST, +0900) NTP enabled: yes NTP synchronized: yes RTC in local TZ: no DST active: n/a
America/Los_Angelesタイムゾーンの指定、「*」部分は夏時間です。
|$ timedatectl set-timezone America/Los_Angeles |[akira@mimi ~]$ timedatectl status | Local time: 日 2015-06-28 01:54:09 PDT | Universal time: 日 2015-06-28 08:54:09 UTC | RTC time: 日 2015-06-28 08:54:09 | Time zone: America/Los_Angeles (PDT, -0700) | NTP enabled: yes |NTP synchronized: yes | RTC in local TZ: no *| DST active: yes *| Last DST change: DST began at *| 日 2015-03-08 01:59:59 PST *| 日 2015-03-08 03:00:00 PDT *| Next DST change: DST ends (the clock jumps one hour backwards) at *| 日 2015-11-01 01:59:59 PDT *| 日 2015-11-01 01:00:00 PST
ハードウェアクロックの時刻をUTCとして扱うように /etc/adjtime
ファイルを更新する。
また、システムクロック時刻をハードウェアクロックに書き戻す。
$ timedatectl set-local-rtc false
/etc/adjtime
ファイルの3行目がUTCでマークされた。
$ cat /etc/adjtime 0.000000 0 0.000000 0 UTC ★ハードウェアクロックがUTC時刻として扱う
RTC が localtime だった場合予期せぬバグを引き起こす可能性があるため、最近のカーネルは
/etc/adjtime
ファイルに設定値がない場合 RTC を UTC としてみなします。 hwclock
コ
マンドに–debugオプションを付けるとハードウェアクロックがどうのように扱われるかを確認
することが出来ます。
ハードウェアクロックの値をUTC時刻として扱い、表示時に /etc/localtime
に持っている
タイムゾーン情報を元にローカル時刻で表示する。
$ hwclock --show --debug hwclock from util-linux 2.26.2 クロックの /dev インターフェイス を使用中。 ハードウェアの時刻が UTC に設定されているものと仮定します。 クロックティックを待っています... ...クロックティックを取得しました ハードウェアの時計から読み込んだ時刻: 2015/06/27 15:44:18 ハードウェアの時刻値 : 2015/06/27 15:44:18 = 1969 年から 1435419858 秒経過 Time since last adjustment is 1435419858 seconds Calculated Hardware Clock drift is 0.000000 seconds 2015年06月28日 00時44分17秒 .665140 秒
以下 /etc/adjtime=
の設定値を明示された場合 hwclock
の出力です。「*」部分が差分で
す。
|$ hwclock -r --debug |hwclock from util-linux 2.26.2 |クロックの /dev インターフェイス を使用中。 *|直前のズレの修正は、 1969 年から 0 秒経過した時点で行なわれました *|直前の調整は 1969 年から 0 秒経過した時点で行なわれました *|ハードウェアの時刻は UTC です |ハードウェアの時刻が UTC に設定されているものと仮定します。 |クロックティックを待っています... |...クロックティックを取得しました |ハードウェアの時計から読み込んだ時刻: 2015/06/27 16:05:20 |ハードウェアの時刻値 : 2015/06/27 16:05:20 = 1969 年から 1435421120 秒経過 |Time since last adjustment is 1435421120 seconds |Calculated Hardware Clock drift is 0.000000 seconds |2015年06月28日 01時05分19秒 .432450 秒
ファイル | 役割 |
---|---|
/etc/localtime | ローカル時刻(標準時)に適用するタイムゾーン情報 |
/etc/adjtime | ハードウェアクロック情報を保持するファイル、詳細について |
man hwclockのThe Adjust Functionセクションにて確認出来る | |
/dev/rtc | ハードウェアクロックデバイスファイル |
/dev/rtc0 | 同上 |
/dev/misc/rtc | 同上 |
以下 からの抜粋
windows はハードウェア クロックのタイムゾーンを、暗黙裡にローカル タイムであると認識 します。 Linux のようにタイムゾーンを選択(UTC or ローカルタイム)できる機能はありませ ん。 そのため windows を含むマルチ ブート環境では、ハードウェア クロックのタイムゾーンを UTCとすることは不可能(ローカルタイム固定)で、他のOSが windows の作法に合わせる必要が あります。
日本の場合、システムをWindowsモードで起動したとして、次回Linux起動した後のシステム時 刻がローカル時刻より9時間早まる。
回避方法: Windows で UTC を使う
関数名 | 機能概要 |
---|---|
gettimeofday | システム時刻を取得する |
settimeofday | システム時刻を設定する |
time | 秒単位の時間を得る |
strftime | 日付および時刻の文字列への変換 |
adjtimex | カーネルの時計を調整する |
clock | プログラムの使用したプロセッサ時間の近似値を返す |
asctime | 日付と時刻を要素別の時刻や ASCII に変換する |
ctime | |
gmtime | |
localtime | |
mktime | |
asctime_r | |
ctime_r | |
gmtime_r | |
localtime_r |
勉強メモ
JCPサイト JavaBeans
で検索したら ;-( EJBの仕様しかなかったで、JavaBeans APIで検索
するとヒットしました。
これがが一番わかり易いと思います。→ Oracle Javaロードマップ:JavaBeans
公式の仕様はここ → JavaBeans Spec
仕様変更履歴
仕様のバージョン | JDKバージョン | |
---|---|---|
1996 | 1.0.0 | JDK1.1 |
1997 | 1.0.1 |
JCP設立 されるまで策定された仕様なのでJSR番号が付いていないですね。 1997以降仕様更新あり?なし?分からない、掲示がないので恐らく大きな更新がないでしょう。
PDF版は114ページで結構のボリュームです。
JavaBeans Specより
The goal of the JavaBeans APIs is to define a software component model for Java, so that thirdparty ISVs can create and ship Java components that can be composed together into applica-tions by end users.
Javaで作成された移植可能なプラットフォームに依存しないコンポーネント・モデルで、 JavaBean仕様に従う。 再使用可能なコンポーネントを作成できる。
JavaBeans Specの2.1 What is a Bean?より
Let's start with an initial definition and then refine it: “A Java Bean is a reusable software component that can be manipulated visually in a builder tool.” This covers a wide range of different possibilities. The builder tools may include web page builders, visual application builders, GUI layout build- ers, or even server application builders. Sometimes the “builder tool” may simply be a docu- ment editor that is including some beans as part of a compound document. Some Java Beans may be simple GUI elements such as buttons and sliders. Other Java Beans may be sophisticated visual software components such as database viewers, or data feeds. Some Java Beans may have no GUI appearance of their own, but may still be composed togeth- er visually using an application builder. Some builder tools may operate entirely visually, allowing the direct plugging together of Java Beans. Other builders may enable users to conveniently write Java classes that interact with and control a set of beans. Other builders may provide a simple scripting language to allow easy high-level scripting of a set of beans. Individual Java Beans will vary in the functionality they support, but the typical unifying fea- tures that distinguish a Java Bean are: • Support for "introspection" so that a builder tool can analyze how a bean works • Support for "customization" so that when using an application builder a user can customize the appearance and behaviour of a bean. • Support for "events" as a simple communication metaphor than can be used to connectup beans. • Support for "properties", both for customization and for programmatic use. • Support for persistence, so that a bean can be customized in an application builder and then have its customized state saved away and reloaded later. A bean is not required to inherit from any particular base class or interface. Visible beans must inherit from java.awt.Component so that they can be added to visual containers, but invisible beans (see 2.7 below) aren’t required to do this. Note that while beans are primarily targeted at builder tools they are also entirely usable by hu- man programmers. All the key APIs such as events, properties, and persistence, have been de- signed to work well both for human programmers and for builder tools. Many beans will have a strong visual aspect, in both the application builder and in the final con- structed application, but while this is common it is not required
なんとなくAWT/SwingのGUI系コンポネント向けの仕様ですね。
現在はAWT/Swing/JSFなどMVCアーキテクチャのモデルとして利用することが多いいでしょう。
<tr:inputText valueChangeListener="#{myBean.valueChangeHandler}" value="#{myBean.value}"/>
※Oracle JDeveloperを使うとJavaBeans作成用の専用ウィザードが提供されている
4がピンとこないですね。下記の例で理解できると思います。
public class MyBean { private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener listener) { this.pcs.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { this.pcs.removePropertyChangeListener(listener); } private String value; public String getValue() { return this.value; } public void setValue(String newValue) { String oldValue = this.value; this.value = newValue; this.pcs.firePropertyChange("value", oldValue, newValue); } } public class MyBeanTest { static class MyBeanPropertyListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { System.out.println("evt.getPropertyName() = " + evt.getPropertyName()); System.out.println("evt.getSource() = " + evt.getSource()); System.out.println("evt.getOldValue() = " + evt.getOldValue()); System.out.println("evt.getNewValue() = " + evt.getNewValue()); } } public static void main(String[] args) { MyBean myBean = new MyBean(); myBean.addPropertyChangeListener(new MyBeanPropertyListener()); myBean.setValue("hello"); } }
MyBean
のvalue属性変更時にPropertyChangeEventを発火させ、MyBeanPropertyListenerでイ
ベントをハンドリングする。Web開発用MVCフレームワークでもよく利用されている。
JavaDoc より
パッケージ java.beans の説明 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaBeans™ アーキテクチャーに基づいたコンポーネントである Bean の開発に関連したクラス が含まれています。一部のクラスは、アプリケーションの実行中に Bean によって使用されま す。たとえば、イベントクラスは、プロパティーと拒否可能な変更イベントをトリガーする Bean によって使用されます (PropertyChangeEvent を参照)。しかし、このパッケージに含ま れるほとんどのクラスは、Bean エディタ (アプリケーションを作成するために Bean のカスタ マイズおよび組み合わせを行う開発環境) 用です。こうしたクラスを使用すれば、Bean エディ タで Bean のカスタマイズ用ユーザーインタフェースを簡単に作成できます。たとえば、Bean エディタでは処理できない特殊な型のプロパティーを持つ Bean があるとします。この場合、 Bean 開発者は PropertyEditor インタフェースを使って、この特殊な型のエディタを提供でき ます。 Bean による消費リソースを最小限に抑えるため、Bean エディタによって使用されるクラスが 読み込まれるのは、Bean の編集時だけとなります。アプリケーションで Bean が実行されてい るときは必要ないため、読み込まれません。この情報は、Bean 情報 (BeanInfo を参照) に保 管されます。 明示しない限り、null 値または空の文字列は、このパッケージのメソッドで有効なパラメータ ではありません。そのようなパラメータが使用されると、例外が発生する可能性があります。
イマイチですね、わかりづらい
PropertyDescriptor nameProp = new PropertyDescriptor("value", MyBean.class); System.out.println(nameProp.getReadMethod().invoke(myBean)); System.out.println(nameProp.getWriteMethod().invoke(myBean, "goodbye")); System.out.println(nameProp.getReadMethod().invoke(myBean));
URLパラメータの日本語文字が化けたので、APサーバのURLパラメータデコード処理について 調べました。
HTTPプロトコルのGETメソッドで通信する場合、URLパラメータをエンコーディングしなければ いけません。その仕様は rfc3986:Uniform Resource Identifier (URI): Generic Syntax にて 定義されている。
例えばブラウザに下記URLを入力して送信する。
http://www.yahoo.co.jp/?param1=あきら
パケットレベルで実際に送信された内容を確認するとURLに日本語パラメータ部分がパーセン トエンコーディングされていることが分かります。
GET http://www.yahoo.co.jp/?param1=%E3%81%82%E3%81%8D%E3%82%89 HTTP/1.1 Host: www.yahoo.co.jp User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ja,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate
param1=あきら
にマルチバイト部分が下記のイメージでエンコードされる。
+-------------------+-------------------+-------------------+ | あ | き | ら | +-----+------+------+-----+------+------+-----+------+------+ | -29 | -127 | -126 | -29 | -127 | -155 | -29 | -126 | -119 | ← ステップ1 +-----+------+------+-----+------+------+-----+------+------+ | 0xe3| 0x81 | 0x82 | 0xe3| 0x81 | 0x8d | 0xe3| 0x82 | 0x89 | ← ステップ2 +-----+------+------+-----+------+------+-----+------+------+ | %E3 | %81 | %82 | %E3 | %81 | %8D | %E3 | %82 | %89 | ← ステップ3 +-----+------+------+-----+------+------+-----+------+------+
表1
ステップ1の文字コード(UTF-8)はブラウザの実装/設定に依存する。一般にUTF-8を採用するこ とが多いい。
Javaに下記URLエンコード/デコード用の標準APIが用意されている。
表1
にエンコードの流れについて下記サンプルプログラムで確認することができる。
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; public class URLEncoderTest { public static void main(String[] args) throws UnsupportedEncodingException { System.out.println(URLEncoder1.encode("あきら", "UTF-8")); // UTF-8バイト表現を出力する byte[] bs = "あきら".getBytes("UTF-8"); for (byte b : bs) { System.out.print(b); System.out.print(" | "); } System.out.print("\n"); // 16進数表現を出力する for (byte b : bs) { System.out.println(Integer.toHexString(b)); } } }
出力結果
%E3%81%82%E3%81%8D%E3%82%89 -29 | -127 | -126 | -29 | -127 | -115 | -29 | -126 | -119 | ffffffe3 ffffff81 ffffff82 ffffffe3 ffffff81 ffffff8d ffffffe3 ffffff82 ffffff89
HTTPリクエストを受ける側(Web/APサーバ)、上記 表1
と逆順でURLパラメータをデコードし
なければいけません。URLパラメータのデコード処理はAPサーバを隠蔽してくれるので業務AP側
あんまり意識しないかもしれないですが。ステップ1でクライアントとAPサーバが異なる文字
コードを使用すると文字化けが起こりえるので要注意です。
URLパラメータの文字コードについて、クライアントとAPサーバ間のネゴシエーション仕様は HTTPプロトコル上明確に定義されていないため、ベンダによって実装が変わる。主に以下のよ うな処理パターンが存在するでしょう。
HTTPヘッダのContent-Type値はPOSTリクエストにHTTPボディ部分の文字コードを示す項目です が、GETリクエストにContent-Type値を含めることは稀に見ないでしょう。
環境: apache-tomcat-7.0.47
検証用サンプルプログラム profiler.MainServlet.java
package profiler; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class MainServlet */ @WebServlet("/MainServlet") public class MainServlet extends HttpServlet { private static final long serialVersionUID = 1L; public MainServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // URLパラメータを出力する System.out.println(request.getParameter("param1")); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
Debugモードで上記サンプルをトレースしてみると、URLパラメータデコード処理時の実行スタッ クは以下となります。
at org.apache.tomcat.util.buf.UDecoder.convert(UDecoder.java:100) at org.apache.tomcat.util.http.Parameters.urlDecode(Parameters.java:489) at org.apache.tomcat.util.http.Parameters.processParameters(Parameters.java:396) at org.apache.tomcat.util.http.Parameters.processParameters(Parameters.java:501) at org.apache.tomcat.util.http.Parameters.handleQueryParameters(Parameters.java:194) at org.apache.catalina.connector.Request.parseParameters(Request.java:3059) at org.apache.catalina.connector.Request.getParameter(Request.java:1151) at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:384) at profiler.MainServlet.doGet(MainServlet.java:32)
実行スタックから見ると javax.servlet.ServletRequest.getParameter(String name) が初回 呼ばれた時に次のJavaクラスが実行され、URLパラメータのデコード処理が行われることが分か ります。
※記述が冗長のため、以降はRequestとParametersで記述する。
以下はParameters.javaにURLパラメータの文字コード決める部分の抜粋です。
1 |package org.apache.tomcat.util.http; 2 |(中略) 3 |public final class Parameters { 4 | 5 | (中略) 6 | 7 | public void handleQueryParameters() { 8 | (中略) 14行目を呼び出し 9 | processParameters( decodedQuery, queryStringEncoding ); 10 | } 11 | 12 | (中略) 13 | 14 | public void processParameters( MessageBytes data, String encoding ) { 15 | (中略) 22行目を呼び出してから34行目を実行する 16 | processParameters( bc.getBytes(), bc.getOffset(), 17 | bc.getLength(), getCharset(encoding)); 18 | } 19 | 20 | (中略) 21 | 22 | private Charset getCharset(String encoding) { 23 | if (encoding == null) { 24 | return DEFAULT_CHARSET; // ISO-8859-1 25 | } 26 | try { 27 | return B2CConverter.getCharset(encoding); 28 | } catch (UnsupportedEncodingException e) { 29 | return DEFAULT_CHARSET; 30 | } 31 | } 32 | 33 | (中略) 34 | private void processParameters(byte bytes[], int start, int len, 35 | Charset charset) { 36 | 37 | if(log.isDebugEnabled()) { 38 | log.debug(sm.getString("parameters.bytes", 39 | new String(bytes, start, len, DEFAULT_CHARSET))); 40 | } 41 | 42 | (中略) 43 | String name; 44 | String value; 45 | 46 | // ★1 パラメータ名のデコード 47 | 48 | if (decodeName) { 49 | urlDecode(tmpName); 50 | } 51 | 52 | tmpName.setCharset(charset); 53 | name = tmpName.toString(); 54 | 55 | // ★2 パラメータ値のデコード 56 | 57 | if (valueStart >= 0) { 58 | if (decodeValue) { 59 | urlDecode(tmpValue); 60 | } 61 | 62 | tmpValue.setCharset(charset); 63 | value = tmpValue.toString(); 64 | } else { 65 | value = ""; 66 | } 67 | 68 | (中略) 69 | } 70 |}
62
行目でURLパラメータ値パーセントデコード後バイト表現の文字コードを指定しています。
ここのcharset値は次のメカニズムで決められる。
Parameters.javaのqueryStringEncodingフィールド値が設定された場合その値が適用される。
そうでない場合、デフォルト値 ISO-8859-1
が適用される。
Tomcat7にqueryStringEncodingフィールドの値を設定しているところは以下4箇所です。
1| org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:386) 2| org.apache.catalina.connector.Request.parseParameters(Request.java:3048) 3| org.apache.catalina.connector.Request.parseParameters(Request.java:3054) 4| org.apache.catalina.authenticator.FormAuthenticator.restoreRequest(FormAuthenticator.jaba:586)
conf/server.xml
にConnectorのURIEncoding属性値をRequestオブジェクトの
queryStringEncodingに設定する。実行時スタックは下記の通りです。
"http-bio-8080-exec-1" - Thread t@23 java.lang.Thread.State: RUNNABLE at org.apache.tomcat.util.http.Parameters.setQueryStringEncoding(Parameters.java:98) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:386) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) - locked <63f701a0> (a org.apache.tomcat.util.net.SocketWrapper) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
1 |package org.apache.catalina.connector; 2 |(中略) 3 |public class Request 4 | implements HttpServletRequest { 5 | 6 | (中略) 7 | 8 | /** 9 | * Parse request parameters. 10 | */ 11 | protected void parseParameters() { 12 | 13 | parametersParsed = true; 14 | 15 | Parameters parameters = coyoteRequest.getParameters(); 16 | boolean success = false; 17 | try { 18 | // Set this every time in case limit has been changed via JMX 19 | parameters.setLimit(getConnector().getMaxParameterCount()); 20 | 21 | // getCharacterEncoding() may have been overridden to search for 22 | // hidden form field containing request encoding 23 | 24 | // ★1 25 | // org.apache.coyote.Request.javaのcharEncodingフィールド値、 26 | // もしくはHTTPヘッダのContent-Type値から文字コード情報の取得 27 | String enc = getCharacterEncoding(); 28 | 29 | // ★2 30 | // server.xmlにConnectorのuseBodyEncodingForURI属性値の取得 31 | boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); 32 | 33 | 34 | if (enc != null) { 35 | // ★3 36 | // HTTPヘッダのContent-Type値が指定された場合、適用する 37 | parameters.setEncoding(enc); 38 | 39 | // ★4 40 | // useBodyEncodingForURI属性値がtrueの場合、 41 | // 「★1」で取れた値をorg.apache.tomcat.util.http.Parameters.javaの 42 | // queryStringEncodingフィールドに適用する 43 | if (useBodyEncodingForURI) { 44 | parameters.setQueryStringEncoding(enc); 45 | } 46 | } else { 47 | 48 | // ★5 49 | // 「★1」値が取れない場合、デフォルト値(ISO-8859-1)を適用する 50 | 51 | parameters.setEncoding 52 | (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); 53 | if (useBodyEncodingForURI) { 54 | parameters.setQueryStringEncoding 55 | (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); 56 | } 57 | } 58 | // org.apache.tomcat.util.http.Parameters.javaの解析処理をコール 59 | parameters.handleQueryParameters(); 60 | 61 | (中略)
リスト1
profiler.MainServlet.java
サンプルプログラムに置いて、上記 27
行目実行時のスタッ
クトレースを以下に示す。
"http-bio-8080-exec-1" - Thread t@29 java.lang.Thread.State: RUNNABLE at org.apache.tomcat.util.http.ContentType.getCharsetFromContentType(ContentType.java:41) at org.apache.coyote.Request.getCharacterEncoding(Request.java:264) at org.apache.catalina.connector.Request.getCharacterEncoding(Request.java:1048) at org.apache.catalina.connector.Request.parseParameters(Request.java:3042) at org.apache.catalina.connector.Request.getParameter(Request.java:1151) at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:384) at profiler.MainServlet.doGet(MainServlet.java:32)
conf/server.xml
にConnectorのURIEncoding属性値にてURLパラメータの文字コードのデフォ
ルト値を指定することができる。ほどんどんのブラウザはUTF-8を採用しているため、この値を
UTF-8
に見直すべきでしょう。
conf/server.xml
にConnectorのuseBodyEncodingForURI属性値がtrueの場合、 リスト1
の
44
と 55
行目が実行され、URLパラメータの文字コードとHTTPボディの文字コードが統一
される。
Content-Type HTTPヘッダを含めないGETリクエストの場合 27
行目の値が null のため、URL
パラメータにマルチバイト文字が化けでしょう。
ServletRequest.getParameter(String name)を評価する前に
ServletRequest.setCharacterEncoding(String enc)
で予め
org.apache.coyote.Request.javaのcharEncodingフィールド値を初期化すれば、文字化け問題
を回避することが出来ます。
と言うわけで useBodyEncodingForURI
属性値をtrueに指定する場合、業務APでURLパラメー
タを参照する前に ServletRequest.setCharacterEncoding(String enc)
を実行して文字コー
ド明示した方がよいでしょう。
No | URIEncoding | useBodyEncodingForURI | Content-Type | 結果 |
---|---|---|---|---|
1 | - | false | 有り/無し | 文字化け |
4 | UTF-8 | false | 有り/無し | OK |
3 | 任意 | true | なし | 文字化け |
2 | 任意 | true | 有り | OK |
Tomcat6の場合、この2つパラメータは次のシステムプロパティとして指定することになります。
JBossAS7/EAP6もTomcat6と同じ方法です。
-Dweblogic.http.URIDecodeEncoding=UTF-8
ただし、この設定は1つのサーバインスタンスで1つのみ可能です。
Mapping IANA Character Sets to Java Character Sets
<input-charset> <resource-path>/foo/*</resource-path> <java-charset-name>UTF-8</java-charset-name> </input-charset>
文字コード制御用のFilterにて実装することが多いい。
ADFフレームワーク内でHTTP Requestオブジェクトをラッピング時にFilterで指定した文字コー ド情報をロストしてしまうケースがあります。ADFフレームワーク利用時にJSFフェースリスナー のRESTORE_VIEWフェーズにて行うのが適切です。
今まで、JVM中身の調査は SystemTap + java-1.x.x-openjdk-debuginfo.x86_64
利用してい
たが。もう少しJVMの中身を踏み込みたいのでデバッグ版JVMをビルドしてみました。
http://hg.openjdk.java.net/jdk7/jdk7/raw-file/tip/README-builds.html の手順でビルドし
てもいいのですが、トライ・アンド・エラーで時間が取られそうなので、自分が使っている
Arch Linux
環境で一番手取りの早い手順で行いました。
まずはパッケージリポジトリから jdk7-openjdk のビルドファイルやパッチファイルを入手する。
★ダウンロード $ wget https://projects.archlinux.org/svntogit/packages.git/plain/trunk/PKGBUILD?h=packages/java7-openjdk -O PKGBUILD $ wget https://projects.archlinux.org/svntogit/packages.git/plain/trunk/fontconfig-paths.diff?h=packages/java7-openjdk -O fontconfig-paths.diff $ wget https://projects.archlinux.org/svntogit/packages.git/plain/trunk/jdk7-openjdk.install?h=packages/java7-openjdk -O jdk7-openjdk.install $ wget https://projects.archlinux.org/svntogit/packages.git/plain/trunk/jre7-openjdk-headless.install?h=packages/java7-openjdk -O jre7-openjdk-headless.install $ wget https://projects.archlinux.org/svntogit/packages.git/plain/trunk/jre7-openjdk.install?h=packages/java7-openjdk -O jre7-openjdk.install $ wget https://projects.archlinux.org/svntogit/packages.git/plain/trunk/openjdk7_nonreparenting-wm.diff?h=packages/java7-openjdk -O openjdk7_nonreparenting-wm.diff ★ファイル一覧確認 $ ls -al 合計 52 drwxr-xr-x 2 akira users 4096 5月 10 11:24 . drwxr-xr-x 21 akira users 4096 5月 10 11:23 .. -rw-r--r-- 1 akira users 12429 5月 10 11:24 PKGBUILD -rw-r--r-- 1 akira users 8302 5月 10 11:24 fontconfig-paths.diff -rw-r--r-- 1 akira users 1053 5月 10 11:24 jdk7-openjdk.install -rw-r--r-- 1 akira users 974 5月 10 11:24 jre7-openjdk-headless.install -rw-r--r-- 1 akira users 1201 5月 10 11:24 jre7-openjdk.install -rw-r--r-- 1 akira users 2324 5月 10 11:24 openjdk7_nonreparenting-wm.diff
PKGBUILD
ファイルにデバッグビルドオプションを有効化する。
--enable-native-debuginfo=yes
build with native code debuginfo [default=yes]
--enable-java-debuginfo=yes
build with Java bytecode debuginfo [default=yes]
make icedtea-debug
デバッグビルドターゲットに変える
1
と 2
の規定値は yes
なので、指定しなくても問題ありません。 3
が肝ですね。
..........省略......... |build() { | cd "${srcdir}/icedtea-${_icedtea_ver}" | | export ALT_PARALLEL_COMPILE_JOBS="${MAKEFLAGS/-j}" | export HOTSPOT_BUILD_JOBS="${ALT_PARALLEL_COMPILE_JOBS}" | | . /etc/profile.d/apache-ant.sh | | cp "${srcdir}"/*.diff "${srcdir}"/icedtea-${_icedtea_ver}/patches | export DISTRIBUTION_PATCHES="patches/fontconfig-paths.diff \ | patches/openjdk7_nonreparenting-wm.diff" | | if [ "$_bootstrap" = "1" ]; then | BOOTSTRAPOPT="--enable-bootstrap --with-ecj-jar=/usr/share/java/ecj.jar" | else | BOOTSTRAPOPT="--disable-bootstrap" | fi | | ./configure \ | ${BOOTSTRAPOPT} \ | --with-parallel-jobs="${MAKEFLAGS/-j}" \ | --disable-tests \ | --disable-downloading --disable-Werror \ | --with-pkgversion="Arch Linux build ${pkgver}-${pkgrel}-${CARCH}" \ | --with-jdk-home=${JAVA_HOME} \ | --with-openjdk-src-zip="${srcdir}/icedtea_${_icedtea_ver}_openjdk.tar.bz2" \ | --with-hotspot-src-zip="${srcdir}/icedtea_${_icedtea_ver}_hotspot.tar.bz2" \ | --with-corba-src-zip="${srcdir}/icedtea_${_icedtea_ver}_corba.tar.bz2" \ | --with-jaxp-src-zip="${srcdir}/icedtea_${_icedtea_ver}_jaxp.tar.bz2" \ | --with-jaxws-src-zip="${srcdir}/icedtea_${_icedtea_ver}_jaxws.tar.bz2" \ | --with-jdk-src-zip="${srcdir}/icedtea_${_icedtea_ver}_jdk.tar.bz2" \ | --with-langtools-src-zip="${srcdir}/icedtea_${_icedtea_ver}_langtools.tar.bz2" \ | --enable-nss \ | --with-rhino \ | --with-abs-install-dir=${_jvmdir} \ 1.| --enable-native-debuginfo=yes \ 2.| --enable-java-debuginfo=yes \ | --enable-infinality=no | # TODO latest version of openjdk will disable infinality by default | 3.| make icedtea-debug |} ..........省略.........
あとは makepkg でビルドするだけです。コーヒーいっぱい分の時間かかります。
$ makepkg ==> パッケージを作成: java7-openjdk 7.u79_2.5.5-1 (2015年 5月 10日 日曜日 11:43:40 JST) ==> ランタイムの依存関係を確認... ==> ビルドタイムの依存関係を確認... ==> ソースを取得... ..........省略......... ★肝心のhotspotビルド ######################################################################## ##### Entering hotspot for target(s) all_debug ##### ######################################################################## ..........省略......... ★ビルド時のオプション g++ -DLINUX -D_GNU_SOURCE -DAMD64 -DASSERT -DDEBUG -I. -I/home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/vm/prims -I/home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/vm -I/home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/vm/precompiled -I/home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/cpu/x86/vm -I/home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/os_cpu/linux_x86/vm -I/home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/os/linux/vm -I/home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/os/posix/vm -I../generated -DHOTSPOT_RELEASE_VERSION="\"24.79-b02\"" -DHOTSPOT_BUILD_TARGET="\"jvmg\"" -DHOTSPOT_BUILD_USER="\"akira\"" -DHOTSPOT_LIB_ARCH=\"amd64\" -DHOTSPOT_VM_DISTRO="\"OpenJDK\"" -DDERIVATIVE_ID="\"IcedTea 2.5.5\"" -DDISTRIBUTION_ID="\"Arch Linux, package Arch Linux build 7.u79_2.5.5-1-x86_64\"" -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector-strong --param=ssp-buffer-size=4 -DTARGET_OS_FAMILY_linux -DTARGET_ARCH_x86 -DTARGET_ARCH_MODEL_x86_64 -DTARGET_OS_ARCH_linux_x86 -DTARGET_OS_ARCH_MODEL_linux_x86_64 -DTARGET_COMPILER_gcc -DCOMPILER2 -DCOMPILER1 -fno-rtti -fno-exceptions -D_REENTRANT -fcheck-new -fvisibility=hidden -m64 -pipe -g -DTARGET_OS_FAMILY_linux -DTARGET_ARCH_x86 -DTARGET_ARCH_MODEL_x86_64 -DTARGET_OS_ARCH_linux_x86 -DTARGET_OS_ARCH_MODEL_linux_x86_64 -DTARGET_COMPILER_gcc -DCOMPILER2 -DCOMPILER1 -fpic -fno-rtti -fno-exceptions -D_REENTRANT -fcheck-new -fvisibility=hidden -m64 -pipe ★カスタマイズdebugビルドフラグ -g -finstrument-functions -fvar-tracking-assignments -rdynamic -D_NMT_NOINLINE_ -DVM_LITTLE_ENDIAN -D_LP64=1 -fno-omit-frame-pointer -DINCLUDE_TRACE=1 -Wpointer-arith -Wsign-compare -c -fpch -Deps -MMD -MP -MF ../generated/dependencies/osThread_linux.o.d -o osThread_linux.o /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/os/linux/vm/osThread_linux.cpp Compiling /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/os/linux/vm/os_linux.cpp ..........省略......... -- Build times ---------- Target all_product_build Start 2015-05-10 11:43:19 End 2015-05-10 12:41:39 00:09:54 corba 00:11:03 hotspot 00:01:15 jaxp 00:01:25 jaxws 00:31:45 jdk 00:02:58 langtools 00:58:20 TOTAL ------------------------- ..........省略.........
僕の環境では約1時間ぐらいかかりました。
ビルド成果物はワークディレクトリの下記場所に出力される。
デバッグ版OpenJDK | src/icedtea-2.5.5/openjdk.build-debug |
hotspot単体 | src/icedtea-2.5.5/openjdk.build-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg |
-gdb
オプションを付けて ./hotspot
を起動する
p
で変数の値を確認する
next
でステップオーバーでデバッグする
step
でステップインでデバッグする
backtrace
でスタックトレースを確認する
continue
で最後まで実行される
1.|$ cd src/icedtea-2.5.5/openjdk.build-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg 2.|$ ./hotspot -gdb -version |GNU gdb (GDB) 7.9 |Copyright (C) 2015 Free Software Foundation, Inc. |License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> |This is free software: you are free to change and redistribute it. |There is NO WARRANTY, to the extent permitted by law. Type "show copying" |and "show warranty" for details. |This GDB was configured as "x86_64-unknown-linux-gnu". |Type "show configuration" for configuration details. |For bug reporting instructions, please see: |<http://www.gnu.org/software/gdb/bugs/>. |Find the GDB manual and other documentation resources online at: |<http://www.gnu.org/software/gdb/documentation/>. |For help, type "help". |Type "apropos word" to search for commands related to "word". |Breakpoint 1 at 0x4042f7: file /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/tools/launcher/java.c, line 1270. |[Thread debugging using libthread_db enabled] |Using host libthread_db library "/usr/lib/libthread_db.so.1". |Using java runtime at: /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/bootstrap/jdk1.6.0/jre |[New Thread 0x7ffff7fa6700 (LWP 15629)] |[Switching to Thread 0x7ffff7fa6700 (LWP 15629)] | 3.|Breakpoint 1, InitializeJVM (pvm=0x7ffff7fa5e38, penv=0x7ffff7fa5e30, ifn=0x7ffff7fa5e40) | at /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/tools/launcher/java.c:1270 |1270 memset(&args, 0, sizeof(args)); 4.|(gdb) p args |$1 = {version = 0, nOptions = 0, options = 0x0, ignoreUnrecognized = 0 '\000'} 5.|(gdb) next |1271 args.version = JNI_VERSION_1_2; |(gdb) next |1272 args.nOptions = numOptions; |(gdb) next |1273 args.options = options; |(gdb) next |1274 args.ignoreUnrecognized = JNI_FALSE; |(gdb) next |1276 if (_launcher_debug) { |(gdb) next |1288 r = ifn->CreateJavaVM(pvm, (void **)penv, &args); 6.|(gdb) step |JNI_CreateJavaVM (vm=0x7ffff7fa5e38, penv=0x7ffff7fa5e30, args=0x7ffff7fa5df0) | at /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/vm/prims/jni.cpp:5121 |5121 if (Atomic::xchg(1, &vm_created) == 1) { 7.|(gdb) backtrace |#0 JNI_CreateJavaVM (vm=0x7ffff7fa5e38, penv=0x7ffff7fa5e30, args=0x7ffff7fa5df0) | at /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/vm/prims/jni.cpp:5121 |#1 0x00000000004043fd in InitializeJVM (pvm=0x7ffff7fa5e38, penv=0x7ffff7fa5e30, ifn=0x7ffff7fa5e40) | at /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/tools/launcher/java.c:1288 |#2 0x00000000004031ea in JavaMain (_args=0x7fffffffc080) | at /home/akira/temp/java7-openjdk/src/icedtea-2.5.5/openjdk/hotspot/src/share/tools/launcher/java.c:423 |#3 0x00007ffff5c17374 in start_thread () from /usr/lib/libpthread.so.0 |#4 0x00007ffff595527d in clone () from /usr/lib/libc.so.6 8.|(gdb) continue |Continuing. |java version "1.7.0_79" |OpenJDK Runtime Environment (IcedTea 2.5.5) (Arch Linux build 7.u79_2.5.5-1-x86_64) |OpenJDK 64-Bit Server VM (build 24.79-b02-jvmg, mixed mode) |[Thread 0x7ffff7fa6700 (LWP 16481) exited] [Thread 0x7ffff7fa8740 (LWP 16477) exited] [Inferior 1 (process 16477) exited normally] (gdb) q $
valgrindでコールグラフを出してみました。
必要なパッケージを入れておく。
$ sudo pacman -S valgrind $ sudo pacman -S kdesdk-kcachegrind
valgrind
カーバーして java -version
を実行する
kcachegrind
で出力結果を解析する
1.|$ cd src/icedtea-2.5.5/openjdk.build-debug 2.|$ valgrind --tool=callgrind bin/java -version |==18306== Callgrind, a call-graph generating cache profiler |==18306== Copyright (C) 2002-2013, and GNU GPL'd, by Josef Weidendorfer et al. |==18306== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info |==18306== Command: bin/java -version |==18306== |==18306== For interactive control, run 'callgrind_control -h'. |java version "1.7.0_79-debug" |OpenJDK Runtime Environment (IcedTea 2.5.5) (Arch Linux build 7.u79_2.5.5-1-x86_64) |OpenJDK 64-Bit Server VM (build 24.79-b02-jvmg, mixed mode) |==18306== |==18306== Events : Ir |==18306== Collected : 1610140735 |==18306== |==18306== I refs: 1,610,140,735 |$ ls -al callgrind.out.* |-rw------- 1 akira users 5003407 5月 10 12:50 callgrind.out.18306 3.|$ kcachegrind callgrind.out.18306
コールグラフ
バッチリですね!
本記事の内容は以下環境を前提としています。
-Xss
、 -XX:ThreadStackSize
パラメータ値と ulimit -s
リソースリミット制限値を混
乱している記事を見受けたため、HotSpotの中身を調べることにしました。
結論を先に、
ulimit -s
のスタック最大サイズ制限値は親プロセスであるJVMランチャーのみ適用される。-Xss
か
-XX:ThreadStackSize
の値が適用される。-Xss
パラメータの
み制御できる、つまり -Xss
の適用範囲は -XX:ThreadStackSize
より広い
JVMスタックに関して、公式のJVMスペックドキュメント (Java SE 7 Virtual Machine Specification) は次のように記載されています。
2.5.2. Java Virtual Machine Stacks
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
※メモ: VMスタック内のFrameはヒープ上に配置されるかも知れない。
An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
仕様上ではJVMに Java Stack
と Native Stack
2種類のスタックメモリが定義されています。
スタック種別 | 説明 |
---|---|
Java Stack | Javaコード実行時に使われるスタック |
Native Stack | C/C++で書かれた部分実行時に使われるスタック |
システムコール、JNI経由でC/C++ライブラリコール時に使われる |
以下は、JVMのメモリ論理構成イメージです。
+----------------+--------------+-------------+------------------------------------------------------+ | | | | +-----------------+ +----------+ +-------------+ | | Heap | PermGen | Code Cache | | Program Counter | |JavaStack | |Native Stack | | | | | | +-----------------+ | | | | | +----------------+--------------+-------------+ +----------+ +-------------+ | | | Frame #2 | | | | | +----------+ +-------------+ | | << Thread >> | Frame #1 | | | | | +----------+ +-------------+ | +------------------------------------------------------+
図1
理論上は Java Stack
と Native Stack
がスレッド毎に領域が確保されいます。ただし、
実際のメモリページ構成はJDKの実装に依存するものです。
次の情報によると、HotSpotの実装は Java Stack
と Native Stack
が同じメモリ領域を共
有してる。
Troubleshooting Guide for HotSpot VM の 4.1.3 Crash due to Stack Overflow
In the HotSpot implementation, Java methods share stack frames with C/C++ native ★~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ code, namely user native code and the virtual machine itself. Java methods generate code that checks that stack space is available a fixed distance towards the end of the stack so that the native code can be called without exceeding the stack space. This distance towards the end of the stack is called “Shadow Pages.” The size of the shadow pages is between 3 and 20 pages, depending on the platform. This distance is tunable, so that applications with native code needing more than the default distance can increase the shadow page size. The option to increase shadow pages is -XX:StackShadowPages=n, where n is greater than the default stack shadow pages for the platform.
OpenJDKの開発メーリングリストから拾った内容
The stock HotSpot VM (the one in Oracle's Java SE JDK and OpenJDK) uses the same stack for Java and native methods for a Java thread; Java frames and native frames can be mixed together in such a stack. -Xss/-XX:ThreadStackSize controls the whole stack's size for Java threads.
情報源: What the difference between -Xss and -XX:ThreadStackSize is?
HotSpotの実装から見るとJVMから起動されたJavaスレッドのスタックページは次の形で構成さ れると思います。VM内部スレッドやJITコンパイルスレッドのページ構成はまだ別です。
--+-- +------------------------+ | /| |\ | / | StackRedPages | -XX:StackRedPages=1(4Kb) | / | |/ | HotSpot Guard Pages-- +------------------------+ | \ | |\ | \ | StackYellowPages | -XX:StackYellowPages=2(8Kb) | \| |/ | +------------------------+ | /| |\ ★Native Stackはここです★ | / | StackShadowPages | -XX:StackShadowPages=20(80Kb) -XX:ThreadStackSize / | |/ | / +------------------------+ | / | |\ | / | +----------------+ | \ | Normal Stack-- | | Frame | | \ | \ | +----------------+ | \ | \ | | Frame | | ★Java Stackはここです★ | \ | +----------------+ | / | \ | | Frame | | / | \ | +----------------+ | / | \| |/ --+-- +------------------------+
図2
以下はHotSpotのソースコードのコメントに書かれたスタックページ構成図です。
jdk7:hotspot/src/os_cpu/linux_x86/vm/os_linux_x86.cpp
// Java thread: // // Low memory addresses // +------------------------+ // | |\ JavaThread created by VM does not have glibc // | glibc guard page | - guard, attached Java thread usually has // | |/ 1 page glibc guard. // P1 +------------------------+ Thread::stack_base() - Thread::stack_size() // | |\ // | HotSpot Guard Pages | - red and yellow pages // | |/ // +------------------------+ JavaThread::stack_yellow_zone_base() // | |\ // | Normal Stack | - // | |/ // P2 +------------------------+ Thread::stack_base() // // Non-Java thread: // // Low memory addresses // +------------------------+ // | |\ // | glibc guard page | - usually 1 page // | |/ // P1 +------------------------+ Thread::stack_base() - Thread::stack_size() // | |\ // | Normal Stack | - // | |/ // P2 +------------------------+ Thread::stack_base() // // ** P1 (aka bottom) and size ( P2 = P1 - size) are the address and stack size returned from // pthread_attr_getstack()
図3
図の内容によるとJavaスレッドと非Javaスレッドのスタックページ構成が異なる。 以下はJVMの非Javaスレッドのリストです。
スレッド名 | 説明 |
---|---|
VM thread | JVM自身のコアスレッド |
Periodic task thread | WatcherThreadのシングルトンインスタンス、定義的なVMタスクを実行する |
GC threads | その名の通りです、メモリ管理自動化役を務める |
Compiler threads | ByteCodeからアセンブラにコンパイルするスレッド |
Signal dispatcher thread | 外部からシグナルをハンドリングする役を務める |
図3の各領域についてソースコードを見ながら解説していきます。
glibc guard page
はスタックポインタのオーバーフローを防ぐための-ガードページ。Java
スレッドには HotSpot Guard Pages
が別途用意されているため、この領域のサイズが0であ
る。非Javaスレッドはスタック頂上位置に1ページ分のガードページが割り当てられる。以下は
その実装内容です。
スレッド作成時にglibcの pthread_attr_setguardsize 関数にてガードページを作成してい る
jdk7/hotspot/src/os/linux/vm/os_linux.cpp#l923
// Thread start routine for all newly created threads static void *java_start(Thread *thread) { .............. // glibc guard page pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type)); .............. }
スレッド種別によってガードページのサイズを決める
jdk7/hotspot/src/os_cpu/linux_x86/vm/os_linux_x86.cpp#l662
size_t os::Linux::default_guard_size(os::ThreadType thr_type) { // Creating guard page is very expensive. Java thread has HotSpot // guard page, only enable glibc guard page for non-Java threads. return (thr_type == java_thread ? 0 : page_size()); }
glibc guard page
の詳細について以下の情報が参考になると思います。
Javaスレッドスタックオーバーフローを検出するために書き込み不可の HotSpot Guard
Pages
領域がスタック領域のトップ位置に設けている。また HotSpot Guard Pages
は
StackYellowPages
と StackRedPages
から構成されている。
StackYellowPages
はスタックオーバーフローの緩衝域として、余分のメモリーを割り当てま
す。 スタックポインターが StackRedPages
まで行くとStackOverflowErrorが起きる。
以下はページの構成イメージです。
+------------------------+ /| |\ HotSpot / | StackRedPages | - 1ページ (4Kb) Guard / | |/ Pages +------------------------+ \ | |\ \ | StackYellowPages | - 2ページ (8Kb) \| |/ +------------------------+ | | | Normal Stack | | | +------------------------+
図4
Linux/x86_64環境に置いて、 StackYellowPages
と StackRedPages
の初期値が2と1である。
それぞれの値は -XX:StackYellowPages
と -XX:StackRedPages
パラメータにて変更するこ
とが可能です。
下記は HotSpot Guard Pages
の割当処理ロジックです。
Javaスレッド起動時のガードページ割当位置やサイズの計算処理
jdk7:openjdk/hotspot/src/share/vm/runtime/thread.cpp
void JavaThread::create_stack_guard_pages() { if (! os::uses_stack_guard_pages() || _stack_guard_state != stack_guard_unused) return; // ★ ガードページの位置とサイズの計算 address low_addr = stack_base() - stack_size(); size_t len = (StackYellowPages + StackRedPages) * os::vm_page_size(); // ★ ガードページ割当処理はプラットフォーム依存のため、別関数をコール int allocate = os::allocate_stack_guard_pages(); // warning("Guarding at " PTR_FORMAT " for len " SIZE_FORMAT "\n", low_addr, len); if (allocate && !os::create_stack_guard_pages((char *) low_addr, len)) { warning("Attempt to allocate stack guard pages failed."); return; } if (os::guard_memory((char *) low_addr, len)) { _stack_guard_state = stack_guard_enabled; } else { warning("Attempt to protect stack guard pages failed."); if (os::uncommit_memory((char *) low_addr, len)) { warning("Attempt to deallocate stack guard pages failed."); } } }
ガードページ割当処理
jdk7u60:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
bool os::pd_create_stack_guard_pages(char* addr, size_t size) { if (os::Linux::is_initial_thread()) { // As we manually grow stack up to bottom inside create_attached_thread(), // it's likely that os::Linux::initial_thread_stack_bottom is mapped and // we don't need to do anything special. // Check it first, before calling heavy function. uintptr_t stack_extent = (uintptr_t) os::Linux::initial_thread_stack_bottom(); unsigned char vec[1]; if (mincore((address)stack_extent, os::vm_page_size(), vec) == -1) { // Fallback to slow path on all errors, including EAGAIN stack_extent = (uintptr_t) get_stack_commited_bottom( os::Linux::initial_thread_stack_bottom(), (size_t)addr - stack_extent); } if (stack_extent < (uintptr_t)addr) { ::munmap((void*)stack_extent, (uintptr_t)(addr - stack_extent)); } } // ★ここから mmapシステムコールが発行される。 // 最後の引数に書き込み不可のフラグが付与された return os::commit_memory(addr, size, !ExecMem); }
以下は JBoss AS7
アプリケーションサーバ実行時にワーカスレッドのスタック仮想メモリ割
当状況です。
$ cat /proc/`ps -ef | grep [j]boss.modules.system | awk '{print $2}'`/smaps ...省略... 7ff751076000-7ff751079000 ---p 00000000 00:00 0 Size: 12 kB ★説明: StackRedPages(4Kb) + StackYellowPages(8Kb) = 12Kb Rss: 0 kB Pss: 0 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 0 kB Referenced: 0 kB Anonymous: 0 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB VmFlags: mr mw me ac ★説明:書き込み不可 7ff751079000-7ff751177000 rw-p 00000000 00:00 0 [stack:21275] Size: 1016 kB ★説明: ここからNormal Stackページ Rss: 108 kB Pss: 108 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 108 kB Referenced: 108 kB Anonymous: 108 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB VmFlags: rd wr mr mw me ac ...省略...
次のSystemTapスクリプトで HotSpot Guard Pages
の割当処理をトレースしてみた。
jvm_memory_trace.stp
#!/usr/bin/stap -p4 probe process("/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so").function("commit_memory_impl") { printf("---------------------------------------------------------\n") printf("%d\t%s\n", tid(), $$parms) print_ustack(ubacktrace()) }
出力結果
|$ stap jvm_memory_trace.stp -c "java -version" |Using a compile server. |WARNING: Missing unwind data for module, rerun with 'stap -d ...dk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/jli/libjli.so' |WARNING: Missing unwind data for module, rerun with 'stap -d /usr/lib64/libpthread-2.17.so' |java version "1.7.0_75" |OpenJDK Runtime Environment (rhel-2.5.4.7.el7_1-x86_64 u75-b13) |OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode) | |★省略★ |-------------------------------------------------------------------------------------------------------------- ①|12179 exec=0x0 size=0x3000 addr=0x7f1d05b57000 ★commit_memory_impl関数実行時の引数情報 | 0x7f1d04808371 : _ZN2os16pd_commit_memoryEPcmb+0x1/0xf0 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d04802dee : _ZN2os13commit_memoryEPcmb+0x2e/0xd0 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] ②| 0x7f1d048092df : _ZN2os27pd_create_stack_guard_pagesEPcm+0x7f/0x180 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d04945519 : _ZN7Threads9create_vmEP14JavaVMInitArgsPb+0x339/0x1550 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d0463fca7 : JNI_CreateJavaVM+0x67/0x2a0 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d0562aa68 : 0x7f1d0562aa68 [...dk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/jli/libjli.so+0x2a68/0x20e000] |-------------------------------------------------------------------------------------------------------------- |★以降省略★
出力結果に①の commit_memory_impl 仮想メモリ割当処理の第2引数にメモリサイズを指定し
ています。 size=0x3000
の値が16進数ですので、10進数に変換すると12Kbです。予測通りで
すね。
gdbを用いて上記出力結果から②のソースコード位置を特定する方法を以下に示す。
$ gdb /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-64.el7 ★一部内容省略★ Reading symbols from /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so...Reading symbols from /usr/lib/debug/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so.debug...done. done. ★出力結果から関数名ぽいの文字列で関数を探す (gdb) info functions pd_create_stack_guard_pages All functions matching regular expression "pd_create_stack_guard_pages": ★検索結果 File /usr/src/debug/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/openjdk/hotspot/src/os/linux/vm/os_linux.cpp: bool os::pd_create_stack_guard_pages(char*, unsigned long); (gdb)
Javaスレッドを前提に置いて、 Normal Stack
には通常Javaメソッド実行時のフレーム情報
が格納される。ただし、スレッドからC/C++メソッドを実行する時も Normal Stack
が利用さ
れる。スタックのトップ位置にC/C++メソッド実行用の StackShadowPages
が設けられている。
Linux/x86_64環境に置いて StackShadowPages
の初期値が20である。
+------------------------+ /| |\ / | StackShadowPages | -XX:StackShadowPages=20(80Kb) / | |/ ★Native Stackはここです! / +------------------------+ / | |\ / | +----------------+ | \ Normal Stack | | Frame | | \ \ | +----------------+ | \ \ | | Frame | | - ★Java Stackはここです! \ | +----------------+ | / \ | | Frame | | / \ | +----------------+ | / \| |/ +------------------------+
図5
以下は StackShadowPages
初期値の代入処理ロジックです。
jdk7:hotspot/src/cpu/x86/vm/globals_x86.hpp#l60
#ifdef AMD64 // Very large C++ stack frames using solaris-amd64 optimized builds // due to lack of optimization caused by C++ compiler bugs define_pd_global(intx, StackShadowPages, NOT_WIN64(20) WIN64_ONLY(6) DEBUG_ONLY(+2)); #else define_pd_global(intx, StackShadowPages, 6 DEBUG_ONLY(+5)); #endif // AMD64
StackShadowPages
に関して以下の情報が参考になると思います。
What does the StackShadowPages JVM setting do?
StackShadowPages reserves a portion of the thread stack for native layer allocations. The page size usually is 4096b, which mean that 20 pages would occupy 80Kb. The thread stack is sized through -Xss. Consider some config examples: -Xss1024k -XX:StackShadowPages=10 [ 984kb java stack | 40kb native stack] -Xss1024k -XX:StackShadowPages=20 [ 944kb java stack | 80kb native stack] -Xss512k -XX:StackShadowPages=10 [ 472kb java stack | 40kb native stack] If you decrease just -Xss, the overall stack is decreased, but the StackShadowPages native reservation is not; only the java portion would lose space. Likewise if you only increase -Xss, only the java portion gains space with the increased stack. If you increase StackShadowPages, the java portion becomes smaller so that the native portion can be larger. If the native portion of a stack is exhausted, the JVM can fatally crash so sometimes StackShadowPages needs to be increased.
従いましてJava Methodに使えるスタック領域(Java Stack)のサイズは次の式で計算出来る。
Java Satck Size = Thread::stack_size() - ((StackRedPages + StackYellowPages + StackShadowPages) * PageSize)
Thread::stack_size()
の値はスレッド起動時にglibcの pthread_attr_setstacksize 関数を
用いて設定される。以下はHotSpotの実装です。
JVM起動時に実行される処理 jdk7/hotspot/src/os/linux/vm/os_linux.cpp#4820
// this is called _after_ the global arguments have been parsed jint os::init_2(void) { // ★一部省略★ // ★スレッドに割当るスタックサイズの最小許容値の計算 // Check minimum allowable stack size for thread creation and to initialize // the java system classes, including StackOverflowError - depends on page // size. Add a page for compiler2 recursion in main thread. // Add in 2*BytesPerWord times page size to account for VM stack during // class initialization depending on 32 or 64 bit VM. os::Linux::min_stack_allowed = MAX2(os::Linux::min_stack_allowed, (size_t)(StackYellowPages+StackRedPages+StackShadowPages) * Linux::page_size() + (2*BytesPerWord COMPILER2_PRESENT(+1)) * Linux::vm_default_page_size()); #ifdef ZERO // If this is Zero, allow at the very minimum one page each for the // Zero stack and the native stack. This won't make any difference // for 4k pages, but is significant for large pages. os::Linux::min_stack_allowed = MAX2(os::Linux::min_stack_allowed, (size_t)(StackYellowPages+StackRedPages+StackShadowPages+2) * Linux::page_size()); #endif size_t threadStackSizeInBytes = ThreadStackSize * K; if (threadStackSizeInBytes != 0 && threadStackSizeInBytes < os::Linux::min_stack_allowed) { tty->print_cr("\nThe stack size specified is too small, " "Specify at least %dk", os::Linux::min_stack_allowed/ K); return JNI_ERR; } // ★-XX:ThreadStackSizeの値を静的_stack_size_at_create変数に代入する // Make the stack size a multiple of the page size so that // the yellow/red zones can be guarded. JavaThread::set_stack_size_at_create(round_to(threadStackSizeInBytes, vm_page_size())); // ★イニシャルスレッドのスタックサイズ設定処理(★TODO: 別途調査する) Linux::capture_initial_stack(JavaThread::stack_size_at_create()); // ★以降省略★
新規スレッド起動時の処理 jdk7:hotspot/src/os/linux/vm/os_linux.cpp#901
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) { // ★一部省略★ // ★スレッド種別毎にスタックサイズを決める if (os::Linux::supports_variable_stack_size()) { // calculate stack size if it's not specified by caller if (stack_size == 0) { stack_size = os::Linux::default_stack_size(thr_type); switch (thr_type) { //★Javaワーカスレッドの場合 case os::java_thread: // Java threads use ThreadStackSize which default value can be // changed with the flag -Xss assert (JavaThread::stack_size_at_create() > 0, "this should be set"); stack_size = JavaThread::stack_size_at_create(); break; //★JITコンパイラスレッドの場合 case os::compiler_thread: if (CompilerThreadStackSize > 0) { stack_size = (size_t)(CompilerThreadStackSize * K); break; } // else fall through: // use VMThreadStackSize if CompilerThreadStackSize is not defined //★VMスレッド、GCスレッド、ウォッチャースレッドの場合 case os::vm_thread: case os::pgc_thread: case os::cgc_thread: case os::watcher_thread: if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K); break; } } stack_size = MAX2(stack_size, os::Linux::min_stack_allowed); // ★glic関数用いてstack領域を確保する pthread_attr_setstacksize(&attr, stack_size); // ★以降省略★
以上コードの通り、JVMから起動されたスレッドの種別毎のスタックサイズが下記XXパラメータ値が適用されてい る。
引数 | Linux/x86_64環境初期値 | 適用範囲 |
---|---|---|
-XX:ThreadStackSize | 1M | Javaスレッド |
-XX:VMThreadStackSize | 1M | VM thread、GC threads、VM Periodic Task Threadなど |
-XX:CompilerThreadStackSize | 4M | C1 C2 CompilerThread |
以下は実機にて確認された各パラメータの初期値です。
$ java -XX:+PrintFlagsFinal -version | grep -e "CompilerThreadStackSize\|ThreadStackSize\|VMThreadStackSize" intx CompilerThreadStackSize = 0 {pd product} intx ThreadStackSize = 1024 {pd product} intx VMThreadStackSize = 1024 {pd product} java version "1.7.0_75" OpenJDK Runtime Environment (rhel-2.5.4.7.el7_1-x86_64 u75-b13) OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)
CompilerThreadStackSize
が指定しない場合下記コードにて初期値が代入される。
jdk7:hotspot/src/os_cpu/linux_x86/vm/os_linux_x86.cpp#652
// return default stack size for thr_type size_t os::Linux::default_stack_size(os::ThreadType thr_type) { // default stack size (compiler thread needs larger stack) #ifdef AMD64 size_t s = (thr_type == os::compiler_thread ? 4 * M : 1 * M); #else size_t s = (thr_type == os::compiler_thread ? 2 * M : 512 * K); #endif // AMD64 return s; }
次のサンプルプログラムを実行して、各スレッドのスタックサイズ値を実測してみる。
HelloWorld.java
public class HelloWorld implements Runnable { public void run(){ while(true) { try { Thread.sleep(1 * 1000L); System.out.println(Thread.currentThread().toString() + ": Hello World"); } catch (Exception e) { } } } public static void main(String[] args) throws Exception { Thread[] t_arry = new Thread[5]; for (int i = 0; i < t_arry.length; i++) { t_arry[i] = new Thread(new HelloWorld()); t_arry[i].start(); } for (int i = 0; i < t_arry.length; i++) { t_arry[i].join(); } } }
スタックサイズの実測値は次のスクリプトで取得しています。
jvm_stacksize.sh
#!/bin/sh if [ ! $# -eq 1 ]; then echo "Usage ${0} <JVM PID>" exit 1 fi printf "[ PID ]\t[StackSize]\t[GuardPages]\t[Thread Name]\n" # jstackの出力結果からスレッドIDと名前を抽出する jstack $1 | grep nid | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/" | sort | while read line do # スレッドIDを切り出す pid_hex=`echo "${line}" | awk -F"," '{print $1}'` # スレッド名を切り出す thread_name=`echo "${line}" | awk -F"," '{print $2}'` # スレッドIDを10進数に変換 pid=`printf '%d\n' ${pid_hex}` # /proc/<pid>/smaps ファイルからスタックサイズ、ガードページサイズを取得する guard_page=`cat /proc/$1/smaps | grep -B15 "stack:${pid}"| head -1 | awk '{print $2}'` stack_page=`cat /proc/$1/smaps | grep -A1 "stack:${pid}" | tail -1 | awk '{print $2}'` stack_size=`expr ${guard_page} + ${stack_page}` printf "%7d\t%11s\t%12s\t%s\n" "${pid}" "${stack_size}Kb" "${guard_page}Kb" "${thread_name}" done
スタックサイズを明示的に指定して、サンプルを実行する。
java -XX:VMThreadStackSize=2048 -XX:CompilerThreadStackSize=3072 -XX:ThreadStackSize=512 HelloWorld Thread[Thread-0,5,main]: Hello World Thread[Thread-4,5,main]: Hello World Thread[Thread-1,5,main]: Hello World Thread[Thread-3,5,main]: Hello World Thread[Thread-2,5,main]: Hello World ★以降は省略
測定結果
$ ./jvm_stacksize.sh `ps -ef | grep "[j]ava.*HelloWorld" | awk '{print $2}'` [ PID ] [StackSize] [GuardPages] [Thread Name] 17285 1048Kb 12Kb main 17286 2096Kb 4Kb GC task thread#0 (ParallelGC) 17287 2052Kb 4Kb GC task thread#1 (ParallelGC) 17288 2052Kb 4Kb GC task thread#2 (ParallelGC) 17289 2052Kb 4Kb GC task thread#3 (ParallelGC) 17290 2052Kb 4Kb VM Thread 17291 64584Kb 12Kb Reference Handler 17292 516Kb 12Kb Finalizer 17293 516Kb 12Kb Signal Dispatcher 17294 3076Kb 12Kb C2 CompilerThread0 17295 3076Kb 12Kb C2 CompilerThread1 17296 516Kb 12Kb Service Thread 17297 2052Kb 4Kb VM Periodic Task Thread 17298 516Kb 12Kb Thread-0 17299 516Kb 12Kb Thread-1 17300 516Kb 12Kb Thread-2 17301 516Kb 12Kb Thread-3 17302 516Kb 12Kb Thread-4 17342 516Kb 12Kb Attach Listener
実測値はXXパラメータで指定した値より1ページ分多い。これは glibc
内部の
allocate_stack 処理で追加されているものです。
Linux環境に置いて、JVM内のスレッドは全てglibcの pthread_create
関数経由で起動される。
スレッド起動時にスタックサイズ明示的に指定していない場合、 ulimit -s
で設定された値
がスタックのデフォルトサイズとして適用される。前文に書いた通りJVMはスレッド起動時に明
示的 pthread_attr_setstacksize
関数でXXパラメータ値の元にスタックサイズを指定してい
るため、これらのスレッドのスタックサイズは ulimit -s
の値に影響されないだ。
ただし、JVMランチャー自身は ulimit -s
の制限値が適用される。
ulimit
コマンドでスタックの上限値 RLIMIT_STACK
を64Kbを設定し、サンプルプログラム
グライムを実行すると、ランチャーのスタックサイズが60Kbで収まった。
$ ulimit -s 64 $ java -Xss1024K -XX:VMThreadStackSize=2048 -XX:CompilerThreadStackSize=3072 -XX:ThreadStackSize=512 HelloWorld Thread[Thread-1,5,main]: Hello World Thread[Thread-3,5,main]: Hello World Thread[Thread-0,5,main]: Hello World Thread[Thread-2,5,main]: Hello World Thread[Thread-4,5,main]: Hello World ★省略
pmap
コマンドで仮想メモリマップの最上位アドレス近くにランチャーのスタックサイズを確
認することができる。
$ pmap `ps -ef | grep "[j]ava.*HelloWorld" | awk '{print $2}'` ★省略 00007f39d068d000 4K r---- ld-2.17.so 00007f39d068e000 4K rw--- ld-2.17.so 00007f39d068f000 4K rw--- [ anon ] 00007fff7cf79000 60K rw--- [ stack ] ★ランチャーのスタックサイズ 00007fff7cffe000 8K r-x-- [ anon ] ffffffffff600000 4K r-x-- [ anon ] total 3513684K
JVM内の各スレッドのスタックサイズは下記の通りです、 RLIMIT_STACK
に影響されていない
ことが分かります。
]$ ./jvm_stacksize.sh `ps -ef | grep "[j]ava.*HelloWorld" | awk '{print $2}'` [ PID ] [StackSize] [GuardPages] [Thread Name] 10770 1048Kb 12Kb main 10771 2096Kb 4Kb GC task thread#0 (ParallelGC) 10772 2052Kb 4Kb GC task thread#1 (ParallelGC) 10773 2052Kb 4Kb GC task thread#2 (ParallelGC) 10774 2052Kb 4Kb GC task thread#3 (ParallelGC) 10775 15812Kb 4Kb VM Thread 10776 516Kb 12Kb Reference Handler 10777 63556Kb 12Kb Finalizer 10778 516Kb 12Kb Signal Dispatcher 10779 3076Kb 12Kb C2 CompilerThread0 10780 3076Kb 12Kb C2 CompilerThread1 10781 516Kb 12Kb Service Thread 10782 2052Kb 4Kb VM Periodic Task Thread 10783 516Kb 12Kb Thread-0 10784 516Kb 12Kb Thread-1 10785 516Kb 12Kb Thread-2 10786 516Kb 12Kb Thread-3 10787 516Kb 12Kb Thread-4 11203 516Kb 12Kb Attach Listener
-Xss
と -XX:ThreadStackSize
両方ともJavaスレッドのスタックを指定するパラメータで
ある。 ただし、JVMランチャーから起動されたイニシャルスレッドのスタックサイズの制御は
-Xss
パラメータのみできる。
以下はJVMランチャーからイニシャルスレッド起動するまでの流れ
行 | ★ランチャーの実行 1| openjdk/jdk/src/share/bin/main.c:93 ==> int main(int, char **); 2| openjdk/jdk/src/share/bin/java.c:170 ==> int JLI_Launch(int, char **, int, const char **, int, const char **, const char *, const char *, const char *, const char *, jboolean, jboolean, jboolean, jint); 3| openjdk/jdk/src/share/bin/java.c:1835 ==> int ContinueInNewThread(InvocationFunctions *, jlong, int, char **, int, char *, int); | ★イニシャルスレッド起動 4| openjdk/jdk/src/solaris/bin/java_md_solinux.c:1021 ==> int ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) 5| openjdk/jdk/src/share/bin/java.c:337 ==> int JavaMain(void *); | openjdk/jdk/src/share/bin/java.c:1097 ==> jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn) 6| openjdk/hotspot/src/share/vm/prims/jni.cpp ==> jint JNI_CreateJavaVM(JavaVM**, void**, void*); 7| openjdk/hotspot/src/share/vm/runtime/thread.cpp:3271 ==> jint Threads::create_vm(JavaVMInitArgs*, bool*); 8| openjdk/hotspot/src/os/linux/vm/os_linux.cpp:4898 ==> jint os::init_2(void) 9| openjdk/hotspot/src/os/linux/vm/os_linux.cpp:1205 ==> void os::Linux::capture_initial_stack(size_t max_size)
JLI_Launch
関数にてコマンドラインパラメータのパーシング処理が実行される。-Xss
パラメータが指定されていない場合、デフォルト値(1024Kb)を取得し(4)に渡す。pthread_create
関数を用いてイニシャルスレッドを起動する。 -Xss
の
値がスタックサイズに適用される。
-Xss
と -XX:ThreadStackSize
片方指定する場合、と両方指定する場合効果が違うので要注意です。
イニシャルスレッド | ワーカスレッド | |
---|---|---|
-Xss2048K | 2048K | 2048K |
-XX:ThreadStackSize=2048 | 1024K | 2048K |
-Xss2048K | 2048K | 512K |
-XX:ThreadStackSize=512 |
本記事書く際に下記コンテンツを参考した。
以下LinuxプラットフォームでJBossASアプリケーションサーバの話です。
次のケースに置いて、Acceptorスレッドやワーカスレッドの働き状態が悪くなるため、クライ アントから送信されてデータがサーバ側のTCPソケット受信バッファーに溜まる。バッファーが 一杯になるとパケット受信ができなくなる、TCPレーヤでパケット再送が起きる。
この記事はTCP受信バッファーのサイズの実測値を調査致します。
受信したHTTPリクエストパラメータを出力するシンプルなサーブレットをJBossASにデプロイす る。
package jp.co.jizai.sample.jbossas7.tcp; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; import java.util.Map; public class ShowParameterServlet extends HttpServlet { private static final String CONTENT_TYPE = "text/html; charset=UTF-8"; public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } public void process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(CONTENT_TYPE); PrintWriter out = resp.getWriter(); System.out.println("=== http parameter ==="); Map<String, Object> paramMap = (Map<String, Object>) req.getParameterMap(); for (Map.Entry<String, Object> entry : paramMap.entrySet()) { out.write(String.format("%s = %s", entry.getKey(), entry.getValue()) + "<br/>"); } System.out.println("=== http attribute ==="); Enumeration<String> attrNames = (Enumeration<String>) req.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = attrNames.nextElement(); out.write(String.format("%s = %s", attrName, req.getAttribute(attrName)) + "<br/>"); } out.close(); } }
新規接続のサーバソケットのgetReceiveBufferSize()メソッドをコールする Byteman
スクリ
プトを実行中のJBassASにアタッチメントする。
RULE trace http ReceiveBufferSize CLASS org.apache.tomcat.util.net.JIoEndpoint METHOD processSocket AT ENTRY BIND socket = $1 IF TRUE DO traceln("socket.getReceiveBufferSize() = " + socket.getReceiveBufferSize()), traceStack() ENDRULE
クライアントブラウザからテスト用サーブレットをアクセスすると、サーバログに以下の内容 が出力される。
15:46:11,002 INFO [stdout] (http-/0.0.0.0:8080-Acceptor-0) socket.getReceiveBufferSize() = 43690 15:46:11,004 INFO [stdout] (http-/0.0.0.0:8080-Acceptor-0) Stack trace for thread http-/0.0.0.0:8080-Acceptor-0 15:46:11,004 INFO [stdout] (http-/0.0.0.0:8080-Acceptor-0) org.apache.tomcat.util.net.JIoEndpoint.processSocket(JIoEndpoint.java:-1) 15:46:11,004 INFO [stdout] (http-/0.0.0.0:8080-Acceptor-0) org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:315) 15:46:11,005 INFO [stdout] (http-/0.0.0.0:8080-Acceptor-0) java.lang.Thread.run(Thread.java:745)
[[https://docs.oracle.com/javase/jp/6/api/java/net/Socket.html#setReceiveBufferSize%2528int%2529][java.net.Socket.getReceiveBufferSize]]()
で取れた値→ 43690
単位がよくわからないの
でJavaDocを引いてみた。
public int getReceiveBufferSize() throws SocketException この Socket で使われる SO_RCVBUF オプションの値を取得します。これは、この Socket で入力用としてプラットフォームが使うバッファーのサイズです。 戻り値: この Socket の SO_RCVBUF オプションの値 例外: SocketException - 使用しているプロトコルでエラー (TCP エラーなど) が発生した場合 導入されたバージョン: 1.2 関連項目: setReceiveBufferSize(int)
やはり、分からないので SO_RCVBUF
オプションの単位を探って見る。
SO_RCVBUF ソケットの受信バッファーの最大サイズを設定・取得する (バイト単位)。 setsockopt(2) を使って値が設定されたときに (管理オーバヘッド用の領域を確保するために) カーネルは この値を 2倍し、 getsockopt(2) はこの 2倍された値を返す。 デフォルトの値は /proc/sys/net/core/rmem_default ファイルで設定され、許容される最大の値は /proc/sys/net/core/rmem_max ファイルで設定される。 このオプションの最小値は (2倍し た値で) 256 である
僕の日本語理解力が低いので、正確な意味が掴めないままだが。一先ず受信バッファーサイズの
単位がバイトだそうです。 43690
バイトは約42KB、またTCP受信バッファーカーネルパラメー
タ net.ipv4.tcp_rmem
の値は下記の通りとなります。 43690
は丁度 net.ipv4.tcp_rmem
のデフォルト値の 1/2 であることが分かりました。
# sysctl -a | grep net.ipv4.tcp_rmem net.ipv4.tcp_rmem = 4096 87380 4194304
新規接続を受け付ける処理 org.apache.tomcat.util.net.JIoEndpoint.processSocket(Socket
socket)
でスレッドを30秒間Sleepされる Byteman
スクリプトを実行中のJBossASにアタッチ
メントする。
RULE pause acceptor thread
CLASS org.apache.tomcat.util.net.JIoEndpoint
METHOD processSocket
AT ENTRY
BIND socket = $1
IF TRUE
DO
Thread.sleep(300000)
ENDRULE
以下のコマンドでTCPソケット受信バッファサイズを監視する
$ watch -n 2 'netstat -an | grep ESTABLISHED | grep 8080'
クライアント側にて以下のコマンドでパケット通信の監視を行う
$sudo tcpdump -n -i virbr0 port 8080
net.ipv4.tcp_rmem
パラメータデフォルト値 87380
より大きい電文を送るようにHTTPヘッ
ダーに Content-Length: 120100
を指定する。
$ telnet jbossas-lab02 8080 POST /jbossas7-tcp-basic/ShowParameterServlet HTTP/1.1 Host: jbossas-lab02:8080 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:36.0) Gecko/20100101 Firefox/36.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ja,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://jbossas-lab002:8080/jbossas7-tcp-basic/ Connection: Close Content-Type: application/x-www-form-urlencoded Content-Length: 120100 ★まず100バイト分を送る(エンターは2バイト分になります) param1=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ★エンターを押す ★以下4000バイト文字を30回繰り返しサーバに送る bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
以下TCPソケット受信バッファサイズの監視結果、文中の★マークは結果の解析コメントとなり ます。
# while [ true ]; do netstat -an | grep ESTABLISHED | grep 8080; sleep 2; done ★1 TCP3WHS 接続確立 tcp 0 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED ★2 HTTPヘッダー受信 tcp 447 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED ★3 リクエストデータの受信 tcp 547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 4547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 8547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 12547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 16547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 20547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 24547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 28547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 32547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 36547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 40547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED ★4 アプリ予約バッファサイズに達した、以降は管理領域を食い始めた ★ このタイミングからサーバ受信ウィンドウサイズが徐々に縮める tcp 44547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 48547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 52547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 56547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 60547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 64547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 68547 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED ★5 以降TCPバッファが満タン tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED tcp 72211 0 192.168.122.66:8080 192.168.122.1:40976 ESTABLISHED
パケット監視結果は以下の通りです。
★1 TCP3WHS 接続確立 16:45:01.262499 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [S], seq 3028524293, win 29200, options [mss 1460,sackOK,TS val 50120659 ecr 0,nop,wscale 7], length 0 16:45:01.262697 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [S.], seq 1682372075, ack 3028524294, win 14480, options [mss 1460,sackOK,TS val 3798305 ecr 50120659,nop,wscale 7], length 0 16:45:01.262773 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 50120659 ecr 3798305], length 0 ★2 HTTPヘッダー送信 16:45:09.935452 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 1:57, ack 1, win 229, options [nop,nop,TS val 50123261 ecr 3798305], length 56: HTTP: POST /jbossas7-tcp-basic/ShowParameterServlet HTTP/1.1 16:45:09.935789 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 57, win 114, options [nop,nop,TS val 3806979 ecr 50123261], length 0 16:45:09.935850 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 57:422, ack 1, win 229, options [nop,nop,TS val 50123261 ecr 3806979], length 365: HTTP 16:45:09.935905 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 422, win 122, options [nop,nop,TS val 3806979 ecr 50123261], length 0 16:45:10.330264 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 422:446, ack 1, win 229, options [nop,nop,TS val 50123379 ecr 3806979], length 24: HTTP 16:45:10.330448 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 446, win 122, options [nop,nop,TS val 3807373 ecr 50123379], length 0 16:45:10.897750 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 446:448, ack 1, win 229, options [nop,nop,TS val 50123550 ecr 3807373], length 2: HTTP 16:45:10.897911 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 448, win 122, options [nop,nop,TS val 3807941 ecr 50123550], length 0 ★3 リクエストデータの送信 16:45:19.464509 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 448:548, ack 1, win 229, options [nop,nop,TS val 50126120 ecr 3807941], length 100: HTTP 16:45:19.464668 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 548, win 122, options [nop,nop,TS val 3816507 ecr 50126120], length 0 16:45:26.602423 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 548:4548, ack 1, win 229, options [nop,nop,TS val 50128261 ecr 3816507], length 4000: HTTP 16:45:26.602602 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 4548, win 145, options [nop,nop,TS val 3823645 ecr 50128261], length 0 16:45:32.436162 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 4548:8548, ack 1, win 229, options [nop,nop,TS val 50130011 ecr 3823645], length 4000: HTTP 16:45:32.436338 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 8548, win 167, options [nop,nop,TS val 3829479 ecr 50130011], length 0 16:45:35.452590 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 8548:12548, ack 1, win 229, options [nop,nop,TS val 50130916 ecr 3829479], length 4000: HTTP 16:45:35.452721 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 12548, win 190, options [nop,nop,TS val 3832495 ecr 50130916], length 0 16:45:38.082228 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 12548:16548, ack 1, win 229, options [nop,nop,TS val 50131705 ecr 3832495], length 4000: HTTP 16:45:38.082396 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 16548, win 212, options [nop,nop,TS val 3835125 ecr 50131705], length 0 16:45:40.743705 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 16548:20548, ack 1, win 229, options [nop,nop,TS val 50132504 ecr 3835125], length 4000: HTTP 16:45:40.743848 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 20548, win 235, options [nop,nop,TS val 3837787 ecr 50132504], length 0 16:45:43.016683 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 20548:24548, ack 1, win 229, options [nop,nop,TS val 50133185 ecr 3837787], length 4000: HTTP 16:45:43.016863 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 24548, win 258, options [nop,nop,TS val 3840060 ecr 50133185], length 0 16:45:45.721066 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 24548:28548, ack 1, win 229, options [nop,nop,TS val 50133997 ecr 3840060], length 4000: HTTP 16:45:45.721253 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 28548, win 280, options [nop,nop,TS val 3842764 ecr 50133997], length 0 16:45:49.227814 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 28548:32548, ack 1, win 229, options [nop,nop,TS val 50135049 ecr 3842764], length 4000: HTTP 16:45:49.227964 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 32548, win 253, options [nop,nop,TS val 3846271 ecr 50135049], length 0 16:45:51.671841 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 32548:36548, ack 1, win 229, options [nop,nop,TS val 50135782 ecr 3846271], length 4000: HTTP ★4 ここから再送が発生する 16:45:51.680266 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 35444:36548, ack 1, win 229, options [nop,nop,TS val 50135785 ecr 3846271], length 1104: HTTP 16:45:51.711839 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 36548, win 225, options [nop,nop,TS val 3848755 ecr 50135782,nop,nop,sack 1 {35444:36548}], length 0 16:45:55.390996 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 36548:40548, ack 1, win 229, options [nop,nop,TS val 50136898 ecr 3848755], length 4000: HTTP 16:45:55.430802 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 40548, win 197, options [nop,nop,TS val 3852474 ecr 50136898], length 0 16:45:59.291140 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 40548:43444, ack 1, win 229, options [nop,nop,TS val 50138068 ecr 3852474], length 2896: HTTP 16:45:59.291169 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 43444:44548, ack 1, win 229, options [nop,nop,TS val 50138068 ecr 3852474], length 1104: HTTP 16:45:59.310296 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 43444:44548, ack 1, win 229, options [nop,nop,TS val 50138074 ecr 3852474], length 1104: HTTP 16:45:59.330773 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 44548, win 166, options [nop,nop,TS val 3856374 ecr 50138068,nop,nop,sack 1 {43444:44548}], length 0 16:46:01.367501 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 44548:47444, ack 1, win 229, options [nop,nop,TS val 50138691 ecr 3856374], length 2896: HTTP 16:46:01.367533 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 47444:48548, ack 1, win 229, options [nop,nop,TS val 50138691 ecr 3856374], length 1104: HTTP 16:46:01.406803 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 48548, win 135, options [nop,nop,TS val 3858450 ecr 50138691], length 0 16:46:03.523326 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 48548:51444, ack 1, win 229, options [nop,nop,TS val 50139337 ecr 3858450], length 2896: HTTP 16:46:03.523356 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 51444:52548, ack 1, win 229, options [nop,nop,TS val 50139337 ecr 3858450], length 1104: HTTP 16:46:03.553582 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 51444:52548, ack 1, win 229, options [nop,nop,TS val 50139347 ecr 3858450], length 1104: HTTP 16:46:03.553716 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 52548, win 104, options [nop,nop,TS val 3860596 ecr 50139337,nop,nop,sack 1 {51444:52548}], length 0 16:46:06.322183 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 52548:55444, ack 1, win 229, options [nop,nop,TS val 50140177 ecr 3860596], length 2896: HTTP 16:46:06.322212 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 55444:56548, ack 1, win 229, options [nop,nop,TS val 50140177 ecr 3860596], length 1104: HTTP 16:46:06.361777 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 56548, win 73, options [nop,nop,TS val 3863405 ecr 50140177], length 0 16:46:08.944983 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 56548:57996, ack 1, win 229, options [nop,nop,TS val 50140964 ecr 3863405], length 1448: HTTP 16:46:08.945017 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 57996:59444, ack 1, win 229, options [nop,nop,TS val 50140964 ecr 3863405], length 1448: HTTP 16:46:08.945027 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 59444:60548, ack 1, win 229, options [nop,nop,TS val 50140964 ecr 3863405], length 1104: HTTP 16:46:08.945188 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 57996, win 62, options [nop,nop,TS val 3865988 ecr 50140964], length 0 16:46:08.983605 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 59444:60548, ack 1, win 229, options [nop,nop,TS val 50140976 ecr 3865988], length 1104: HTTP 16:46:08.983765 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 60548, win 43, options [nop,nop,TS val 3866027 ecr 50140964,nop,nop,sack 1 {59444:60548}], length 0 16:46:10.874178 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 60548:63444, ack 1, win 229, options [nop,nop,TS val 50141543 ecr 3866027], length 2896: HTTP 16:46:10.874209 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 63444:64548, ack 1, win 229, options [nop,nop,TS val 50141543 ecr 3866027], length 1104: HTTP 16:46:10.913789 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 64548, win 12, options [nop,nop,TS val 3867957 ecr 50141543], length 0 16:46:13.263314 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 64548:65996, ack 1, win 229, options [nop,nop,TS val 50142259 ecr 3867957], length 1448: HTTP 16:46:13.263477 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 65996, win 1, options [nop,nop,TS val 3870306 ecr 50142259], length 0 16:46:13.483589 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 65996:66124, ack 1, win 229, options [nop,nop,TS val 50142326 ecr 3870306], length 128: HTTP 16:46:13.483812 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 66124, win 46, options [nop,nop,TS val 3870527 ecr 50142326], length 0 16:46:13.483865 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 66124:67572, ack 1, win 229, options [nop,nop,TS val 50142326 ecr 3870527], length 1448: HTTP 16:46:13.483885 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 67572:68548, ack 1, win 229, options [nop,nop,TS val 50142326 ecr 3870527], length 976: HTTP 16:46:13.523583 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 67572:68548, ack 1, win 229, options [nop,nop,TS val 50142338 ecr 3870527], length 976: HTTP 16:46:13.523752 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 68548, win 28, options [nop,nop,TS val 3870567 ecr 50142326,nop,nop,sack 1 {67572:68548}], length 0 16:46:15.144825 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 68548:69996, ack 1, win 229, options [nop,nop,TS val 50142824 ecr 3870567], length 1448: HTTP 16:46:15.144860 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], seq 69996:71444, ack 1, win 229, options [nop,nop,TS val 50142824 ecr 3870567], length 1448: HTTP 16:46:15.145025 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 69996, win 17, options [nop,nop,TS val 3872188 ecr 50142824], length 0 16:46:15.184796 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 71444, win 6, options [nop,nop,TS val 3872228 ecr 50142824], length 0 16:46:15.406939 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [P.], seq 71444:72212, ack 1, win 229, options [nop,nop,TS val 50142903 ecr 3872228], length 768: HTTP ★5 ここからサーバから受信可能なTCPウィンドウサイズが0の応答パケットが出始めた 16:46:15.407110 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 72212, win 0, options [nop,nop,TS val 3872450 ecr 50142903], length 0 16:46:15.626933 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 50142969 ecr 3872450], length 0 16:46:15.627096 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 72212, win 0, options [nop,nop,TS val 3872670 ecr 50142903], length 0 16:46:16.066943 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 50143101 ecr 3872670], length 0 16:46:16.067143 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 72212, win 0, options [nop,nop,TS val 3873110 ecr 50142903], length 0 16:46:16.950253 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 50143366 ecr 3873110], length 0 16:46:16.950401 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 72212, win 0, options [nop,nop,TS val 3873993 ecr 50142903], length 0 16:46:18.716950 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 50143896 ecr 3873993], length 0 16:46:18.717129 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 72212, win 0, options [nop,nop,TS val 3875760 ecr 50142903], length 0 16:46:22.250259 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 50144956 ecr 3875760], length 0 16:46:22.250438 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 72212, win 0, options [nop,nop,TS val 3879293 ecr 50142903], length 0 16:46:29.303610 IP 192.168.122.1.40976 > 192.168.122.66.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 50147072 ecr 3879293], length 0 16:46:29.303785 IP 192.168.122.66.8080 > 192.168.122.1.40976: Flags [.], ack 72212, win 0, options [nop,nop,TS val 3886347 ecr 50142903], length 0
JavaDocから引用
受信バッファーのサイズを増やすと、大規模な接続でのネットワーク入出力のパフォーマンス を上げることができます。一方、サイズを減らすと、受信データのバックログを減らすことが できます。
SO_RCVBUF は ヒント
なので、アプリケーションでバッファーのサイズ設定を調べる必要が
ある場合は、getReceiveBufferSize() を呼び出してください。
SO_RCVBUF の値は、リモートピアに通知される TCP 受信ウィンドウの設定にも使用されます。 一般に、ソケットが接続されているかぎり、このウィンドウサイズはいつでも変更できます。 ただし、64K を超える受信ウィンドウを要求する場合は、ソケットをリモートピアに接続する 前に変更を要求する必要があります。次の 2 つの場合に注意してください。
ServerSocket から受け入れたソケットの場合、ServerSocket をローカルアドレスにバインド する前に、ServerSocket.setReceiveBufferSize(int) を呼び出してこれを実行する必要があ ります。
クライアントソケットの場合、ソケットをそのリモートピアに接続する前に、 setReceiveBufferSize() を呼び出す必要があります。
SO_RCVBUF の値は、内部ソケット受信バッファーのサイズの設定と、リモートピアに通知さ れる TCP 受信ウィンドウのサイズの設定の両方に使用されます。
その後、Socket.setReceiveBufferSize(int) を呼び出すことで値を変更できます。ただし、 アプリケーションが RFC1323 で定義されている 64K バイトを超える受信ウィンドウを使用可 能にする必要がある場合には、ローカルアドレスにバインドする前に 推奨値を ServerSocket で設定する必要があります。つまり、引数なしコンストラクタを使って ServerSocket を作成し、次に setReceiveBufferSize() を呼び出し、最後に bind() を呼び 出して ServerSocket をアドレスにバインドする必要があることを意味します。
これに失敗してもエラーは発生せず、バッファーサイズは要求された値に設定されます。た だし、この ServerSocket から受け取るソケットの TCP 受信ウィンドウは 64K バイト以下に なります。
net.ipv4.tcp_rmem
の1/2が適用さ
れる(約42KB)。
net.ipv4.tcp_rmem
値の8割ぐらい
ミドルウェアの内部動作をトレースするためによく使うので手順を残しておきます。
$ wget http://downloads.jboss.org/byteman/2.2.1/byteman-download-2.2.1-bin.zip $ unzip byteman-download-2.2.1-bin.zip
$ export BYTEMAN_HOME=`pwd`/byteman-download-2.2.1 $ export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.65.x86_64 $ JBOSS_PID=`ps -ef | grep [j]boss-modules.jar | awk '{print $2}'` $ ${BYTEMAN_HOME}/bin/bminstall.sh -b ${JBOSS_PID}
JBossASサーバソケット受信バッファーサイズを調べるスクリプト
trace_socket_receive_buffer_size.btm
を作成する
RULE trace http ReceiveBufferSize CLASS org.apache.tomcat.util.net.JIoEndpoint METHOD processSocket AT ENTRY BIND socket = $1 IF TRUE DO traceln("socket.getReceiveBufferSize() = " + socket.getReceiveBufferSize()), traceStack() ENDRULE
$ ${BYTEMAN_HOME}/bin/bmsubmit.sh trace_socket_receive_buffer_size.btm
本記事は以下の環境を前提とする。
両方指定した場合、方式2が優先される。指定しない場合 512 x JVMに割り当てたコア数
サーバ起動時にワーカースレッドの初期値が0、リクエストが来るたびに新規スレッドを作る。 スレッドの数がmax-connectionsの数に達した時にログに以下のメッセージがを出力される。
INFO [JIoEndpoint] Maximum number of threads (xxx) created for connector with address /127.0.0.1 and port 8080
下記AとBの何れが起きた場合、クライアントからすると無応答や応答が激遅いことが感じるので 防止の対策を講じることをおすすめします。
以下の状況に置いて、クライアントからのリクエスデータがサーバ側のTCPバッファーに詰ま る。TCPバッファーが一杯になると、パケットがdropされるのでクライアントからのデータ再 送が発生する。
また、Acceptorスレッドが止まる場合新規接続(3WHS完了)がbacklogキューに溜まるので backlogキューが溢れる可能性もあります。
上記に関して、Threadサブシステム利用時も同様です。
TCPバッファーがについて、TCP接続単位にTCPバッファーが持っている、デフォルト値は net.ipv4.tcp_rmem カーネルパラメータ値の1/2になります、約42KB。
CPU高負荷でAcceptorスレッドが止まってかつbacklogキューが溢れた場合、コネクションの確立 ができない、クライアントからのsyn(1)やack(3)の再送が起きる。 TCP SYNの再送間隔は以下の通り、約64秒でタイムアウトになる。
1回目 +1秒 2回目 +2秒 3回目 +4秒 4回目 +8秒 5回目 +16秒 6回目 +32秒 タイムアウト
クライアントからすると応答が激遅いと感じることがある。
※上記に関して、Threadサブシステム利用時も同様です。
同時接続数がmax-connections値を超えた場合、Acceptorスレッドが新規コネクションを受付し、 その後のワーカースレッド割り当て処理でワーカースレッドがないことを気付き、ソケットをク ローズする(FINパケットを送出)。
注意: このケースに置いて必ずじもCPU高負荷とは限らない、ワーカスレッドがアプリに掴み放しの場 合も起こりえる。
実際の運用上はどちらも発生しりえる。それぞれのパターンに置いてLBがどう振る舞うかを明確 した上でワーカースレッドのbusy数とCPUの使用率を監視し、閉塞運用、スペアインスタンスの 運用などを設計することが大事だと思います。