TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

Java デバッグ:止めて見る、止めずに見る、記録して見る

状態:ドラフト(2026-10-26)、実測反映済み。 実測環境は2つあります。①ノートPC上のコンテナ(事前に取得した eclipse-temurin:21 イメージ、JDK 21.0.12、ネットワークなし)でサンプルプログラムを5つ自作して実行しました。新しくダウンロードやインストールを行ったものはありません。②TT Labクラスターで稼働中のKeycloak Pod(Red Hatビルド OpenJDK 21.0.11)に対して、 読み取りコマンドのみ(jcmd 1 VM.version を実行しました。デバッグポートを開くこと、JFRの記録を開始すること、ヒープダンプを取得すること、再起動することは本番Podでは行っていません。async-profilerはダウンロードが必要なため 未実測です。公式ドキュメントは同日に原文を開いて確認しました。 作成ルール:実測データを基本とし、測定できなかったものは「未実測」、確認できなかったものは「確認できず」、推測は「推定」と記載します。

目次

1. 3つの見方

Javaサービスに異常が発生した際に行えることは大きく分けて3つあります。

この記事では、まずJVMが外部ツールと対話する2つの経路を図解で確認し(第2節)、デバッガ(第3〜4節)、診断コマンド(第5節)、3つの事例(第6〜8節)、JFR(第9節)、本番環境での手順と注意点(第10節)の順に解説します。

2. JVMの内部を覗き見る2つの経路

JVM 안을 들여다보는 두 길: 디버거(JPDA)와 진단 명령(Attach) ① 디버거 — 멈추고, 한 줄씩, 값을 바꾼다 IDE · jdb JDI (자바 API) 프런트엔드 JDWP 에이전트 JVM TI 로 JVM 조작 -agentlib:jdwp JDWP (TCP 소켓, 인증 없음) 포트포워드로 내 PC 에서만 ② 진단 명령 — 멈추지 않고(또는 아주 잠깐) 상태를 읽는다 jcmd · jstack jmap · jfr 같은 사용자 · 같은 호스트 Attach Listener 첫 요청에 생기는 스레드 명령을 실행해 돌려줌 유닉스 소켓 (.java_pid<N>) 네트워크 포트 없음 대상 JVM 스레드 · 락 Thread.print 데드락 탐지 힙 · GC GC.heap_info 힙 덤프 · GC 로그 JFR 버퍼 JFR.start → .jfr → jfr view 실측: 디버거 없이 JDWP 만 열어 둔 JVM 은 속도 차이 없음(−0.4%, 잡음 수준). 그러나 포트에 닿는 누구나 「JDWP-Handshake」 14바이트로 붙는다.
図:デバッガがJDWPソケットを介してJDWPエージェントにアタッチする経路と、jcmdがUnixドメインソケットを介してAttach Listenerにアタッチする経路

デバッガの経路:JPDA Javaプラットフォームデバッガアーキテクチャ(JPDA)は3つの層で構成されています(公式ドキュメント)。

層 位置づけ 役割
JVM TI 対象JVM内のネイティブインターフェース 「このスレッドを停止せよ」、「このメソッドにブレークポイントを設定せよ」、「このローカル変数の値」といったデバッグサービスをJVMが提供
JDWP 通信プロトコル デバッガと対象JVM間のバイトプロトコル。ソケットまたは共有メモリを使用
JDI デバッガ側のJava API IDEやjdbが使用する高水準インターフェース

-agentlib:jdwp=… は、対象JVMにJDWPエージェント(ネイティブライブラリ)をロードし、JVM TIを呼び出してJDWPで通信させます。よく使われる設定を紐解くと以下のようになります(JDK 21ドキュメント)。

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
               │                   │        │         └ 어디서 기다리나. 호스트를 비우면 루프백만, * 면 모든 인터페이스
               │                   │        └ 시작하자마자 멈추고 디버거를 기다릴까(기본 y!)
               │                   └ JVM 이 듣는 쪽(서버)이 될까(기본 n: JVM 이 디버거로 걸어 나간다)
               └ TCP 소켓

suspend のデフォルト値が y であることを忘れていると、デバッグオプションを付与して起動したサービスが デバッガがアタッチされるまで起動すらしない という状況になります。Kubernetes環境であれば、プローブ(Liveness/Readiness)が失敗し続けます。アドレスの仕様はJDK 9で変更されました。以前はポートを指定するだけですべてのインターフェースで待ち受けていましたが、JDK 9以降はループバックでのみ待ち受けるようになり、すべてのインターフェースでの待ち受けが必要な場合は *: を付与する必要があります。JDK 9のリリースノートには、 * へのバインドは「安全ではなく、推奨されない」と記載されています。

診断コマンドの経路:Attach jcmd·jstack·jmap はネットワークを使用しません。対象JVMの一時ディレクトリにあるUnixドメインソケット(.java_pid<PID>)にアタッチし、JVMは最初のリクエストを受け取ると Attach Listener というスレッドを起動してコマンドを実行します。同じユーザー、同じホスト(コンテナの場合は同じコンテナ)からのみ実行可能です。そのため、Kubernetesでは kubectl exec を使ってそのコンテナ内に進入し、実行します。

3. デバッガで止めて見る:jdbを一巡する

1秒ごとに1点ずつスコアを記録する小さなプログラム(Rally.point(boolean leftWins, int rallyNo))を -g でコンパイルし、JDWPを有効にして起動した後、同じコンテナ内からjdbでアタッチしました(2026-10-26)。

$ jdb -attach 5005 -sourcepath /w
> stop in Rally.point
Breakpoint hit: "thread=main", Rally.point(), line=6 bci=0
6            if (leftWins) left++; else right++;
main[1] where
  [1] Rally.point (Rally.java:6)
  [2] Rally.main (Rally.java:13)
main[1] locals
Method arguments:
leftWins = false
rallyNo = 6
main[1] print Rally.left           ← 정적 필드는 클래스 이름을 붙여야 한다(그냥 left 는 「Name unknown」)
 Rally.left = 4
main[1] set leftWins = !leftWins
 leftWins = !leftWins = true
main[1] clear Rally.point
main[1] cont

プログラム側の出力を時刻とともに確認すると、デバッガが何を行ったのかがわかります。

時刻(秒) 出力 説明
:34.03 ラリー 5 → 4:1 通常通りの1秒間隔
:39.54 ラリー 6 → 5:1 ブレークポイントで 4.5秒間停止し、本来は右側の得点(4:2)になるはずのラリーを左側の得点に変更した
:42.05 Listening for transport dt_socket at address: 5005 jdbを終了すると 待ち受けを再開

デバッガは実行を停止させるだけでなく、 値を変更することもできます。 そして、デバッガが切断されてもポートは次の接続を待ち続けます。この2つの事実が、次の節で述べるセキュリティ上の問題につながります。

IDEのリモートデバッグも原理は同じです。IntelliJの「Remote JVM Debug」やVS Codeの "request": "attach" の設定が、上記のjdbの位置でJDIを介して同じJDWPポートにアタッチします。

デバッグポートには認証がない

JDWP接続は、双方が JDWP-Handshake の14バイトをやり取りすることから始まります。それだけです。実際に検証してみました。ポートにアタッチして14バイトを送信し、「VMのバージョンを教えてほしい」というコマンドパケット(11バイト)を1つ送信しました。

JDWP-Handshake
…Java Debug Wire Protocol (Reference Implementation) version 21.0
JVM Debug Interface version 21.0
JVM version 21.0.12 (OpenJDK 64-Bit Server VM, mixed mode, sharing)…

パスワードも証明書も要求されませんでした。これは、デバッガが行えること(任意のメソッドの呼び出し、値の変更)を、ポートに到達できる誰でも行えるということを意味します。JDWPに認証がないと断言する公式ドキュメントの文言は見つかりませんでしたが(確認できず)、上記の実測結果、JDK 9リリースノートの警告、OpenJDKメーリングリストにおける「リモートコード実行を許可する」という説明は同じ方向を向いています。実測したコンテナ内では、 address=*:5005 のソケットが /proc/net/tcp6 の全アドレス(::)の5005番ポートで立ち上がっていました。

コストはどうでしょうか? デバッガがアタッチされていない状態であれば、JDWPエージェントをロードしているだけのコストは測定できないほど小さかったです(第9節の表:基準値比 -0.4%、誤差の範囲)。そのため、「切るのが面倒くだから」という理由で本番イメージにそのまま残りやすく、それが危険を招きます。

4. Kubernetes Podをリモートデバッグするには

本番Podでは行わないのが原則です。ブレークポイントがサービスのスレッドを停止させるとその間にリクエストが蓄積され、Livenessプローブが失敗してkubeletがコンテナを終了させてしまいます。開発やステージング環境で行う際の安全な構成は以下の通りです。

  1. デバッグ用のコピーを別途立ち上げる。 本番のDeploymentを修正せず、同じイメージでデバッグ専用のPodを作成します(kubectl debug <파드> --copy-to=<사본> または個別のマニフェスト)。環境変数 JAVA_TOOL_OPTIONS にJDWPオプションを指定すれば、イメージの起動スクリプトを変更しなくてもJVMが読み込みます。
  2. ループバックにのみバインドする。 address=127.0.0.1:5005 のように、Pod内のループバックでのみ待ち受けるようにし、Pod外に向けたServiceは作成しません。
  3. port-forwardで自PCのみに公開する。 kubectl port-forward pod/<사본> 5005:5005 はデフォルトで自分のPCの localhost にのみオープンされ(公式ドキュメント: --address デフォルト値はlocalhost)、TCPのみをサポートします。APIサーバーの認証・認可(RBACの pods/portforward)を通過するため、JDWP自体には備わっていない認証をKubernetesが肩代わりしてくれることになります。
  4. プローブを無効化するか、タイムアウトを延長する。 コピー環境ではlivenessプローブを外し、IDEのブレークポイントは「すべてのスレッド」ではなく「そのスレッドのみ」を停止させるように設定します。
  5. 終了したらコピーを削除する。

この節の手順は、本番Podではデバッグポートを開かないという本書の原則に従い、 未実測です。port-forwardがPod内のループバックアドレスに到達するという点については、Kubernetesドキュメントの例(Podのポートへの転送)でのみ確認しています。

5. 止めずに見る:jcmd

jcmd <PID> help を実行すると、そのJVMが受け付け可能なコマンドがすべて表示されます。頻繁に使用するものと、 JVMにかかる負荷を併せて記載します(負荷は実測とドキュメントに基づき判断)。

コマンド 表示内容 負荷
VM.version · VM.uptime · VM.flags バージョン、稼働時間、実際に適用されたJVMオプション ほぼなし
Thread.print (= jstack) 全スレッドのスタックとロック、デッドロック検出 短い停止(セーフポイント)。実測89ms(コマンド全体の往復)
GC.heap_info ヒープ領域ごとの使用量 ほぼなし
GC.class_histogram クラスごとのオブジェクト数・バイト数 Full GCを引き起こす(第8節の実測)
GC.heap_dump <파일> ヒープ全体をHPROFファイルに出力 JVMの停止、ヒープサイズ分のディスク領域
JFR.start · JFR.dump · JFR.stop フライトレコーダの開始・保存・終了 デフォルト設定で1%前後(第9節)
VM.native_memory summary ヒープ外メモリ(スレッドスタック、メタスペース等) 起動時に -XX:NativeMemoryTracking を有効にした場合のみ
Thread.dump_to_file 仮想スレッドを含むダンプ(JSON出力可能) JEP 444の例に記載、JDK 21のjcmdマニュアルにはなくJDK 25のマニュアルに存在

実測:コンテナ内のJVMはメモリをいくらと認識しているか

JVMはコンテナのメモリ制限(cgroup)を読み取ってデフォルトのヒープサイズを決定します。デフォルトはその制限の25%(-XX:MaxRAMPercentage のデフォルト値、公式ドキュメント)です。ノートPC上のコンテナにメモリ制限を渡し、 -XX:+PrintFlagsFinal で最大ヒープサイズを読み取りました。

コンテナメモリ オプションなし -XX:MaxRAMPercentage=70
512MiB 128MiB 360MiB
2GiB 512MiB 1,434MiB

オプションを指定せずに2GiBのPodで起動した場合、ヒープは512MiBだけで残り1.5GiBは遊ぶことになります。逆に割合を高く設定しすぎると、ヒープ外メモリ(スレッドスタック、メタスペース、ダイレクトバッファ)が不足してコンテナがOOMKilledになります。この場合、JavaのOutOfMemoryErrorもヒープダンプも残らず、Podだけが消滅します。

実測:クラスターのKeycloak Podを読み取りのみで確認する

TT LabクラスターのKeycloak Podに kubectl exec で入り、jcmdを実行しました(2026-10-26)。このイメージはRed Hatのheadless JREを使用していますが、その中にも jcmd が含まれていました(jstack·jmap はなし)。jcmd一つで両方の役割をこなすため問題ありません。JVMのPIDは1でした。

コマンド 結果
VM.version OpenJDK 64-Bit Server VM 21.0.11+10-LTS
VM.uptime 711,384秒(約8.2日)
VM.flags(一部) MaxRAM=2147483648(ポッド制限 2GiB)、 MaxRAMPercentage=70 → MaxHeapSize=1503657984(1,434MiB)、 UseG1GC, ExitOnOutOfMemoryError, FlightRecorderOptions=stackdepth=512
GC.heap_info G1ヒープ 177MiB確保中、157MiB使用、メタスペース 141MiB
Thread.print 要約 スレッド 95個(Java状態あり:RUNNABLE 40、TIMED_WAITING 15、WAITING 14)。デッドロックなし。最も多いのは vert.x-eventloop-thread-N 22個
JFR.check 進行中の記録なし
VM.native_memory summary 「Native memory tracking is not enabled」

いくつかの項目が読み取れます。イメージで ExitOnOutOfMemoryError を有効にしておいたため、ヒープがあふれるとJVMが直ちに終了し、Kubernetesが再起動します。その代わり、原因を残すヒープダンプオプションはありません。最大ヒープ1,434MiBに比べて実使用量は157MiBと余裕が大きいです。上の表の2GiB・70%の計算(1,434MiB)と稼働ポッドの値が完全に一致していることも確認できます。

コマンドを1回実行するたびにAttach Listenerスレッドが生成される点は覚えておきます。「読み取りコマンド」であっても、対象JVM内にスレッドを1つ作成する処理です。

6. ケース1:停止とデッドロック

증상에서 도구로: 무엇을 먼저 보나 멈춤 · 응답 없음 jcmd Thread.print 5–10초 간격 세 번 같은 자리에 BLOCKED? 「Found one Java-level deadlock」 확인 실측: 2초 만에 두 스레드의 고리 CPU 가 높다 top -H 로 뜨거운 TID jstack 의 nid 와 맞추기 (JDK 21 은 10진수) JFR 10초 기록 jfr view hot-methods 실측: 460 샘플 100% 한 메서드 메모리 · OOM GC 로그: Full GC 뒤 줄지 않는가 GC.heap_info OOM 때 힙 덤프 (힙 크기만큼 디스크) 실측: 64MB 힙이 5.5초에 OOM 운영 파드에서 조심할 것 · GC.class_histogram 은 Full GC 를 일으킨다(실측으로 확인) · 힙 덤프는 그동안 JVM 을 멈추고, 파일에 개인정보·토큰이 그대로 담긴다 · JFR 파일에는 환경 변수 값이 들어간다(실측) — 밖으로 보내기 전에 scrub · 디버그 포트(JDWP)는 운영 이미지에 켜 두지 않는다
図:停止・CPU過熱・メモリ増加の3つの症状において最初に確認すべきツールと実測結果

2つのスレッドが2つのロックを逆の順序で取得する古典的な例(rally-A は table → net、 rally-B は net → table)を起動し、2秒後に jstack 1 を取得しました。

Found one Java-level deadlock:
=============================
"rally-A":
  waiting to lock monitor 0x0000ffff18001d30 (object 0x000000008bb13c80, a java.lang.Object),
  which is held by "rally-B"
"rally-B":
  waiting to lock monitor 0x0000ffff1c000f00 (object 0x000000008bb13c70, a java.lang.Object),
  which is held by "rally-A"
…
"rally-A":
	at Deadlock.grab(Deadlock.java:10)
	- waiting to lock <0x000000008bb13c80> (a java.lang.Object)
	- locked <0x000000008bb13c70> (a java.lang.Object)
…
Found 1 deadlock.

JVMが自ら循環参照を見つけ出します(公式のトラブルシューティングガイドの例と同じ形状)。2つのスレッドのCPU時間はそれぞれ1.01ms、0.60msであり、停止したサービスがCPUを使用していないこともわかります。実際の現場での停止は、デッドロックよりも 遅い何かを待っている状態(DBコネクションプールの枯渇、外部HTTP呼び出しのタイムアウト設定なし)の方がより一般的です。そのため、ダンプは 5〜10秒間隔で3回 取得し、同じスレッドが同じ行に留まり続けているかを確認します。1枚だけでは、たまたまその瞬間そこにいただけなのかどうか判断できません。

7. ケース2:CPU使用率が高い

1つのスレッド(referee-worker)だけが計算を実行し、残りの4つのスレッドはスリープする例です。手順は どのスレッドか → そのスレッドが何をしているかとなります。

  1. コンテナ内で top -H -p 1 を実行してスレッド別のCPU使用率を確認します。実測:TID 24が90.9%、名前は referee+(切り捨て)。
  2. jstack 1 でそのTIDを探します。スレッド行の nid がOSスレッド番号です。
"referee-worker" #20 [24] … cpu=3922.52ms elapsed=3.96s … nid=24 runnable
   java.lang.Thread.State: RUNNABLE
	at Hot.scoreRally(Hot.java:7)

実測したJDK 21は nid=24 のように 10進数で記載し、 [24] も併記して表示します。JDK 8・11時代のガイドでは nid=0x18 のような16進数だったため、TIDを16進数に変換して探すよう案内されています。そのため、使用しているバージョンに合わせて読み替える必要があります。

  1. スタックの1枚はあくまで瞬間的なものです。「概ねどこに時間を費やしているか」はJFRで確認します。稼働中のJVMに対して 再起動なしで 記録を開始しました。
$ jcmd 1 JFR.start name=hot duration=10s filename=/w/hot.jfr
$ jfr view hot-methods /w/hot.jfr
Method                  Samples Percent
Hot.scoreRally(long)        460 100.00%

10秒間の実行サンプル460個(デフォルト設定は約20ms間隔)がすべて同じメソッドでした。同じ記録の jfr view cpu-load ではJVMユーザーCPUの平均が9.02%でしたが、これは マシン全体(10コア)に対する 値であり、「1つのコアを完全に使い切っている」ことを意味します。パーセンテージが何に対する割合なのかを常に確認します。

async-profiler(最新のv4.6)は、JVMの内部API(AsyncGetCallTrace)とLinuxのperf_eventsを使用し、セーフポイントの偏りなしにJavaとネイティブのスタックを同時に取得できるツールです(公式README)。フレイムグラフを直接生成できるためCPUのトラブルシューティングに特に優れていますが、ダウンロードしてポッドに配置する必要があるため、今回は 未実測となります。

8. ケース3:メモリが増加し続ける

64KBのフレームをリストに追加し続けるだけで削除しない「キャッシュ」を、最大ヒープ64MBで起動しました。オプションは本番環境で推奨される組み合わせです。

java -Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/w/heap.hprof \
     -XX:+ExitOnOutOfMemoryError "-Xlog:gc*:file=/w/gc.log:uptime,level,tags" -cp classes Leak

-Xlog:gc* はJDK 9のUnified Logging(JEP 158)と統合GCロギング(JEP 271)によって導入されたフォーマットです。結果は以下の通りでした。

時刻 標準出力 / GCログ
1.9秒 Pause Young (Normal) 23M->22M(64M) 8.5ms — Young世代をクリアしたが、ほとんど減らなかった
2.3秒 Pause Full (Heap Inspection Initiated GC) 27M->26M — 自分が実行した GC.class_histogram が引き起こしたFull GC
3.2〜5.3秒 YoungとConcurrent Markが繰り返し実行され、使用量が36M → 59Mへと増加し続ける
5.45〜5.55秒 Evacuation Failure、続いて Pause Full (G1 Compaction Pause) 59M->59M が3回 — Full GCを実行しても1バイトも減らない
5.5秒 OutOfMemoryError: Java heap space、ヒープダンプ65,304,426バイトを0.073秒で出力、終了コード3

読み方は一つです。 Full GCの後でも使用量が下がらない場合、生きている何かがオブジェクトを保持し続けている。 これが単なるアロケーションの多さとメモリリークを切り分ける基準です。2秒時点のクラスヒストグラムは、犯人の姿まで示していました。 [B(バイト配列)の9,728個が26,419,600バイトで1位でした。ただし、そのコマンド自体がFull GCを引き起こしたことが実測として記録に残りました(2.3秒目の行)。大きなヒープを持つ本番サービスで「軽く一度確認してみよう」と実行するようなコマンドではありません。

ヒープダンプから「誰がそれらの配列を保持しているのか」(支配ツリー)を特定するにはEclipse MATやVisualVMなどの分析ツールが必要となるため、今回は 未実測です。実測により確認された本番運用上の事実は2つあります。ダンプファイルは ヒープサイズと同等の大きさ(64MBのヒープに対して65MB)になるため、1.4GBのヒープを持つポッドであれば同等の書き込み可能なボリュームが必要になります。また、ダンプにはメモリ上のすべてのデータ、すなわちユーザー情報、トークン、パスワードなどがそのまま含まれるため、機密情報として取り扱う必要があります。

9. JFR:常時有効にするフライトレコーダー

JFR(JDK Flight Recorder)はJDK 11でオープンソース化され(JEP 328)、デフォルト設定の目標オーバーヘッドは1%以下です。JDK 21の default.jfc は「本番環境での常時稼働も安全、通常1%未満」、 profile.jfc は「通常2%前後」と自ら記載しています。本当にそうであるか、ハッシュ計算と短命なオブジェクトのアロケーションを組み合わせた処理を指定回数(400万回)実行して測定しました。4つの設定を交互に7回ずつ、コンテナの2コアに制限して実行しました。

設定 メインループ時間の中央値(7回) 基準との比較
基準(オプションなし) 1,734ms —
JFR default 1,750ms +0.9%
JFR profile 1,753ms +1.1%
JDWPエージェントのみ(デバッガー未接続) 1,727ms −0.4%

基準となる7回の測定範囲が1,713〜1,784msとすでに±2%程度の変動があるため、上記の差は「ドキュメントにある1%・2%の範囲内」であるとしか言えません。処理の特性(アロケーションが非常に多い、ロック競合が激しいなど)によって異なるため、ご自身のサービスで一度測定してみることをお勧めします。

JFRファイルには環境変数が含まれている

上記の実測記録の jfr summary には jdk.InitialEnvironmentVariable が11件、 jdk.InitialSystemProperty が15件含まれており、 jfr print で開くと のキーと値がそのまま 露出していました。KubernetesのポッドはDBのパスワードやAPIキーを環境変数として受け取るケースが多いため、JFRファイルをチケットに添付したり外部に送信したりする前に削除する必要があります。JDK 21の jfr scrub がその処理を行います。

$ jfr scrub --exclude-events jdk.InitialEnvironmentVariable,jdk.InitialSystemProperty hot.jfr hot-clean.jfr

実測:整理されたファイルでは該当する2つのイベントが0件となり、実行サンプル460件はそのまま維持されていました(211,772バイト → 201,397バイト)。

本番環境でのJFRの活用パターン

10. 本番ポッドにおける手順と注意事項

症状別に最初に確認すべき事項をまとめると図2のようになります。手順の原則は 負荷の少ないものからです。

  1. すでに残っているデータ:アプリケーションログ、GCログ、メトリクス、有効化しておいたJFR。何も変更しません。
  2. 軽量な読み取り: VM.flags, GC.heap_info, VM.uptime。ここでヒープサイズの設定ミス(セクション5)を先に対象外とします。
  3. 短い一時停止: Thread.print を間隔を空けて3回実行。
  4. 記録: JFR.start duration=….
  5. 重い処理:ヒープダンプ、クラスヒストグラム。トラフィックを他のポッドに逃がすか(準備状態から外す)、 -XX:+HeapDumpOnOutOfMemoryError を使用してOOM発生時のみ自動的にダンプが取得されるように設定します。

そして、避けるべき事項です。

11. まとめ

T-iの一言。 デバッガーでラリーを止めると、ボールも、相手も、観客もすべて止まってしまいます。試合中はjcmdで写真だけを撮り、ビデオ(JFR)は常時オンにしておくのがベストです。あ、それからビデオにロッカールームのパスワード(環境変数)が写り込んでしまうので、共有する前に必ず消去してくださいね!

参考

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます