「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战
JVM 开放了那么复杂的一个能力出来,肯定会有参数开放给我们。我们平时在做 JVM 的优化,其实本质上来说就是在做 JVM 的调参。
在做 JVM 调优的时候,有哪些关键且致命的参数,我们一定要注意的。
关于内存大小的取舍,有的人可能会觉得很奇怪,为什么我们要在内存大小上面去做取舍?
设置合适的内存大小
扩大内存就可以更少的去触发 GC,这个想法肯定是对的,假定我们有 100G 或者上 1000G 的一个虚拟机内存,这样的话他可能就永远触发不了一些很低 QPS 应用的一个 GC,因为它需要花很长的一段时间,才可能把对应的 Eden 区或者老年代塞满,才可能去触发我们的 Young GC、Major GC 或者 Full GC。
但是,我们往往没有想到,如果说永远都没有 GC 的话,是肯定不可能的。随着应用程序不断的 new 对象,这些对象都会卡在内存当中,只有等待到 GC 的时候才可以将它销毁。
这样势必就会带来一个问题,如果说我的堆内存设置的太大,触发 GC 的时候的停顿时间就会更长。本质就是因为我们如果申请一个很大级别的一个内存,一旦触发了 GC,整个 GC root开始往下扫描,无论是哪种情况,都有可能会导致 GC 的 Stop the world 的停顿时间变长。
因此实际上来说,我们是需要去根据实际的业务场景设置一个合适的内存大小的值,并且去配合我们的一个压测或者线上的环境去做不断的调优。
吞吐量
我们的应用是有一定的吞吐量的,也就是花费在非 GC 停顿时间上的一个工作时间/整个运行的一个总时间,就代表了我们吞吐量。
假定我们的程序一共运行了 100 秒,其中 99 秒都是在做我们的应用,只有 1 秒是在做 GC 相关的一个时间,那我们的吞吐量就是百分之 99%。
针对一个线上的一个互联网应用来说,我们对应的吞吐量至少要优化到 95%,甚至于要优化到 98% 以上,才能够给用户一个比较好的体验。
因为你永远没有办法去控制它触发 GC 的那一下的 Stop the world,会影响什么样的业务,会影响什么样的用户,因此这个吞吐量是非常的重要的。
核心参数
-Xms:代表的是启动 JVM 时候,我们设置的一个堆内存的大小,有点类似于刚启动初始化的一个大小。
-Xmx:指的是堆内存的最大限制。我们在实际的产线环境上面,必须得要将 -Xms 和 -Xmx 对应的内存大小设置成一样的。
本质上来说,我们 server 服务器是 24 小时常驻内存去做用户的一个服务的。因此如果说将 -Xms 和 -Xmx 这两个大小设置成不一样,往往启动大小会比最大大小小一些。这个时候我们的应用的内部的一个 JVM 就会频繁地去做扩缩容,去扩到最大堆内层,在闲下来的时候又去缩,这种样子的方式是非常不利于cpu 的一个运算的。
因为我们在做扩容的时候,往往就代表了你的业务是在涨,这个时候我们的 cpu 又要去关注对应的 JVM 内存的一个扩容,又要去服务业务,本质上来说会对我们的 cpu 产生干扰。
因此,在最好的情况下面,-Xms 和 -Xmx 这两个内存的值设置成是一样的。
-XX:newSize:表示新生代初始内存的大小,应该小于 -Xms 的值。
-XX:MaxnewSize:表示新生代可被分配的内存的最大上限;当然这个值应该小于 -Xmx 的值。
一样的原理需要将这两个大小设置成一样。年轻代的大小往往我们一般会调整的比老年代稍息来说大一些,因为针对互联网的应用来说,说本质上来说都是 request 驱动的。拿一个request 做一笔,然后做完之后即销毁。
如果说我们年轻代设置稍许大一些,就可以保证对应的对象在触发 GC 次数一定的时候,可以尽可能减少晋升到老年代的时间。因为晋升到老年代之后,我们尽可能的是要减少老年代的 GC。
那这个时候我们其实就可以看到,首先两者是需要设置成一样的,防止扩缩容,同时我们的年轻代一般是需要比老年代设置的大一些。
-XX:NewRatio:设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。默认值为 8,代表的是说我们假设我们的内存在年轻带当中设置了 100m,那其实 80m 是给 Eden区,另外 20m 分别两个 Survivor 区各占 10m。
为什么这个默认值是 8?我们假定定义为每次经过一次 Young GC,一共有 90% 的对象会被做清楚,这个是一个工程学的一个假设,那我们自然可以的一个业务的实际场景去调整对应的这个数值。但是永远要记住,Eden 要比 Survivor 区设置的大至少一倍,否则就没有任何的意义。
从 JDK8 开始,永久代(PermGen)的概念被废弃掉了,取而代之的是一个称为 Metaspace 的存储空间。Metaspace 使用的是本地内存,也就是说在默认情况下 Metaspace 的大小只与本地内存大小有关。
元数据区也有两个对应的参数,分别是:-XX:MetaspaceSize=N 和-XX:MaxMetaspaceSize。
-XX:MetaspaceSize=N:这个参数是初始化Metaspace大小。该值越大出发Metaspace GC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加也可能降低。在默认情况下,这个值的大小根据不同的平台在12M到20M浮动。使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,Metaspacesize为21810376B(约20.8M)。
-XX:MaxMetaspaceSize=N:这个参数用于限制 Metaspace 的上限,防止其无限使用本地内存,影响到其他程序。在本机上默认值为4294967295B(大约4096MB)。
这两个参数我其实是不建议大家去设置的。如果说我们限制了元数据区的一个最大空间。本质上来说,如果超过了这个最大执行空间,我们的程序就会报错。
如果说我们不设置最大执行空间,而是我们有这个预期,我们对应的 static 变量类分别是多少,它不会随着运行时动态的增长许多。
那这个时候我们其实就可以忽略对应的这个参数,完全交给我们的 JVM 去判断应该扩多少就完事儿了。