前置条件
账号注册
前往官方网站
观测云 - 云时代的系统可观测平台注册账号,使用已注册的账号/密码登录。
安装 Datakit
获取命令
点击 [集成] 模块, [DataKit],根据您的操作系统和系统类型选择合适的安装命令。
执行安装
复制 Datakit 安装命令在需要被监控的服务器上直接运行。
- 安装目录 /usr/local/datakit/
- 日志目录 /var/log/datakit/
- 主配置文件 /usr/local/datakit/conf.d/datakit.conf
- 插件配置目录 /usr/local/datakit/conf.d/
datakit默认已安装如下插件:
Datakit 安装完成后,已经默认开启 Linux 主机常用插件,可以在DF——基础设施——内置视图查看。
内置视图
点击 [基础设施] 模块,查看所有已安装 Datakit 的主机列表以及基础信息,如主机名,CPU,内存等。
点击 [主机名] 可以查看该主机的详细系统信息,集成运行情况 (该主机所有已安装的插件),内置视图(主机)。
点击 [集成运行情况] 任意插件名称 [查看监控视图] 可以看到该插件的内置视图。
JVM采集相关配置:
JAVA_OPTS声明
本示例使用ddtrace采集java应用的jvm指标,先根据您的需求定义JAVA_OPTS,在启动应用的时候替换JAVA_OPTS,启动jar方式如下:
java ${JAVA_OPTS} -jar your-app.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=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:JVM数据来源的应用名称,必填
-Ddd.agent.host=localhost DataKit地址
-Ddd.agent.port=9529 DataKit端口,必填
-Ddd.version:版本
-Ddd.jmxfetch.check-period 表示采集频率,单位为毫秒,必填
-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等别名
如需详细了解JVM,请参考
JVM采集器
1 jar使用方式
开启statsd
1. $ cd /usr/local/datakit/conf.d/statsd
2. $ cp statsd.conf.sample statsd.conf
开启ddtrace
1. $ cd /usr/local/datakit/conf.d/ddtrace
2. $ cp ddtrace.conf.sample ddtrace.conf
重启datakit
$ datakit --restart
启动jar,请用您的应用名替换下面的your-app,如果应用未连接mysql,请去掉-Ddd.service.mapping=mysql:mysql01,其中mysql01是观测云应用性能监控看到的mysql的别名
nohup java -Dfile.encoding=utf-8 \
-javaagent:/usr/local/datakit/data/dd-java-agent.jar \
-XX:FlightRecorderOptions=stackdepth=256 \
-Ddd.service=your-app \
-Ddd.service.mapping=mysql:mysql01 \
-Ddd.env=dev \
-Ddd.agent.port=9529 \
-Ddd.jmxfetch.enabled=true \
-Ddd.jmxfetch.check-period=1000 \
-Ddd.jmxfetch.statsd.port=8125 \
-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 --restart
请在您的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文件中
$ cd /usr/local/java
$ docker build -t your-app-image:v1 .
拷贝/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 -XX:FlightRecorderOptions=stackdepth=256 -Ddd.service=your-app -Ddd.service.mapping=mysql:mysql01 -Ddd.env=dev -Ddd.agent.host=172.16.0.215 -Ddd.agent.port=9529 -Ddd.jmxfetch.enabled=true -Ddd.jmxfetch.check-period=1000 -Ddd.jmxfetch.statsd.host=172.16.0.215 -Ddd.jmxfetch.statsd.port=8125 " --name your-app -d -p 9299:9299 your-app-image:v1
Docker compose启动
Dockerfile需要声明ARG参数来接收docker-compose传过来的参数,示例如下:
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 -XX:FlightRecorderOptions=stackdepth=256 -Ddd.logs.injection=true -Ddd.trace.sample.rate=1 -Ddd.service=your-app -Ddd.service.mapping=mysql:mysql01 -Ddd.env=dev -Ddd.agent.host=172.16.0.215 -Ddd.agent.port=9529 -Ddd.jmxfetch.enabled=true -Ddd.jmxfetch.check-period=1000 -Ddd.jmxfetch.statsd.host=172.16.0.215 -Ddd.jmxfetch.statsd.port=8125
ports:
networks:
- myNet
networks:
myNet:
driver: bridge
启动
$ cd /usr/local/java
#制作镜像
$ docker build -t your-app-image:v1 .
#启动
$ docker-compose up -d
3 Kubernetes 使用方式
3.1部署Datakit
在kubernetes中使用DaemonSet方式部署Datakit,如需详细了解,请参考
Datakit DaemonSet安装
新建/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:
- ""
resources:
- nodes
- nodes/proxy
- namespaces
- pods
- services
- endpoints
- persistentvolumes
- persistentvolumeclaims
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
- daemonsets
- statefulsets
- replicasets
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- batch
resources:
- jobs
- cronjobs
verbs:
- get
- list
- watch
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: datakit
namespace: datakit
---
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.dataflux.cn?token=<your-token>
- name: ENV_GLOBAL_TAGS
value: host=__datakit_hostname,host_ip=__datakit_ip
- name: ENV_ENABLE_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.jiagouyun.com/datakit/datakit:1.1.8-rc0
imagePullPolicy: Always
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/kubernetes/kubernetes.conf
name: datakit-conf
subPath: kubernetes.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
workingDir: /usr/local/datakit
hostIPC: true
hostNetwork: true
hostPID: true
restartPolicy: Always
serviceAccount: datakit
serviceAccountName: datakit
terminationGracePeriodSeconds: 30
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
updateStrategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: datakit-conf
namespace: datakit
data:
#### container
container.conf: |-
[inputs.container]
endpoint = "unix:///var/run/docker.sock"
enable_metric = true
enable_object = true
enable_logging = true
metric_interval = "10s"
## TLS Config
# tls_ca = "/path/to/ca.pem"
# tls_cert = "/path/to/cert.pem"
# tls_key = "/path/to/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
[inputs.container.kubelet]
kubelet_url = "http://127.0.0.1:10255"
## Use bearer token for authorization. ('bearer_token' takes priority)
## If both of these are empty, we'll use the default serviceaccount:
## at: /run/secrets/kubernetes.io/serviceaccount/token
# bearer_token = "/path/to/bearer/token"
## OR
# bearer_token_string = "abc_123"
## Optional TLS Config
# tls_ca = /path/to/ca.pem
# tls_cert = /path/to/cert.pem
# tls_key = /path/to/key.pem
## Use TLS but skip chain & host verification
# insecure_skip_verify = false
#[[inputs.container.logfilter]]
# filter_message = [
# '''<this-is-message-regexp''',
# '''<this-is-another-message-regexp''',
# ]
# source = "<your-source-name>"
# service = "<your-service-name>"
# pipeline = "<pipeline.p>"
[inputs.container.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
#### kubernetes
kubernetes.conf: |-
[[inputs.kubernetes]]
# required
interval = "10s"
## URL for the Kubernetes API
url = "https://kubernetes.default:443"
## Use bearer token for authorization. ('bearer_token' takes priority)
## at: /run/secrets/kubernetes.io/serviceaccount/token
bearer_token = "/run/secrets/kubernetes.io/serviceaccount/token"
## Set http timeout (default 5 seconds)
timeout = "5s"
## Optional TLS Config
tls_ca = "/run/secrets/kubernetes.io/serviceaccount/ca.crt"
## Use TLS but skip chain & host verification
insecure_skip_verify = false
[inputs.kubernetes.tags]
#tag1 = "val1"
#tag2 = "valn"
#### 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 = '''^\S'''
[inputs.logging.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
在
https://console.guance.com找到openway地址,如下图所示,替换datakit-default.yaml中的ENV_DATAWAY的值
部署Datakit
$ cd /usr/local/k8s
$ kubectl apply -f datakit-default.yaml
$ kubectl get pod -n 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.2sidecar镜像
在jar使用方式中使用到了dd-java-agent.jar,而在用户的镜像中并不一定存在这个jar,为了不侵入客户的业务镜像,我们需要制作一个包含dd-java-agent.jar的镜像,再以sidecar的方式先于业务容器启动,以共享存储的方式提供dd-java-agent.jar。
pubrepo.jiagouyun.com/datakit/dk-sidecar:1.0
3.3编写Java应用的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 -XX:FlightRecorderOptions=stackdepth=256 -Ddd.service=<your-app-name> -Ddd.tags=container_host:$(PODE_NAME) -Ddd.env=dev -Ddd.agent.port=9529 -Ddd.jmxfetch.enabled=true -Ddd.jmxfetch.check-period=1000 -Ddd.jmxfetch.statsd.port=8125 -Ddd.version=1.0
- 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 /usr/dd-java-agent/agent/* /ddtrace/agent;
image: pubrepo.jiagouyun.com/datakit/dk-sidecar:1.0
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为您的镜像名
1. $ cd /usr/local/k8s/
2. $ kubectl apply -f your-app-deployment-yaml
新建JVM可观测场景:
登录
观测云进入observer空间,点击新建场景
点击新建空白场景
输入场景名称JVM监控,点击确定
找到上图中的JVM监控视图,鼠标移到上面,点击创建
JVM监控视图如下:
JVM及相关指标介绍
1 JVM概述
1.1 什么是JVM
JVM是Java Virtual Machine的简称,是运行在操作系统之上,用来执行java字节码的虚拟计算机。
1.2 类的加载机制
首先java源文件会被java编译器编译成字节码,然后由JVM中的类加载器加载字节码,加载完成交给JVM执行引擎执行。
1.3 类的生命周期
一个Java类从开始到结束整个生命周期会经历7个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中验证、准备、解析三个部分又统称为连接(Linking)。
1.4 JVM内存结构
在整个类的加载过程中,JVM会用一段空间来存储数据和相关信息,这段空间就是我们常说的JVM内存。根据JVM规范,JVM内存共分为:
- 执行引擎
java是一种跨平台的编程语言,执行引擎就是把字节码解析成对应平台能识别的机器指令。
- 程序计数器
程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
特点:占用内存很小,可忽略不计;线程隔离;执行native本地方法时,程序计数器的值为空;此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
- 虚拟机栈
描述Java方法执行的内存模型,每个方法在执行时都会创建一个“栈帧(Stack Frame)”,即线程私有,生命周期和线程一致。栈帧的结构分为局部变量表、操作数栈、动态链接、方法出口几个部分。
我们常说的“堆内存、栈内存”中的“栈内存”指的便是虚拟机栈,确切地说,指的是虚拟机栈的栈帧中的局部变量表,因为这里存放了一个方法的所有局部变量。方法调用时,创建栈帧,并压入虚拟机栈;方法执行完毕,栈帧出栈并被销毁。
JVM会为每个线程的虚拟机栈分配一定的内存大小(-Xss参数),若单个线程请求的栈深度大于虚拟机允许的深度,则会抛出StackOverflowError(栈溢出错误)。当整个虚拟机栈内存耗尽,并且无法再申请到新的内存时抛出OutOfMemoryError异常。
- 本地方法栈
本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。
不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。如何去服务native方法?native方法使用什么语言实现?怎么组织像栈帧这种为了服务方法的数据结构?虚拟机规范并未给出强制规定,因此不同的虚拟机可以进行自由实现,我们常用的HotSpot虚拟机选择合并了虚拟机栈和本地方法栈。
- 方法区
JDK8废弃永久代,每个类的运行时常量池、编译后的代码移到了另一块与堆不相连的本地内存--元空间。
元空间(Metaspace):元空间是方法区的在 HotSpot JVM 中的实现,方法区主要用于存储类信息、常量池、方法数据、方法代码、符号引用等。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统内存大小,可以通过 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 配置内存大小。
元空间有两个参数:MetaspaceSize :初始化元空间大小,控制发生GC阈值。MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存。
- 堆
堆区由所有线程共享,主要是存放对象实例和数组。可以位于物理上不连续的空间,但是逻辑上要连续。
堆内存分为年轻代(Young Generation)、老年代(Old Generation)。年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出 OutOfMemoryError 异常。
JVM堆内存常用参数
- 运行时数据区
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都各有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。
根据《Java 虚拟机规范(Java SE 8版)》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。
- 直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。不会受到Java 堆大小的限制,受到本机总内存大小限制。
直接内存也用 -XX:MaxDirectMemorySize 指定,直接内存申请空间耗费更高的性能,直接内存IO读写的性能要优于普通的堆内存,耗尽内存抛出 OutOfMemoryError 异常。
- 垃圾回收
程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存。
垃圾收集器:
串行收集器(Serial)
并行收集器(Parallel)
CMS收集器(Concurrent Mark Sweep)
G1收集器(Garbage First)
垃圾回收算法(GC,Garbage Collection):
标记-清除(Mark-Sweep)
复制(Copy)
标记-整理(Mark-Compact)
1.5 GC、Full GC
为了分代垃圾回收,Java堆内存分为3代:年轻代,老年代和永久代。永久代是否执行GC,取决于采用的JVM。新生成的对象优先放到年轻代Eden区,大对象直接进入老年代,当Eden区没有足够空间时,会发起一次Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC(默认15次)仍然存活的对象移动到老年代。老年代存储长期存活的对象,当升到老年代的对象大于老年代剩余空间时会发生Major GC,当老年代空间不足时会引发Full GC。发生Major GC时用户线程会暂停,会降低系统性能和吞吐量。所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。如果GC后,还是无法存放从Survivor区复制过来的对象,就会出现OOM(Out of Memory)。
1.6 引发OutOfMemoryError原因
OOM(Out of Memory)异常常见有以下几个原因:1)老年代内存不足:java.lang.OutOfMemoryError:Javaheapspace2)永久代内存不足:java.lang.OutOfMemoryError:PermGenspace3)代码bug,占用内存无法及时回收。OOM在这几个内存区都有可能出现,实际遇到OOM时,能根据异常信息定位到哪个区的内存溢出。可以通过添加个参数-XX:+HeapDumpOnOutMemoryError,让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后分析。
1.7 JVM调优
熟悉了JAVA内存管理机制及配置参数,下面是对JAVA应用启动选项调优配置:
- 设置堆内存最小-Xms和最大值-Xmx相等,避免每次垃圾回收后重新分配内存
- 设置GC垃圾收集器为G1, -XX:+UseG1GC
- 启用GC日志,方便后期分析-Xloggc:../logs/gc.log