JAVA语言之明白生产环境中的jvm参数
小标 2019-03-04 来源 : 阅读 580 评论 0

摘要:本文主要向大家介绍了JAVA语言之明白生产环境中的jvm参数,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

本文主要向大家介绍了JAVA语言之明白生产环境中的jvm参数,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

JAVA语言之明白生产环境中的jvm参数

明白生产环境中的jvm参数


写代码的时候,程序写完了,发到线上去运行,跑一段时间后,程序变慢了,cpu负载高了……一堆问题出来了,所以了解一下生产环境的机器上的jvm配置是有必要的。比如说:


JDK版本是多少?采用何种垃圾回收器?
程序启动的时候默认分配堆内存空间是多少?随着程序的运行,程序最多能使用多大的内存空间?
程序中使用了多少个线程?这些线程又处于何种状态?
了解了这些,会对程序的运行有一个更好的了解。本文结合生产实践,记录一下我常用的一些操作。


注意:如果没有特殊说明,下面所有的参数讨论都是基于JDK8 server class machine 而言的


根据官方调优文档,server类型的机器满足以下要求:


A class of machine referred to as a server-class machine has been defined as a machine with the following:


2 or more physical processors
2 or more GB of physical memory
我的理解:就是这台机器 有两个以上的物理处理器,并且 具有2G或2G以上的内存,那么就是 server 类型的机器


可通过这个命令查看机器的物理处理器核数:


cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l


可通过这个命令查看机器的总内存大小:


cat /proc/meminfo | grep MemTotal


当写完一个Spring boot Maven 工程,使用 mvn clean package 打包成可运行的jar文件后,可使用如下命令开始执行:


nohup java -Xloggc:${logging_file_location}gc.log -XX:+PrintGCDetails -jar app.jar --spring.profiles.active=${environment} --logging.file.location=${logging_file_location} --domain=com.xx.xxx.xxxx > /dev/null 2>&1 &
-Xloggc: 指定程序运行过程中产生的 GC 日志输出到 gc.log 文件中。
-XX:+PrintGCDetails 指定 输出详细的GC日志。
spring.profiles.active=${environment} 可根据 environment变量来选择是生产环境还是测试环境。有时生产环境中使用的数据源(比如 Mysql)与测试环境不一样,这样就很方便。
--logging.file.location指定程序输出的日志
--domain 这个参数主要用来对程序进行标识。比如,使用 ps aux | grep com.xx.xxx.xxxx 就能方便地找到程序的进程号了。
查看GC收集器


JDK版本号一般很容易知道,java --version就行了。那如何知道运行你的程序的JAVA虚拟机采用何种垃圾回收器呢?


其实可以从gc日志里面看出 jvm 使用的何种垃圾收集器。我安装的JDK8,server 类型的机器,新生代默认使用的是:parallel scavenge,而老年代默认使用:ParOldgen 垃圾收集器。而看JVM调优官方文档:Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide JDK9 默认是G1收集器


一条新生代GC日志:


0.791: [GC (Allocation Failure) [PSYoungGen: 64000K->3229K(74240K)] 64000K->3237K(243712K), 0.0040270 secs] [Times: user=
0.04 sys=0.00, real=0.00 secs]


一条老年代GC日志:


152561.075: [Full GC (Ergonomics) [PSYoungGen: 9303K->0K(68096K)] [ParOldGen: 424954K->389313K(439296K)] 434258K->389313K
(507392K), [Metaspace: 42513K->42513K(1087488K)], 0.0598682 secs] [Times: user=0.39 sys=0.01, real=0.06 secs]


查看JVM堆使用


知道了垃圾回收器,再来看看默认情况下,程序运行时初始堆大小,随着程序的运行,堆内存最终可达到多大?


如果在启动程序时使用-Xmx 指定了最大堆容量,那堆内存最终可达到的值,就是 Xmx设置的值(当然,Xmx不可能设置得比机器的物理内存还要大,同时也不要设置得和机器内存很接近,毕竟还有留一些内存给机器上的其他程序用)


下面以一台实际的物理机器,来分析下,程序是如何使用堆内存的。这台物理机器的内存大小为:16225356KB(约为16GB),物理处理器核数为2。因此符合 sever class machine。对于 server class 机器,默认使用如下参数:


On server-class machines, the following are selected by default:


Throughput garbage collector
Initial heap size of 1/64 of physical memory up to 1 GB
Maximum heap size of 1/4 of physical memory up to 1 GB
Server runtime compiler
使用以吞吐量优先的GC 回收器。


Initial heap size of 1/64 of physical memory up to 1 GB 这句话,解读很多。我的理解是:JAVA程序启动时,默认分配的堆大小为:机器物理内存的64分之一,在我的示例中,机器的物理内存是16225356KB,因为初始时分配的堆大小为247MB:


16225356/64/1024
247


而使用java -XX:+PrintCommandLineFlags命令:(单位是B)


-XX:InitialHeapSize=259605696 -XX:MaxHeapSize=4153691136


可看出初始堆大小为259605696B,259605696/1024/1024=247MB。由此可知:JVM启动时分配的初始堆大小为物理机器内存的64分之一。


然后我再在一台内存为128GB的机器上:


cat /proc/meminfo | grep MemTotal
MemTotal: 131829708 kB


131829708 / 1024 /1024 =125 (也即:128GB内存)


java -XX:+PrintCommandLineFlags
-XX:InitialHeapSize=2109275328 -XX:MaxHeapSize=32037767584 -XX:+PrintCommandLineFlags -XX:+UseCompressedOops -XX:+UseParallelGC


可以看出:-XX:InitialHeapSize=2109275328,也即:2109275328/1024/1024=2GB,也就是说:在物理内存为128GB的机器上,JAVA堆的初始分配大小为2GB,是超过1GB的。


Maximum heap size of 1/4 of physical memory up to 1 GB,随着程序的运行,JVM堆内存会越来越大,但是一个JAVA进程最大能使用多大的堆内存空间呢?答案是 四分之一的物理机器内存。


更具体地,对于一台物理内存为16GB的机器,如果在JAVA程序启动时 不用 Xms、Xmx 参数指定jvm堆大小,即:这个程序就是使用默认的 java堆大小配置,一开始JAVA堆大小为:16GB/64 ,约为:247MB;然后随着程序的运行,JAVA堆分配的内存会动态增大,动态增大的上限是:物理机器内存的四分之一,即约为4GB。


比如,我查看 程序启动后 第一次 GC日志如下:


0.791: [GC (Allocation Failure) [PSYoungGen: 64000K->3229K(74240K)] 64000K->3237K(243712K), 0.0040270 secs] [Times: user=0.04 sys=0.00, real=0.00 secs]
GC前该内存区域(新生代)大小:62MB,GC后该区域的大小:3MB,该区域的总内存大小:72MB。
而GC前JAVA堆使用量62MB,gc后JAVA堆使用量3.1MB. JAVA堆的总大小:238MB(与247MB很接近)


我来做个猜想:根据第一条gc日志,JAVA堆总大小是238MB,新生代与老年代的比例是1:2,即:-XX:NewRatio=2,1/3的堆是新生代,2/3的堆是老生代,这样的话,新生代的堆大小是:238/3=79MB,刚好与发生GC的区域总内存72MB接近。
而新生代再进一步细分:分为 Eden区、两个Survivor区,其中Eden区占新生代堆大小的8/10,两个Survivor区占新生代堆大小的2/10。即:Eden区的大小为:790.8=63MB ,与前面提到的 62MB非常接近。也就是说:在Eden区空间不足以容纳新创建的对象的时候,发生了一次 PSYoungGen 垃圾回收操作。而Survivor区的大小为790.1=8MB,回收完成后,将剩余的3.1MB对象 存储 在其中一个Survivor区了。


当随着程序运行一段时间后:再看一条GC日志:


95191.984: [GC (Allocation Failure) [PSYoungGen: 48668K->2932K(73216K)] 507352K->461823K(648192K), 0.0032727 secs] [Times: user=0.04 sys=0.00, real=0.01 secs] 
GC前该内存区域(新生代)大小:48668/1024=47MB,GC后该区域大小约为2MB,该区域的总内存大小:71MB。GC前JAVA堆内存的使用量 507352/1024=495MB,GC后JAVA堆的内存使用量461823/1024=450MB,JAVA堆内存总大小:648192/1024=633MB


可见,运行一段时间后,JAVA堆内存大小从:238MB,动态增加到了:633MB


进程的各种状态


一般我们会用 ps aux | grep java来查看java进程,知道进程ID号(比如13988)后,可通过:


cat /proc/13998/status | grep Threads
Threads: 78


查看一个JAVA进程下一共启动了多少个线程。


另外,ps aux 中其中有一列是显示进程状态的,那进程状态有哪些呢?


man ps 找到的进程状态的解释:


PROCESS STATE CODES
Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will display to describe the state of a process:
D uninterruptible sleep (usually IO)
R running or runnable (on run queue)
S interruptible sleep (waiting for an event to complete)
T stopped, either by a job control signal or because it is being traced.
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z defunct ("zombie") process, terminated but not reaped by its parent.


For BSD formats and when the stat keyword is used, additional characters may be displayed:
<    high-priority (not nice to other users)
N    low-priority (nice to other users)
L    has pages locked into memory (for real-time and custom IO)
s    is a session leader
l    is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)



  • is in the foreground process group.
    D 代表不可中断的阻塞,比如说I/O操作。不管是显示IO,还是隐式IO,访问本地磁盘的IO操作时,一般会处于D状态。



In practice, processes typically go into D state ("uninterruptible sleep") when they're blocked on access to a local disk, whether that's explicit I/O (read/write) or implicit (paging).
S代表可中断的睡眠状态,比如线程执行下面的代码:sleep(500),就处于可中断的睡眠状态吧。


try {
    Thread.sleep(500);
} catch (InterruptedException e) {
    logger.info("thread interrupted:{}", e.getCause());
}


关于S状态的解释:(waiting for an event to complete),比如说,线程A在争抢锁时,由于这把锁已经被线程B拿到了,那么 线程A 就会进入 S 状态吧,线程A等待着线程B释放锁这一事件。


谈到线程的状态,其实有个参数与线程状态息息相关,那就是CPU负载。处于哪个状态的线程,才会计入CPU的负载呢?


There are two contributions to the load factor: number of processes/threads on the ready-to-run queue and the number blocked on I/O. The processes blocked on I/O show up in the "D" state in ps and top and also contribute to this number.


Not all processes blocked on I/O are in D state - for a common example, processes blocked on I/O to a network socket or terminal will simply be in the S state, and not count towards load.
可以这样理解:准备运行的线程( ready-to-run queue)和阻塞在I/O操作上的线程都是计入负载的。


但是阻塞在I/O操作上的线程有两种状态,一种是D状态,另一种是S状态。其中S状态的线程是不计入负载的。


总结


在我的生产环境中,默认安装JDK8,在未人为指定任何JVM参数的情况下,新生代采用Parallel Scavenge收集器,它是一个以吞吐量为目标的GC,采用多线程基于复制算法对新生代进行垃圾回收。老年代采用Parallel Old垃圾收集器,采用多线程基于 Mark-sweep 算法进行回收。
每一款垃圾收集器都会有一个相应的调优目标,以最短停顿时间为目标、以最大吞吐量为目标、以最小使用堆内存空间为目标。这些目标是有优先级的,优先级最大的目标是停顿时间,其次是吞吐量。对于并行垃圾收集器(Parallel Collectors),默认情况下,并没有设置“最大停顿时间”这一目标,这也就意味着最短停顿时间目标默认是实现了的。因此,Parallel Collectors 达到设置的吞吐量要求,而这个吞吐量由参数 -XX:GCTimeRatio指定,默认值为99,也即:为了保证吞吐量,GC时间只能占到整个程序运行时间的1% (参考JVM官方调优指南)
最短停顿时间目标是关于GC时间的,而最大吞吐量也是讲GC时间,在我看来:最短停顿时间是在一次GC过程中设置了一个“硬指标”,即:每次GC时间不能超过 设置的停顿时间这个值;而最大吞吐量则是讲:所有的总的GC时间加起来不要超过某个值。

   

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注编程语言JAVA频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程