JVM 可観測のベストプラクティス¶
前提条件¶
公式サイト Guance にアクセスしてアカウントを登録し、登録したアカウント/パスワードでログインします。
DataKit のインストール¶
コマンドの取得¶
[統合] モジュール、[DataKit] をクリックし、OS とシステムタイプに応じて適切なインストールコマンドを選択します。
インストールの実行¶
DataKit のインストールコマンドをコピーし、監視対象のサーバー上で直接実行します。
- インストールディレクトリ
/usr/local/datakit/ - ログディレクトリ
/var/log/datakit/ - メイン設定ファイル
/usr/local/datakit/conf.d/datakit.conf - プラグイン設定ディレクトリ
/usr/local/datakit/conf.d/
DataKit にデフォルトでインストールされているプラグイン¶
DataKit のインストールが完了すると、Linux ホストの一般的なプラグインがデフォルトで有効になります。ワークスペース → インフラストラクチャーからホストの基本情報で確認できます。
| コレクター名 | 説明 |
|---|---|
| cpu | ホストの CPU 使用状況を収集 |
| disk | ディスク使用状況を収集 |
| diskio | ホストのディスク IO 状況を収集 |
| mem | ホストのメモリ使用状況を収集 |
| swap | Swap メモリ使用状況を収集 |
| system | ホスト OS の負荷を収集 |
| net | ホストのネットワークトラフィック状況を収集 |
| host_process | ホスト上の常駐(10分以上生存)プロセスリストを収集 |
| hostobject | ホストの基本情報(OS 情報、ハードウェア情報など)を収集 |
| docker | ホスト上のコンテナオブジェクトとコンテナログを収集 |
組み込みビュー¶
[インフラストラクチャー] モジュールをクリックし、DataKit がインストールされているすべてのホストのリストと基本情報(ホスト名、CPU、メモリなど)を確認します。
JVM 収集に関する設定:¶
JAVA_OPTS の宣言¶
本サンプルでは ddtrace を使用して Java アプリケーションの JVM メトリクスを収集します。まず要件に応じて JAVA_OPTS を定義し、アプリケーション起動時に JAVA_OPTS を置き換えます。jar の起動方法は以下の通りです。
完全な JAVA_OPTS は以下の通りです。
-javaagent:/usr/local/datakit/data/dd-java-agent.jar \
-XX:FlightRecorderOptions=stackdepth=256 \
-Ddd.profiling.enabled=true \
-Ddd.logs.injection=true \
-Ddd.trace.sample.rate=1 \
-Ddd.service.name=your-app-name \
-Ddd.env=dev \
-Ddd.agent.port=9529 \
-Ddd.jmxfetch.enabled=true \
-Ddd.jmxfetch.check-period=1000 \
-Ddd.jmxfetch.statsd.port=8125 \
-Ddd.trace.health.metrics.enabled=true \
-Ddd.trace.health.metrics.statsd.port=8125 \
詳細説明:
-Ddd.env:アプリケーションの環境タイプ、任意
-Ddd.tags:カスタムタグ、任意
-Ddd.service.name: JVM データの送信元アプリケーション名、必須
-Ddd.agent.host=localhost DataKit アドレス、任意
-Ddd.agent.port=9529 DataKit ポート、必須
-Ddd.version:バージョン、任意
-Ddd.jmxfetch.check-period 収集頻度(ミリ秒単位)、デフォルト 1500、任意
-Ddd.jmxfetch.statsd.host=127.0.0.1 statsd コレクターの接続アドレス(DataKit アドレスと同じ)、任意
-Ddd.jmxfetch.statsd.port=8125 DataKit 上の statsd コレクターの UDP 接続ポート、デフォルト 8125、任意
-Ddd.trace.health.metrics.statsd.host=127.0.0.1 自身のメトリクスデータ収集送信アドレス(DataKit アドレスと同じ)、任意
-Ddd.trace.health.metrics.statsd.port=8125 自身のメトリクスデータ収集送信ポート、任意
-Ddd.service.mapping:アプリケーションが呼び出す redis、mysql などのエイリアス、任意
1. jar の使用方式¶
statsd を有効にする
ddtrace を有効にする
datakit を再起動する
jar を起動します。以下の your-app は実際のアプリケーション名に置き換えてください。アプリケーションが mysql に接続していない場合は、-Ddd.service.mapping=mysql:mysql01 を削除してください。mysql01 は DataFlux アプリケーションパフォーマンスモニタリングで表示される mysql のエイリアスです。
nohup java -Dfile.encoding=utf-8 \
-javaagent:/usr/local/datakit/data/dd-java-agent.jar \
-Ddd.service.name=your-app \
-Ddd.service.mapping=mysql:mysql01 \
-Ddd.env=dev \
-Ddd.agent.port=9529 \
-jar your-app.jar > logs/your-app.log 2>&1 &
2. Docker の使用方式¶
jar の使用方式に従って statsd を有効にし、ddtrace を有効にします。
外部ネットワークアクセス用ポートを開放する
/usr/local/datakit/conf.d/vim datakit.conf ファイルを編集し、listen = "0.0.0.0:9529" に変更します。
datakit を再起動する
Dockerfile の ENTRYPOINT 起動パラメータで環境変数 JAVA_OPTS を使用してください。Dockerfile ファイルの例は以下の通りです。
FROM openjdk:8u292-jdk
ENV jar your-app.jar
ENV workdir /data/app/
RUN mkdir -p ${workdir}
COPY ${jar} ${workdir}
WORKDIR ${workdir}
ENTRYPOINT ["sh", "-ec", "exec java ${JAVA_OPTS} -jar ${jar} "]
イメージを作成する
上記の内容を /usr/local/java/Dockerfile ファイルに保存します。
/usr/local/datakit/data/dd-java-agent.jar を /tmp/work ディレクトリにコピーします。
Docker run で起動:172.16.0.215 を実際のサーバーのプライベート IP アドレスに、9299 をアプリケーションのポートに、your-app をアプリケーション名に、your-app-image:v1 をイメージ名に置き換えてください。
docker run -v /tmp/work:/tmp/work -e JAVA_OPTS="-javaagent:/tmp/work/dd-java-agent.jar -Ddd.service.name=your-app -Ddd.service.mapping=mysql:mysql01 -Ddd.env=dev -Ddd.agent.host=172.16.0.215 -Ddd.agent.port=9529 -Ddd.jmxfetch.statsd.host=172.16.0.215 " --name your-app -d -p 9299:9299 your-app-image:v1
Dockerfile では、docker-compose から渡されるパラメータを受け取るために ARG パラメータを宣言する必要があります。例は以下の通りです。
FROM openjdk:8u292-jdk
ARG JAVA_ARG
ENV JAVA_OPTS=$JAVA_ARG
ENV jar your-app.jar
ENV workdir /data/app/
RUN mkdir -p ${workdir}
COPY ${jar} ${workdir}
WORKDIR ${workdir}
ENTRYPOINT ["sh", "-ec", "exec java ${JAVA_OPTS} -jar ${jar} "]
上記の内容を /usr/local/java/DockerfileTest ファイルに保存し、同じディレクトリに docker-compose.yml ファイルを作成します。172.16.0.215 を実際のサーバーのプライベート IP アドレスに、9299 をアプリケーションのポートに、your-app をアプリケーション名に、your-app-image:v1 をイメージ名に置き換えてください。docker-compose.yml の例は以下の通りです。
version: "3.9"
services:
ruoyi-gateway:
image: your-app-image:v1
container_name: your-app
volumes:
- /tmp/work:/tmp/work
build:
dockerfile: DockerfileTest
context: .
args:
- JAVA_ARG=-javaagent:/tmp/work/dd-java-agent.jar -Ddd.service.name=your-app -Ddd.service.mapping=mysql:mysql01 -Ddd.env=dev -Ddd.agent.host=172.16.0.215 -Ddd.agent.port=9529 -Ddd.jmxfetch.statsd.host=172.16.0.215
ports:
networks:
- myNet
networks:
myNet:
driver: bridge
起動
3 Kubernetes の使用方式¶
3.1 DataKit のデプロイ¶
Kubernetes で DaemonSet 方式を使用して DataKit をデプロイする方法については、<Datakit DaemonSet インストール> を参照してください。
JVM メトリクスを収集するには、ddtrace と statsd コレクターを有効にする必要があります。DaemonSet 方式でデプロイされた DataKit では、yaml ファイルの ENV_DEFAULT_ENABLED_INPUTS 環境変数に statsd, ddtrace を追加します。
- name: ENV_DEFAULT_ENABLED_INPUTS
value: cpu,disk,diskio,mem,swap,system,hostobject,net,host_processes,kubernetes,container,statsd,ddtrace
本サンプルのデプロイファイルは /usr/local/k8s/datakit-default.yaml で、内容は以下の通りです。
apiVersion: v1
kind: Namespace
metadata:
name: datakit
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: datakit
rules:
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
- nodes/proxy
- namespaces
- pods
- pods/log
- events
- services
- endpoints
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
- daemonsets
- statefulsets
- replicasets
verbs:
- get
- list
- watch
- apiGroups:
- batch
resources:
- jobs
- cronjobs
verbs:
- get
- list
- watch
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: datakit
namespace: datakit
---
apiVersion: v1
kind: Service
metadata:
name: datakit-service
namespace: datakit
spec:
selector:
app: daemonset-datakit
ports:
- protocol: TCP
port: 9529
targetPort: 9529
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: datakit
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: datakit
subjects:
- kind: ServiceAccount
name: datakit
namespace: datakit
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app: daemonset-datakit
name: datakit
namespace: datakit
spec:
revisionHistoryLimit: 10
selector:
matchLabels:
app: daemonset-datakit
template:
metadata:
labels:
app: daemonset-datakit
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- env:
- name: HOST_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
- name: ENV_DATAWAY
value: https://openway.guance.com?token=<your-token>
- name: ENV_GLOBAL_HOST_TAGS
value: host=__datakit_hostname,host_ip=__datakit_ip,cluster_name_k8s=k8s-prod
- name: ENV_DEFAULT_ENABLED_INPUTS
value: cpu,disk,diskio,mem,swap,system,hostobject,net,host_processes,kubernetes,container,statsd,ddtrace
- name: ENV_ENABLE_ELECTION
value: enable
- name: ENV_HTTP_LISTEN
value: 0.0.0.0:9529
- name: ENV_LOG_LEVEL
value: info
image: pubrepo.guance.com/datakit/datakit:1.2.1
imagePullPolicy: IfNotPresent
name: datakit
ports:
- containerPort: 9529
hostPort: 9529
name: port
protocol: TCP
securityContext:
privileged: true
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-socket
readOnly: true
- mountPath: /usr/local/datakit/conf.d/container/container.conf
name: datakit-conf
subPath: container.conf
- mountPath: /usr/local/datakit/conf.d/log/logging.conf
name: datakit-conf
subPath: logging.conf
- mountPath: /host/proc
name: proc
readOnly: true
- mountPath: /host/dev
name: dev
readOnly: true
- mountPath: /host/sys
name: sys
readOnly: true
- mountPath: /rootfs
name: rootfs
- mountPath: /sys/kernel/debug
name: debugfs
workingDir: /usr/local/datakit
hostIPC: true
hostPID: true
restartPolicy: Always
serviceAccount: datakit
serviceAccountName: datakit
volumes:
- configMap:
name: datakit-conf
name: datakit-conf
- hostPath:
path: /var/run/docker.sock
name: docker-socket
- hostPath:
path: /proc
type: ""
name: proc
- hostPath:
path: /dev
type: ""
name: dev
- hostPath:
path: /sys
type: ""
name: sys
- hostPath:
path: /
type: ""
name: rootfs
- hostPath:
path: /sys/kernel/debug
type: ""
name: debugfs
updateStrategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: datakit-conf
namespace: datakit
data:
#### container
container.conf: |-
[inputs.container]
docker_endpoint = "unix:///var/run/docker.sock"
containerd_address = "/var/run/containerd/containerd.sock"
enable_container_metric = true
enable_k8s_metric = true
enable_pod_metric = false
extract_k8s_label_as_tags = false
## Auto-Discovery of PrometheusMonitoring Annotations/CRDs
enable_auto_discovery_of_prometheus_pod_annotations = false
enable_auto_discovery_of_prometheus_service_annotations = false
enable_auto_discovery_of_prometheus_pod_monitors = false
enable_auto_discovery_of_prometheus_service_monitors = false
## Containers logs to include and exclude, default collect all containers. Globs accepted.
container_include_log = []
container_exclude_log = ["image:*logfwd*", "image:*datakit*"]
exclude_pause_container = true
## Removes ANSI escape codes from text strings
logging_remove_ansi_escape_codes = false
## Search logging interval, default "60s"
#logging_search_interval = ""
## If the data sent failure, will retry forevery
logging_blocking_mode = true
kubernetes_url = "https://kubernetes.default:443"
## Authorization level:
## bearer_token -> bearer_token_string -> TLS
## Use bearer token for authorization. ('bearer_token' takes priority)
## linux at: /run/secrets/kubernetes.io/serviceaccount/token
## windows at: C:\var\run\secrets\kubernetes.io\serviceaccount\token
bearer_token = "/run/secrets/kubernetes.io/serviceaccount/token"
# bearer_token_string = "<your-token-string>"
logging_auto_multiline_detection = true
logging_auto_multiline_extra_patterns = []
## Set true to enable election for k8s metric collection
election = true
[inputs.container.logging_extra_source_map]
# source_regexp = "new_source"
[inputs.container.logging_source_multiline_map]
# source = '''^\d{4}'''
[inputs.container.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
#### logging
logging.conf: |-
[[inputs.logging]]
## required
logfiles = [
"/rootfs/var/log/k8s/demo-system/info.log",
"/rootfs/var/log/k8s/demo-system/error.log",
]
## glob filteer
ignore = [""]
## your logging source, if it's empty, use 'default'
source = "k8s-demo-system"
## add service tag, if it's empty, use $source.
service = "k8s-demo-system"
## grok pipeline script path
#pipeline = ""
## optional status:
## "emerg","alert","critical","error","warning","info","debug","OK"
ignore_status = []
## optional encodings:
## "utf-8", "utf-16le", "utf-16le", "gbk", "gb18030" or ""
character_encoding = ""
## The pattern should be a regexp. Note the use of '''this regexp'''
## regexp link: https://golang.org/pkg/regexp/syntax/#hdr-Syntax
match = '''^\d{4}-\d{2}-\d{2}'''
[inputs.logging.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
https://console.guance.com/ で openway アドレスを確認し、下図のように datakit-default.yaml の ENV_DATAWAY の値を置き換えます。
Datakit をデプロイする
本サンプルでシステムログを収集する場合は、以下の内容を参照してください。
#- mountPath: /usr/local/datakit/conf.d/log/demo-system.conf
# name: datakit-conf
# subPath: demo-system.conf
#### kubernetes
demo-system.conf: |-
[[inputs.logging]]
## required
logfiles = [
"/rootfs/var/log/k8s/demo-system/info.log",
"/rootfs/var/log/k8s/demo-system/error.log",
]
## glob filteer
ignore = [""]
## your logging source, if it's empty, use 'default'
source = "k8s-demo-system"
## add service tag, if it's empty, use $source.
service = "k8s-demo-system"
## grok pipeline script path
pipeline = ""
## optional status:
## "emerg","alert","critical","error","warning","info","debug","OK"
ignore_status = []
## optional encodings:
## "utf-8", "utf-16le", "utf-16le", "gbk", "gb18030" or ""
character_encoding = ""
## The pattern should be a regexp. Note the use of '''this regexp'''
## regexp link: https://golang.org/pkg/regexp/syntax/#hdr-Syntax
match = '''^\S'''
[inputs.logging.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
3.2 sidecar イメージ¶
jar の使用方式では dd-java-agent.jar を使用していますが、ユーザーのイメージにこの jar が存在するとは限りません。お客様の業務イメージに影響を与えないために、dd-java-agent.jar を含むイメージを作成し、sidecar 方式で業務コンテナより先に起動し、共有ストレージとして dd-java-agent.jar を提供します。
3.3 Java アプリケーションの Dockerfile を作成する¶
Dockerfile の ENTRYPOINT 起動パラメータで環境変数 JAVA_OPTS を使用してください。Dockerfile ファイルの例は以下の通りです。
FROM openjdk:8u292
ENV jar your-app.jar
ENV workdir /data/app/
RUN mkdir -p ${workdir}
COPY ${jar} ${workdir}
WORKDIR ${workdir}
ENTRYPOINT ["sh", "-ec", "exec java ${JAVA_OPTS} -jar ${jar}"]
イメージを作成して harbor リポジトリにアップロードします。以下の 172.16.0.215:5000/dk は実際のイメージリポジトリに置き換えてください。
$ cd /usr/local/k8s/agent
$ docker build -t 172.16.0.215:5000/dk/your-app-image:v1 .
$ docker push 172.16.0.215:5000/dk/your-app-image:v1
3.4 deployment を作成する¶
/usr/local/k8s/your-app-deployment-yaml ファイルを新規作成し、内容は以下の通りです。
apiVersion: v1
kind: Service
metadata:
name: your-app-name
labels:
app: your-app-name
spec:
selector:
app: your-app-name
ports:
- protocol: TCP
port: 9299
nodePort: 30001
targetPort: 9299
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: your-app-name
labels:
app: your-app-name
spec:
replicas: 1
selector:
matchLabels:
app: your-app-name
template:
metadata:
labels:
app: your-app-name
spec:
containers:
- env:
- name: PODE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JAVA_OPTS
value: |-
-javaagent:/usr/dd-java-agent/agent/dd-java-agent.jar -Ddd.service.name=<your-app-name> -Ddd.tags=container_host:$(PODE_NAME) -Ddd.env=dev -Ddd.agent.port=9529
- name: DD_AGENT_HOST
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
name: your-app-name
image: 172.16.0.215:5000/dk/your-app-image:v1
#command: ["sh","-c"]
ports:
- containerPort: 9299
protocol: TCP
volumeMounts:
- mountPath: /usr/dd-java-agent/agent
name: ddagent
initContainers:
- command:
- sh
- -c
- set -ex;mkdir -p /ddtrace/agent;cp -r /datadog-init/* /ddtrace/agent;
image: pubrepo.guance.com/datakit-operator/dd-lib-java-init
imagePullPolicy: Always
name: ddtrace-agent-sidecar
volumeMounts:
- mountPath: /ddtrace/agent
name: ddagent
restartPolicy: Always
volumes:
- emptyDir: {}
name: ddagent
説明:JAVA_OPTS 内の -Ddd.tags=container_host:$(PODE_NAME) は、環境変数 PODE_NAME の値をタグ container_host に渡します。9299 はアプリケーションのポートに、your-app-name はサービス名に、30001 はアプリケーションの外部公開ポートに、172.16.0.215:5000/dk/your-app-image:v1 はイメージ名に置き換えてください。
起動
JVM 可観測シナリオの新規作成:¶
Guance にログインしてワークスペースに入り、[シナリオを作成] をクリックします。
[JVM 監視シナリオ] をクリックします。
シナリオ名 [JVM 監視シナリオ] を入力し、[確定] をクリックします。
上図の JVM 監視ビューを見つけ、マウスをその上に移動して、[作成] をクリックします。
JVM 監視ビューは以下の通りです。
JVM および関連メトリクスの紹介¶
1 JVM の概要¶
1.1 JVM とは¶
JVM は Java Virtual Machine の略称で、オペレーティングシステム上で動作し、Java バイトコードを実行する仮想コンピュータです。
1.2 クラスのロード機構¶
まず Java ソースファイルは Java コンパイラによってバイトコードにコンパイルされ、次に JVM 内のクラスローダーがバイトコードをロードします。ロードが完了すると、JVM 実行エンジンに渡されて実行されます。
1.3 クラスのライフサイクル¶
Java クラスは開始から終了までのライフサイクル全体で、ローディング(Loading)、検証(Verification)、準備(Preparation)、解決(Resolution)、初期化(Initialization)、使用(Using)、アンロード(Unloading)の7つの段階を経ます。このうち、検証、準備、解決の3つの部分はまとめてリンク(Linking)と呼ばれます。
1.4 JVM メモリ構造¶
クラスのロードプロセス全体を通じて、JVM はデータや関連情報を格納するための領域を使用します。この領域が、一般的に JVM メモリと呼ばれるものです。JVM 仕様によると、JVM メモリは次のように分類されます。
- 実行エンジン
Java はクロスプラットフォームのプログラミング言語であり、実行エンジンはバイトコードを対応するプラットフォームが認識できるマシン命令に解析します。
- プログラムカウンタ
プログラムカウンタは比較的小さなメモリ領域で、その役割は現在のスレッドが実行しているバイトコードの行番号インジケータと考えることができます。仮想マシンの概念モデルでは、バイトコードインタプリタはこのカウンタの値を変更して次に実行するバイトコード命令を選択します。分岐、ループ、ジャンプ、例外処理、スレッド復元などの基本機能はすべてこのカウンタに依存しています。
特徴:占有メモリが非常に小さく、ほぼ無視できる。スレッド分離。ネイティブメソッドを実行する場合、プログラムカウンタの値は空になる。このメモリ領域は、Java 仮想マシン仕様で OutOfMemoryError が規定されていない唯一の領域です。
- 仮想マシンスタック
Java メソッド実行のメモリモデルを記述します。各メソッドの実行時に「スタックフレーム(Stack Frame)」が作成され、スレッドプライベートであり、ライフサイクルはスレッドと一致します。スタックフレームの構造は、ローカル変数テーブル、オペランドスタック、動的リンク、メソッド出口などの部分に分かれています。
一般的に「ヒープメモリ、スタックメモリ」と呼ばれる「スタックメモリ」は、仮想マシンスタックを指し、正確には仮想マシンスタックのスタックフレーム内のローカル変数テーブルを指します。ここにはメソッドのすべてのローカル変数が格納されるためです。メソッド呼び出し時にスタックフレームが作成され、仮想マシンスタックにプッシュされます。メソッドの実行が完了すると、スタックフレームはポップされて破棄されます。
JVM は各スレッドの仮想マシンスタックに一定のメモリサイズ(-Xss パラメータ)を割り当てます。単一スレッドが要求するスタック深度が仮想マシンで許可される深度を超えると、StackOverflowError(スタックオーバーフローエラー)がスローされます。仮想マシンスタック全体のメモリが枯渇し、新しいメモリを取得できなくなると、OutOfMemoryError 例外がスローされます。
- ネイティブメソッドスタック
ネイティブメソッドスタックの機能と特徴は仮想マシンスタックと類似しており、どちらもスレッド分離の特性を持ち、StackOverflowError および OutOfMemoryError 例外をスローする可能性があります。
異なる点は、ネイティブメソッドスタックがサービスを提供する対象は JVM が実行するネイティブメソッドであるのに対し、仮想マシンスタックは JVM が実行する Java メソッドであることです。ネイティブメソッドにどのようにサービスを提供するか、ネイティブメソッドがどの言語で実装されるか、スタックフレームのようなメソッドサービス用のデータ構造をどのように構成するかについては、仮想マシン仕様は強制規定を設けていないため、仮想マシンごとに自由に実装できます。一般的に使用される HotSpot 仮想マシンは、仮想マシンスタックとネイティブメソッドスタックを統合することを選択しています。
- メソッド領域
JDK8 では Permanent Generation が廃止され、各クラスの実行時定数プール、コンパイル後のコードはヒープとは接続されていない別のネイティブメモリ領域、すなわち Metaspace に移動されました。
Metaspace:Metaspace は HotSpot JVM におけるメソッド領域の実装です。メソッド領域は主にクラス情報、定数プール、メソッドデータ、メソッドコード、シンボル参照などを格納するために使用されます。Metaspace の本質は Permanent Generation と類似しており、どちらも JVM 仕様のメソッド領域の実装です。ただし、Metaspace と Permanent Generation の最大の違いは、Metaspace が仮想マシン内ではなく、ネイティブメモリを使用することです。理論的には32ビット/64ビットシステムのメモリサイズに依存し、-XX:MetaspaceSize および -XX:MaxMetaspaceSize でメモリサイズを設定できます。
Metaspace には2つのパラメータがあります:MetaspaceSize:Metaspace の初期サイズ、GC 発生のしきい値を制御します。MaxMetaspaceSize:Metaspace サイズの上限を制限し、異常な物理メモリ過剰使用を防ぎます。
- ヒープ
ヒープ領域はすべてのスレッドで共有され、主にオブジェクトインスタンスと配列を格納します。物理的に連続していない空間に配置することもできますが、論理的には連続している必要があります。
ヒープメモリは Young Generation と Old Generation に分かれます。Young Generation はさらに Eden 領域と Survivor 領域に分かれます。Survivor 領域は FromSpace と ToSpace で構成されます。Eden 領域が大きな容量を占め、Survivor の2つの領域は小さな容量を占め、デフォルトの比率は8:1:1です。
Java ヒープでインスタンス割り当てに十分なメモリがなく、ヒープを拡張できない場合、Java 仮想マシンは OutOfMemoryError 例外をスローします。
JVM ヒープメモリのよく使われるパラメータ
| パラメータ | 説明 |
|---|---|
| -Xms | ヒープメモリの初期サイズ、単位は m、g |
| -Xmx(MaxHeapSize) | ヒープメモリの最大許容サイズ、通常は物理メモリの80%を超えないようにする |
| -XX:PermSize | 非ヒープメモリの初期サイズ、一般的なアプリケーションでは初期値200m、最大1024mで十分 |
| -XX:MaxPermSize | 非ヒープメモリの最大許容サイズ |
| -XX:NewSize(-Xns) | Young Generation メモリの初期サイズ |
| -XX:MaxNewSize(-Xmn) | Young Generation メモリの最大許容サイズ、省略形も可 |
| -XX:SurvivorRatio=8 | Young Generation 内の Eden 領域と Survivor 領域の容量比率、デフォルトは8(8:1) |
| -Xss | スタックメモリサイズ |
- 実行時データ領域
Java 仮想マシンは Java プログラムの実行中に、管理するメモリをいくつかの異なるデータ領域に分割します。これらの領域にはそれぞれの用途と作成・破棄のタイミングがあり、ある領域は仮想マシンプロセスの起動とともに存在し、別の領域はユーザースレッドの開始と終了に依存して作成・破棄されます。
『Java 仮想マシン仕様(Java SE 8版)』の規定によると、Java 仮想マシンが管理するメモリには、プログラムカウンタ、Java 仮想マシンスタック、ネイティブメソッドスタック、Java ヒープ、メソッド領域の5つの実行時データ領域が含まれます。
- ダイレクトメモリ
ダイレクトメモリは仮想マシンの実行時データ領域の一部ではなく、Java 仮想マシン仕様で定義されたメモリ領域でもありません。Java ヒープサイズの制限を受けず、ホストの総メモリサイズの制限を受けます。
ダイレクトメモリは -XX:MaxDirectMemorySize でも指定されます。ダイレクトメモリの領域確保にはより高いパフォーマンスコストがかかり、ダイレクトメモリの IO 読み書きパフォーマンスは通常のヒープメモリよりも優れています。メモリを使い果たすと OutOfMemoryError 例外がスローされます。
- ガベージコレクション
プログラムカウンタ、仮想マシンスタック、ネイティブメソッドスタックの3つの領域はスレッドの生成と消滅に伴います(スレッドプライベートのため)。スタック内のスタックフレームはメソッドの開始と終了に伴って秩序正しくプッシュおよびポップ操作が実行されます。一方、Java ヒープとメソッド領域は異なります。インターフェースの複数の実装クラスで必要なメモリが異なる場合や、メソッド内の複数の分岐で必要なメモリが異なる場合があります。プログラムが実行時にどのオブジェクトが作成されるかはその時にならないとわかりません。この部分のメモリの割り当てと回収は動的に行われ、ガベージコレクションはこの部分のメモリに注目します。
ガベージコレクタ:
シリアルコレクタ(Serial)
パラレルコレクタ(Parallel)
CMS コレクタ(Concurrent Mark Sweep)
G1 コレクタ(Garbage First)
ガベージコレクション(GC)アルゴリズム:
マークスイープ(Mark-Sweep)
コピー(Copy)
マークコンパクト(Mark-Compact)
1.5 GC、Full GC¶
世代別ガベージコレクションのために、Java ヒープメモリは Young Generation、Old Generation、および Permanent Generation の3世代に分けられます。Permanent Generation で GC が実行されるかどうかは、使用する JVM に依存します。新しく生成されたオブジェクトは優先的に Young Generation の Eden 領域に配置され、大きなオブジェクトは直接 Old Generation に移動します。Eden 領域に十分な空きがない場合、Minor GC が発生し、生存しているオブジェクトは Survivor0 領域に移動します。Survivor0 領域がいっぱいになると Minor GC がトリガーされ、Survivor0 領域の生存オブジェクトは Survivor1 領域に移動します。これにより、一定期間内に常に1つの Survivor 領域が空になります。複数回の Minor GC(デフォルト15回)を経ても生存しているオブジェクトは Old Generation に移動します。Old Generation は長期間生存するオブジェクトを格納します。Old Generation に昇格するオブジェクトが Old Generation の残り容量を超えると Major GC が発生し、Old Generation の空き容量が不足すると Full GC が発生します。Major GC が発生するとユーザースレッドが一時停止し、システムパフォーマンスとスループットが低下します。そのため、応答性が要求されるアプリケーションでは Major GC の発生を最小限に抑え、応答タイムアウトを回避する必要があります。GC 後も Survivor 領域からコピーされたオブジェクトを格納できない場合は、OOM(Out of Memory)が発生します。
1.6 OutOfMemoryError の原因¶
OOM(Out of Memory)例外の一般的な原因は以下の通りです。
1)Old Generation のメモリ不足:java.lang.OutOfMemoryError:Javaheapspace
2)Permanent Generation のメモリ不足:java.lang.OutOfMemoryError:PermGenspace
3)コードのバグにより、占有メモリが適時に回収されない。OOM はこれらのメモリ領域のどこでも発生する可能性があります。実際に OOM が発生した場合、例外情報からどの領域のメモリオーバーフローかを特定できます。パラメータ -XX:+HeapDumpOnOutMemoryError を追加すると、メモリオーバーフロー例外発生時に仮想マシンが現在のメモリヒープダンプスナップショットを出力し、事後分析に役立てることができます。
1.7 JVM チューニング¶
JAVA メモリ管理メカニズムと設定パラメータを理解したところで、以下に JAVA アプリケーション起動オプションのチューニング設定を示します。
1 ヒープメモリの最小値 -Xms と最大値 -Xmx を等しく設定し、ガベージコレクションごとのメモリ再割り当てを回避します。
2 GC ガベージコレクタを G1 に設定します:-XX:+UseG1GC
3 GC ログを有効にして後期の分析に役立てます:-Xloggc:../logs/gc.log
2 組み込みビュー¶
3 パフォーマンスメトリクス¶
| メトリクス | 説明 | データ型 | 単位 |
|---|---|---|---|
| buffer_pool_direct_capacity | ダイレクトバッファの総サイズ | int | Byte |
| buffer_pool_direct_count | ダイレクトバッファの数 | int | count |
| buffer_pool_direct_used | ダイレクトバッファの使用済みサイズ | int | Byte |
| buffer_pool_mapped_capacity | メモリマップドバッファの総サイズ | int | Byte |
| buffer_pool_mapped_count | メモリマップドバッファの数 | int | count |
| buffer_pool_mapped_used | メモリマップドバッファの使用サイズ | int | Byte |
| cpu_load_process | プロセスの CPU 使用率 | 小数 | パーセンテージ |
| cpu_load_system | システムの CPU 使用率 | 小数 | パーセンテージ |
| gc_eden_size | Young Generation の Eden 領域サイズ | int | Byte |
| gc_survivor_size | Young Generation の Survivor 領域サイズ | int | Byte |
| gc_old_gen_size | Old Generation サイズ | int | Byte |
| gc_metaspace_size | Metaspace サイズ | int | Byte |
| gc_major_collection_count | Old Generation の GC 回数 | int | count |
| gc_major_collection_time | Old Generation の GC 所要時間 | int | ms |
| gc_minor_collection_count | Young Generation の GC 回数 | int | count |
| gc_minor_collection_time | Young Generation の GC 所要時間 | int | ms |
| heap_memory_committed | ヒープメモリのコミット済みバイト数 | int | Byte |
| heap_memory_init | ヒープメモリの初期バイト数 | int | Byte |
| heap_memory_max | ヒープメモリの最大バイト数 | int | Byte |
| heap_memory | ヒープメモリの使用バイト数 | int | Byte |
| loaded_classes | ロードされたクラス数 | int | count |
| non_heap_memory_committed | 非ヒープメモリのコミット済みバイト数 | int | Byte |
| non_heap_memory_init | 非ヒープメモリの初期バイト数 | int | Byte |
| non_heap_memory_max | 非ヒープメモリの最大バイト数 | int | Byte |
| non_heap_memory | 非ヒープメモリの使用バイト数 | int | Byte |
| os_open_file_descriptors | 開かれているファイルディスクリプタ数 | int | count |
| thread_count | スレッド総数 | int | count |












