无论是在spring boot 还是spring cloud 项目中,随着应用的不断增多,JVM参数的统一管理的重要性就会凸显出来,否则你可能会遇到几个问题:
- Java进程出现性能问题,无GC日志支撑提供重要信息;
- OOM异常频发,无法通过dump文件进行分析定位;
- JVM堆内存设置规格不一致,被动等待出问题时发现;
作为运维,虽然没有超强的能力去最终的定位、分析、排查问题,但并不意味着我们就可以袖手旁观,那么我们能做什么呢?
-
首先,我们要知道Java进程默认参数启动并不会打印某些我们需要的日志,而是需要我们按需去设置的。
-
其次,即使开启了相应的日志参数,其统一输出位置就成了我们需要面对的问题,毕竟我们不希望满盘搜文件,浪费不必要的时间。
-
最后,各种个性化的JVM参数,无益于运维对数量为百、千级别进程的有效管理。
此时统一的Java进程管理规范就可以发挥作用,通过标准化部署,Java使用统一的JVM参数运行,一旦某个应用出现异常,我们可以快速收集各种异常日志提供给研发进一步定位问题。
最终目标是,运维能提供给研发的有效信息,肯定是排除了因环境差异、参数配置差异等这些低级别干扰因素的,这样才能保证运维和研发的快速定位。
进程规范 1.GC日志GC日志是用来描述JAVA虚拟机垃圾回收情况,主要用来快速定位潜在的内存故障和性能瓶颈。默认情况下是关闭的,我们需要通过参数设置启用。
参数格式如下:
-XX:+PrintGC 输出简要GC日志 -XX:+PrintGCDetails 输出详细GC日志 # gc.log输出到统一的日志目录 -Xloggc:/data/logs/gc.log 输出GC日志到文件 -XX:+PrintGCTimeStamps 输出GC的时间戳(以JVM启动到当期的总时长的时间戳形式) -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息 -XX:+PrintReferenceGC 打印年轻代各个引用的数量以及时长2.dump文件
dump文件是Java进程出现OOM时自动生成的文件,通过此文件可以定位进程发生OOM的位置。可配置参数自动生成 dump 文件。
-XX:+HeapDumponOutOfMemoryError # dump文件输出到统一的日志目录 -XX:HeapDumpPath=/data/logs/HeapDumpOnOutOfMemoryError.dump3.堆内存
堆内存设置是我们最容易设置混乱的参数,但对于我们标准化交付的服务器,虽不至于所有的应用使用完全一致的堆内存参数,但是大多数情况下是可以统一的。
-Xms2048m -Xmx2048m -XX:MaxPermSize:256m
至此,常见的JVM参数设置完毕,由于这些参数多和开发定位问题有关,因此我们在此只是将其作为进程管理规范的内容进行讲解,而不是研究其具体作用。但你以为我们的工作就到此为止了吗?
我们还可以通过设置JVM环境变量来实现部分扩展功能,因此也需要将环境变量作为进程管理规范的一部分。
4.JVM环境变量环境变量便于运维能够灵活控制java进程运行的参数,这样可以和自动化相结合,实现应用的统一部署,有效避免更改配置文件的动作。
关于环境变量的定义,需要结合各自的生产环境特性来自行定义,我这面的定义的变量如下:
# 应用名 -Dapp.name=test # 环境区分 -Denv=prod或uat或stg # 临时文件目录 -Djava.io.tmpdir=/data/tmpdir
其中比较重要的是-Denv=xxx,通过在各个环境预设此变量,可实现不同环境配置文件的绑定。例如:在spring cloud项目中可以和各环境的配置中心调用进行关联。
其他环境变量大家可以和研发协商按需灵活添加。
按规范管理 1.统一管理通过以上各参数的简单讲解,我们有了一套比较固定且完整的JVM参数,稳定性和灵活性兼顾,也便于我们后续的管理。
-server -Xms2048m -Xmx2048m -XX:MaxPermSize:256m -Dapp.name=test -Denv=prod -Djava.io.tmpdir=/data/tmpdir -Xloggc:/data/logs/gc.log -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -Dsun.jnu.encoding=UTF-82.supervisor守护管理
随着JVM启动参数的尘埃落定,如何启动Java进程就是我们接下来要面对的问题。
我们经常使用的后台启动方式有以下几种:
- nohup
- screen
- supervisor
其中nohup、screen都需要配合脚本,才能更友好的管理,因此我还是选择supervisor作为java应用的守护进程。
# 1.安装 yum install supervisor systemctl enable supervisord systemctl start supervisord # 2.配置 vim /etc/supervisord.d/test.ini [program:test] ;启动用户 user=work ;程序启动命令 command=java -server -Xms2048m -Xmx2048m -XX:MaxPermSize:256m -Dapp.name=test -Denv=prod -Djava.io.tmpdir=/data/tmpdir -Xloggc:/data/logs/gc.log -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -Dsun.jnu.encoding=UTF-8 -jar test.jar numprocs=1 ;程序启动目录 directory=/App/java_app/test ;在supervisord启动时自启动 autostart=true ;程序异常退出后自动重启,可选值:[unexpected,true,false],默认为unexpected autorestart=true ;启动10秒后没有异常退出,就表示进程正常启动了 startsecs=10 ;启动失败自动重试次数 startretries=3 # 3.更新配置文件,更新配置文件并重启 supervisorctl update # 4.重载配置文件 ,注意reload会导致supervisor重启,所管理的进程会重启 supervisorctl reload # 5.查看状态 supervisorctl status # 6.启动 supervisorctl start test
在此需要注意的是supervisor并不能托管任何进程,而只适合管理运行于前台的进程(如java 直接启动),对于运行后台daemon的进程(如tomcat),supervisorctl status会报错"BACKOFF Exited too quickly (process log may have details"。
总结总结出一份用于运维过程中各个环境的JVM参数其实很简单,关键在于我们是否意识到了一份进程管理规范的重要性,怎样和当前的自动化水平来结合,实现其最终的价值。而对于只负责躺平的规范来说,我们做的这些工作意义不大。



