明白生产环境中的jvm参数

明白生产环境中的jvm参数

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

  • JDK版本是多少?采用何种垃圾回收器?
  • 程序启动的时候默认分配堆内存空间是多少?随着程序的运行,程序最多能使用多大的内存空间?
  • 程序中使用了多少个线程?目前的java进程又处于何种状态?

了解了这些,会对程序的运行有一个更好的了解。本文结合生产实践,记录一下我常用的一些操作。

注意:如果没有特殊说明,下面所有的参数讨论都是基于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日志:

6.280: [Full GC (Ergonomics) [PSYoungGen: 4696K->0K(873984K)][ParOldGen: 332185K->333434K(583680K)] 336882K->333434K(145

7664K), [Metaspace: 30421K->30421K(1077248K)], 0.2373663 secs][Times: user=3.47 sys=0.04, real=0.24 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区的大小为:79*0.8=63MB ,与前面提到的 62MB非常接近。也就是说:在Eden区空间不足以容纳新创建的对象的时候,发生了一次 PSYoungGen 垃圾回收操作。而Survivor区的大小为79*0.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状态的线程是不计入负载的。

参考资料:

关于CPU负载可参考这两篇文章:cpu load过高问题排查Understanding Linux CPU Load - when should you be worried?

Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide JDK9

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html

原文:https://www.cnblogs.com/hapjin/p/9688289.html

原文地址:https://www.cnblogs.com/hapjin/p/9688289.html

时间: 2024-08-25 15:35:19

明白生产环境中的jvm参数的相关文章

生产环境中tomcat的配置

生产环境中要以daemon方式运行tomcat 通常在开发环境中,我们使用$CATALINA_HOME/bin/startup.sh来启动tomcat, 使用$CATALINA_HOME/bin/shutdown.sh来关闭tomcat. 而在生产环境中,我们要配置tomcat使其以daemon方式运行,这是因为: 以daemon运行不受终端影响,不会因为退出终端而停止运行 可以让tomcat以普通用户身份运行,可以让tomcat随linux启动而启动 如何将tomcat配置成守护进程 将tom

生产环境中使用脚本实现tomcat start|status|stop|restart

一.在实际生产环境中tomcat启动是在bin目录下采用自带脚本startup.sh启动:使用shutdown.sh关闭.如下图: 再如果对于新手来讲在不知道路径情况下重启是一件头痛的事情(注意没有reload,所以重启只能shutdown.sh在startup.sh):而且这里还有一个坑等着: 什么坑呢?   如图: tomcat服务是启动成功了的.那么我想停止服务用shutdown.sh,会出现什么呢? 进程还在而且成为了僵尸进程,万恶啊?居然关不了,终极方法kill -9 进程号.试试?

Production环境中iptables常用参数配置

production环境中iptables常用参数配置 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我相信在实际生产环境中有很多运维的兄弟跟我一样,很少用到iptables的这个命令.因为大家的服务器的防火墙都是关闭的,但是如果你的 服务器是有公网IP的话就会面临各种安全的问题呢,所以我建议大家还是开启防火墙,这个命令其实挺有意思的,就是配置起来比较繁琐,但是原理还 是很容易理解的,关于其原理百度上面一大堆,我这就不再废话啦~ 在配置之前,我们需要扫盲一下知识点: 一.ip

JDK 9 发布仅数月,为何在生产环境中却频遭嫌弃?

千呼万唤始出来,在经历了整整一年的跳票之后,Java 9 终于在 9 月 21 日拨开云雾,露出真正的面目.对众多 Java 程序员来说,这一天无疑是一个重大的日子,首先 Java 开发者们再也不用羡慕别的自带 REPL 的语言了,不用为了试个 Java 功能而开个 Groovy shell:其次最主要的莫过于 Jigsaw 项目下颠覆性的 Java 模块化了,有了它,自己定制/裁剪 JDK 变得更直接. 其中,整个 Java 的核心内容非 JDK 莫属,其包括了 Java 运行环境(Java

Open stack生产环境中几种常见的网络结构

一.概述 想必接触过Open stack的人都知道,Opens stack中最复杂的是网络部份,在实际的生产环境中更是如此,实际场景下往往不仅有Open stack网络,还有外部网络(Open stack将其无法管理的网络统称为外部网络),即便在Open stack内部,客户也有不同的网络诉求,本文选取2个案例对常见的网络模型进行说明. 二.案例说明 1.运营商网络和租户网络 Open stack是面向众多租户提供服务的云操作系统,由租户自行创建的网络称为租户网络,但有时候会出现以下这种情况:

13.生产环境中的 redis 是怎么部署的?

作者:中华石杉 面试题 生产环境中的 redis 是怎么部署的? 面试官心理分析 看看你了解不了解你们公司的 redis 生产集群的部署架构,如果你不了解,那么确实你就很失职了,你的 redis 是主从架构?集群架构?用了哪种集群方案?有没有做高可用保证?有没有开启持久化机制确保可以进行数据恢复?线上 redis 给几个 G 的内存?设置了哪些参数?压测后你们 redis 集群承载多少 QPS? 兄弟,这些你必须是门儿清的,否则你确实是没好好思考过. 面试题剖析 redis cluster,10

生产环境中的PHP WEB 简单架构

使用三台虚拟机器, Ubuntu1(nginx) 192.168.226.128 Ubuntu2(php-fpm+memcached)192.168.226.132 CentOS(MySQL)192.169.226.130 PHP 框架使用CakePHP,这个是很常用的MVC 框架,基于事件的分发模型 当然需要注意的是框架代码要部署在php-fpm机器上,需要在nginx 中配置的配置如下 余下的内容: 1. CakePHP 框架代码 2. PHP 内核 3. Nginx内核 4. 数据库设计模

[virtualenv]生产环境中使用virtualenv

virtualenv 对于python开发和部署都是好工具,可以隔离多个python版本和第三方库的版本,这里作者总结了几个常用python服务怎么样结合virtual部署 原文链接 Python 中我最喜欢的东西之一就是可以使用 virtualenv 去创建隔离的环境.非常简单的就可以在不同的项目中部署不同的python类库. 有一个比较棘手的问题就是在生产环境中使用virtualenv 部署几个不同的服务有一些配置上的不同. 于是我就从我的项目中收集了几种不同的服务的不同配置方式. 可以肯定

生产环境中CentOS7部署NET Core应用程序

NET Core应用程序部署至生产环境中(CentOS7) 阅读目录 环境说明 准备你的ASP.NET Core应用程序 安装CentOS7 安装.NET Core SDK for CentOS7. 部署ASP.NET Core应用程序 配置Nginx 配置守护服务(Supervisor) 这段时间在使用Rabbit RPC重构公司的一套系统(微信相关),而最近相关检验(逻辑测试.压力测试)已经完成,接近部署至线上生产环境从而捣鼓了ASP.NET Core应用程序在CentOS上的部署方案,今天