三分钟带你弄清楚JVM调优到底是什么

前言

相信很多人在学习或者面试的过程中,都会听到很多人提到JVM,以及JVM调优,那么JVM调优,到底调的是什么呢?

JVM到底是什么

JVM的相关概念,相信网上都有很多介绍,简单来说,JVM就是一个java程序的运行环境。我们编写的代码可以在这个环境中运行。

项目准备

很多人的项目运行的时候,并没有考虑过对JVM的相关参数进行设置,而是采用的默认设置。下面,先通过一个简单的代码和一些简单的参数,来看一下JVM可以设置哪些参数,这些参数有什么作用,为什么我们要调整JVM的默认参数。

首先完成下面的准备工作:
在IDEA中设置JVM相关参数,将下面的参数直接复制到VM options中
在这里插入图片描述
在这里插入图片描述

-Xmx30m -Xms30m -Xmn10m -XX:SurvivorRatio=8 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC

导入如下测试代码

public class Test {

    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(10);
        for (int i = 0; i < 20; i++) {
            byte[] arr1 = new byte[2 * 1024 * 1024];
            arr1 = new byte[2 * 1024 * 1024];
            arr1 = new byte[2 * 1024 * 1024];
            byte[] arr2 = new byte[1512 * 1024];
            arr1 = new byte[2 * 1024 * 1024];
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

利用jstat -gcutil查看整个代码执行过程中,GC到底怎么运作的。
想要知道我们刚才这些操作,到底做了些什么,需要首先了解一些理论知识。(如果对JVM的相关概念理解比较清晰,可以跳过这部分内容)

  • 对象的分配与回收
    在java代码中,绝大部分的对象是分配在堆内存里。比如上面代码里的new byte[_1MB]。就是在堆内存中申请了1M左右的大小,用来存储byte数组。
    当内存中的对象全部填满以后,就需要释放那些废弃对象所占用的内存空间,这就是GC。在上面的代码中,byte[] arr = new byte[_1MB],创建了一个对象数组,随后arr又指向了一个新的new byte[_1MB]。这时候,刚才创建的数组就失去了引用,成为了内存中的垃圾。在代码的最后,arr的引用置为了null。此时创建的3个byte数组就均为垃圾对象。需要在GC的时候被释放。

  • 什么是新生代,什么是老年代
    新生代往往存放的是系统中出现比较短暂的,很快会失去引用的对象。老年代往往存放的是系统中生命周期比较长的对象。对象在刚开始创建的时候都会存放在新生代,随着不断的GC,一些生命周期较长的对象就会转移到老年代。

  • GC回收时机与耗时
    JVM中的GC分为新生代回收和老年代回收,简单理解,当年轻代的内存空间不足以容纳新创建的对象时,就会触发一次GC操作。而GC是需要耗费时间的,并且在GC的过程中,会短暂的暂停我们的应用程序。因此,GC越频繁,耗时越久,我们的应用就会越发的卡顿。
    在理解了上面这些概念以后,我们再来回顾这段代码。其实这段代码就是不断的创建新的byte数组,再不断的修改对象的引用,制造垃圾对象,从而不断触发GC。

  • JVM相关参数
    上面的VM options中,对JVM进行了若干参数设置。

Xmx :设置最大堆内存大小 Xms:设置最小堆内存大小 Xmn:设置新生代堆内存大小
XX:SurvivorRatio :设置新生代中Eden与Survivor区的比例
XX:+UseParNewGC 与 XX:+UseConcMarkSweepGC 分别指定新生代与老年代的垃圾回收器

  • jstat
    jstat是jdk中自带的一个工具,利用他可以很方便的观测程序运行过程中的一些状态。

实战演练

在介绍完上述理论知识后,我们运行上面的测试代码,并在命令行中,输入jps找到我们项目的端口号在这里插入图片描述

156,执行 jstat -gcutil 156 1000 来查看项目运行过程中的GC情况
在这里插入图片描述
上图打印的是项目的GC过程,我们首先关注两个核心的值YGCFGC。这两个参数的含义是项目运行过程中,新生代GC的次数与老年代GC的次数。可以发现,随着代码的执行,老年代GC次数在不断增加,而老年代的GC速度往往要比新生代慢很多。因此,这段代码在执行的过程中,会因为GC频繁而持续的卡顿。

GC频繁分析

GC为什么会发生的如此频繁,我们都知道老年代往往存放的是生命周期较长的对象,而上述代码里的变量都是在for循环内的局部变量,随着一次for循环的结束,这些对象都会变成垃圾对象,那为什么老年代会不断有对象产生呢?
其实是因为对象一开始都会在新生代中创建,而一旦新生代内存满了,并且在一次YGC之后,还是无法存放新对象,这时候就会将一部分对象转移到老年代,随着程序的不断运行,老年代里的对象积累越来越多,最终就会触发FullGC,回收老年代里的垃圾对象。

如何优化

优化的方法有两点

  • 调大新生代大小

首先,因为新生代的内存不足,导致对象被迫被转移到老年代。因此,我们可以设置新生代的内存大小,将其稍微设置大一些。利用Xmn调整新生代大小。我们设置-Xmn15m,再来观察GC的表现
在这里插入图片描述
可以发现,随着新生代内存大小的调整,FGC的次数较之前有了明显的下降,YGC的次数也不频繁,说明调整新生代大小可以让对象尽可能停留在新生代。但是我们发现FGC还没有完全消失,因此,我们可以采用第二个优化方案。

  • 调整新生代中Eden区与Survivor区的比例

通过上面的GC我们可以看到,S0与S1几乎占用率达到了100%,因此我们可以调整Eden区与S0、S1区域的内存比例,让对象尽可能的停留在Survivor区,等待下一次YGC,而不必进入老年代。利用XX:SurvivorRatio 参数调整新生代的内存分配比例,我们设置 -XX:SurvivorRatio=4,再次观察一次GC表现。
在这里插入图片描述
这次我们发现,通过增大新生代的大小,并调整新生代的内存分配区间,FGC一直保持在0,并且老年代的内存也没有一直增长。我们仅仅通过JVM相关参数的调整,就实现了一次JVM调优,成功将项目的GC频率大大降低,避免了系统的卡顿。

总结

本次示例代码,主要是带大家熟悉,什么是JVM调优,为什么我们需要对JVM进行调优,调优前后,我们能够改善哪些问题。想要真正对我们的生产环境进行相关调优,并且熟练的掌握JVM相关的知识与技术,还需要学习很多相关知识。后续我会就本文所涉及到的相关理论知识写一些理论型的博文,大家对不懂的概念也可以进行百度查阅。
文中如果有不正确的地方还望大家指正。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值