跳至主要内容

Java微型网络程序GC调优练习

Java微型网络程序GC调优练习

简介

最近用java写了一个微型的网络程序,没进行GC调优的情况下就已经满足性能要求。不过想通过对这个程序调优,再熟悉一下GC调优流程。

通过活跃数据大小设置堆内存大小

活跃数据大小指的是应用程序员运行于稳定态时,长期存活的对象在Java堆中占用的空间大小。所以可以在程序稳定运行时,进行多次Full GC,之后再查看Java堆的占用情况来测量活跃数据大小。

调优过程需要一步一步多次确定更合适的值,JVM的初始参数和版本信息如下(堆大小按感觉先随意分配256M,如果有溢出再调整):

-Xms256m 
-Xmx256m 
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDetails 
-XX:MaxDirectMemorySize=512m 
-XX:+PrintCommandLineFlags 
-XX:+UseParallelGC
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)

其中-XX:+PrintGCDetails参数表示打印垃圾回收情况,-XX:+PrintGCTimeStamps参数表示每次打印的时候带上JVM启动到当前时间的秒数,也可以使用-XX:+PrintGCDateStamps打印日历时间。-XX:+UseParallelGC是java8在Windows64上的默认选择,表示同时启用多线程新生代和多线程老年代的Parallel垃圾收集器[1],这一点可能跟旧版本的java不一样(对旧版本java的没太多研究)。

用这个程序来看平均速率在200KB/S左右的直播。运行一段时间之后,程序达到稳定状态。由于这个程序在256M内存下,不经常发生Full GC,所以在程序稳定运行的时候,用visualvm触发多次Full GC。每次Full GC的结果比较相似,选取其中一个显示如下(已调整换行):

1887.144: [Full GC (System.gc()) 
[PSYoungGen: 224K->0K(86528K)] 
[ParOldGen: 7438K->6220K(175104K)] 
7662K->6220K(261632K), 
[Metaspace: 19356K->19356K(1067008K)], 0.0254513 secs] 
[Times: user=0.11 sys=0.00, real=0.02 secs]

日志显示,Full GC之后的老年代占用6220K,所以活跃数据大约为6M。根据通用法则,堆的初始值和最大值一般设置为老年代活跃数据大小的3-4倍。这里按4倍计算,设置堆的初始值和最大值为24M:-Xms24m -Xmx24m

而新生代的空间应该为老年代活跃数据的1-1.5倍。这里按1.5倍计算,设置新生代大小为9M:-Xmn9m。调整后使用的JVM参数设置如下:

-Xms24m 
-Xmx24m 
-Xmn9m 
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDetails 
-XX:MaxDirectMemorySize=512m 
-XX:+PrintCommandLineFlags 
-XX:+UseParallelOldGC 

验证设置

经过设置之后,再次让程序运行到稳定的状态。其中Minor GC的频率比较稳定,摘取结果如下:

......
4083.597: [GC (Allocation Failure) [PSYoungGen: 8320K->64K(8704K)] 19028K->10804K(24064K), 0.0013510 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
4085.883: [GC (Allocation Failure) [PSYoungGen: 8256K->64K(8704K)] 18996K->10812K(24064K), 0.0014869 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
4088.088: [GC (Allocation Failure) [PSYoungGen: 8256K->96K(8704K)] 19004K->10852K(24064K), 0.0014876 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
4090.371: [GC (Allocation Failure) [PSYoungGen: 8288K->96K(8704K)] 19044K->10860K(24064K), 0.0017656 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
......

Minor GC持续时间在0.001秒左右,频率是每2.2秒一次。

Full GC日志摘取结果如下:

......
1347.321: [Full GC (Ergonomics) [PSYoungGen: 223K->0K(8704K)] [ParOldGen: 15318K->8750K(15360K)] 15542K->8750K(24064K), [Metaspace: 19171K->19049K(1067008K)], 0.0407358 secs] [Times: user=0.06 sys=0.00, real=0.04 secs]
...
2599.391: [Full GC (Ergonomics) [PSYoungGen: 96K->0K(8704K)] [ParOldGen: 15359K->5356K(15360K)] 15455K->5356K(24064K), [Metaspace: 19315K->19304K(1067008K)], 0.0344051 secs] [Times: user=0.06 sys=0.00, real=0.04 secs]
......

Full GC持续时间在0.04秒左右,频率是每隔20分钟一次。

延迟

对这个程序来说,上面数据显示的延迟可以接受。一般来说,如果延迟没有满足需求,可以再次分别调整新生代和老年代的大小。当调整之后,Full GC持续时间依旧过长时,可以改用并发垃圾收集器[2]。在java8中,并发收集器主要有两个:CMS和G1。G1的目的是替换掉CMS[3],而CMS在java8中已经被标记为过时,并在之后的发行版中删除。

使用G1

G1的调优目前没有太多研究,直接把-XX:+UseParallelGC参数替换成-XX:+UseG1GC,删除之前的新生代设置-Xmn,来启用G1垃圾收集器,直观的感受一下。

长时间观看平均速率在200KB/S左右的直播,发现一直没有发生Full GC,老年代空间一直没有被塞满,维持在一定水平的占用。

总结

通过这次调优,看到配置这么小的堆内存就能满足这个程序的正常运行,还是有点不习惯,毕竟平常配置服务器都是按GB来配置。

参考资料

[1] java8的垃圾收集器介绍:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html

[2] java8的并发垃圾收集器介绍:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/concurrent.html#mostly_concurrent

[3]G1收集器简介:https://www.oracle.com/technetwork/java/javase/tech/g1-intro-jsp-135488.html

评论

此博客中的热门博文

国密SM2签名封装成PKCS7格式

在国内做金融行业,难免会有被强制使用国密算法的情况,而且一般还会指定必须使用硬件加密机之类的设备,所以我也稍微的研究了一下国密算法,使用软算法签名并封装 PKCS7 格式(文档中的一个交互)。 以下是基于 Bouncy Castle 的示例,密钥对的生成可以参考 Bouncy Castle 中 test 包下 SM2 相关代码 public static String sign ( ) throws Exception { //加载公钥 byte [ ] plainText = "hello, world" . getBytes ( ) ; FileInputStream input = new FileInputStream ( "F:\\certificate\\public.cer" ) ; CertificateFactory certificateFactory = new CertificateFactory ( ) ; X509Certificate certificate = ( X509Certificate ) certificateFactory . engineGenerateCertificate ( input ) ; input . close ( ) ; //加载私钥,private为换成实际的私钥 PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec ( "private" . getBytes ( ) ) ; //SM2算法实际上为ECC算法,并指定了一些参数值,所以这里的参数是EC KeyFactory factory = KeyFactory . getInstance ( "EC" , "BC" ) ; PrivateKey privateKey = factory . generatePrivate ( spec ) ; //以下为签名并封装成PKCS7格式 byte [ ] signedMessag

Spring Boot Actuator 2 示例

Welcome file 简介 Spring Boot Actuator为应用程序提供了各种开箱即用的运维特性,可以与应用方便的交互和监控。 使用环境:Java 11 和 Spring Boot 2.4.3.RELEASE 集成Spring Boot Actuator 在Spring Boot中集成Spring Boot Actuator与集成其他的框架类似,在 pom.xml 里引入相关的starter就可以: < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-actuator </ artifactId > </ dependency > < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-web </ artifactId > </ dependency > 由于大部分的使用场景还是web,所以这里也用Spring MVC做示例。 配置好 pom.xml 后,默认actuator仅暴露一些基本功能,实际使用中,根据需求暴露对应功能。为了简便测试,这里在 application.yml 中配置暴露全部功能: management : endpoints : web : exposure : include : "*" endpoint : health : enabled : true show-details : always probes : enabled : true shutdown : enabled : true metr

NextCloud数据目录迁移

最近服务器的环境坏了,所以迁移了NextCloud的数据目录。不过在迁移过程中有点小问题。 环境: Ubuntu 18.04 Docker 19.03.7 1.NextCloud页面不正常,Docker日志显示XX目录permission denied 参考了 这里 的做法,不过是把  /var/www/html/   整个目录的权限都修改为  chown -R www-data:www-data ,之后就不再报权限问题了。 2.数据库配置修改 因为NextCloud是在初始化时填的数据库连接信息,所以直接迁移数据目录的情况下,会导致应用连不到新的数据库环境。此时可以找到数据目录下的  config/config.php 文件,直接修改数据库连接配置。