什么是吞吐量?
吞吐量:指在应用程序的生命周期内,应用程序所花费的时间 和 系统
总 运行时间的比值。系统总运行时间=应用程序耗时+GC耗时。如果系统运行了
100分钟,GC耗时1分钟,那么系统的吞吐量就是(100-1)/100 = 99%
什么是停顿时间?
停顿时间:指垃圾回收器正在运行时,应用程序的暂停时间。对于独占回收器而言,停顿时间可能比较长。使用并发的回收器时,由于垃圾回收和应用程序交替运行,程序的停顿时间会变短,但是,由于其效率很可能不如独占垃圾回收器,故系统的吞吐量可能会较低
怎么观察 jvm?
a 可以 用 jconsole,jvisualvm.exe 观察
b 使用jvm参数 ,输出相应的信息
-XX:+PrintGCDetails 输出详细的GC日志
-XX:+PrintGCTimeStamps选项。打开这个开关后,将额外输出GC的发生时间,以此,可以知道GC的
频率和间隔
-XX:+PrintTenuringDistribution 查看新生对象晋升老年代的实际阈值
-XX:PrintHeapAtGC开关,打印详细的堆信息,一旦打开它,那么每次GC时,都将打印堆得使用情况。
-XX:+TraceClassUnloading用于跟踪类卸载信息
-XX:+TraceClassLoading用于跟踪类加载情况
-XX:+PrintGCApplicationStoppedTime 和 -XX:+PrintGCApplicationConcurrentTime 参数。它们显示,
一次垃圾回收,GC的停顿时间 和 应用程序的执行时间
为了能将以上的输出信息保存到文件,可以使用 -Xloggc 参数指定GC日志的输出位置。
对于jvm的调整,至今还没有通用的规则。但是在 业内 的 一些 比较公认的观点,我还是比较赞成的。
1 尽可能让短命对象 存放在 young 区, 尽可能减少Full GC的次数
进入old区的对象,一般是生命周期比较长的对象。如果 常有 一些 朝生夕灭的对象进入old区,会增加Full GC的次数。
那么进入old区的条件是什么呢?
a 当对象的在young区的年龄达到一定的阈值,会晋升到old区
b 当s区空间的空闲比例 低于 一个阈值,会将s区部分对象 晋升到 old区(未测试过)
c 当分配一个较大的对象时,eden区的剩余空间放不下,s区的剩余空间也存放不下。那么就将对象直接放到old区,所以
我们开发的时候 要 避免 短命大对象
我们可以通过 -Xmx -Xms -Xmn 调整堆大小 , 年轻代老年代的比例。来减少GC的频次
我们 可以通过 调整 eden区 和 s区 的比例,来减少短命对象进入 old区的几率
下面是 调整 eden区 和 s区的 参数
-XX:SurvivorRatio = eden/s0 = eden/s1 设置新生代中,eden空间和S0空间的比例关系
-XX:NewRatio 可以用来设置 新生代 和 老年代的比例, ;老年代 / 新生代
这些JVM的XX参数在不同的JDK的版本中的实现可能会略有不同,在具体使用时,可以使用
-XX:+PrintGCDetails 参数打印出堆得实际大小
2 基于以上两点, 如果对响应时间有要求,可以 在可接受停顿范围内 限制 堆内存 的大小
堆太大会增加单次GC时间,stop the world 的时间会相应增长。这对于 一些响应时间要求比较高的 server来说,是不
能接受的。所以有时候,堆大小不建议设置太大,可以用多进程的方案来代替
堆内存大小 对于系统的性能有决定性的影响,分配太大 GC过程慢,分配太小,GC次数频次高
3 针对 吞吐量优先 与 响应时间优先, 选择合适的垃圾选择 器
怎么 分析 堆 快照
使用以下jvm参数运行 下面的java代码
-Xmx20m -Xms20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\m.hprof
其中
-XX:HeapDumpOnOutOfMemoryError 参数在程序发生OOM时,导出应用程序的当前快照
-XX:HeapDumpPath=C:\m.hprof 可以指定堆快照的保存位置
另外 也可以 在 程序运行 时 dump 堆快照文件来
jmap -dump:format=b,file=c:\heap.hprof ${pid}
java 代码是
public class HelloWorld {
public static void main(String[] args) throws InterruptedException {
byte[] c1 = new byte[1024*1024*8];
byte[] c2 = new byte[1024*1024*50];
System.out.println("........................");
}
}
下面 我使用 MAT工具 大开 堆 快照文件
在首页 就已经 帮我们 推断出 内存 溢出 的原因 ,是由于 一个 byte数组 实例 占用了 百分之94 内存
点击 箭头的图标,可以 排序出 相应的类 占用 堆内存的大小 , 也发现 一个 byte数组 实例 占用了 百分之94 内存,
此外 右键 可以 查看 该实例的 引用链,还有其他 的一些 操作 这里 就 不多说了
性能调优的 一般步骤
下面我来简单的演示一下 性能调优的一般步骤,由于程序过于简单,不喜勿喷
,具体调优 还要参考 程序 实现逻辑
工具
jmeter5.1
jvisualvm 或者观察 GC日志 ,也可以使用CAT,zabbix等的一些监控工具
下面 的 基于spring boot2.1的 java代码
@RestController
public class HelloWorldController {
@RequestMapping("hello")
public String hello(@RequestBody Map user){
byte[] bytes = DigestUtils.md5Digest(user.toString().getBytes());
java.util.List list = new ArrayList();
list.add(bytes);
System.out.println(user);
// System.out.println(age);
System.out.println(".......................");
return "hello";
}
}
初始 jvm参数 -Xmx50m -Xms50m
启动后,我们使用 jmeter 一秒 发送500个请求
可以 看到 young gc57次,full gc2次 ,平均响应 时间是 482ms
第一次 调整, 扩大堆内存,-Xmx700m -Xms700m
可以 看到 平均响应 时间 变为 15ms
第二次调整 使用 专注响应时间 的 G1垃圾回收器 -Xmx:]700m -Xms700m -XX:+UseG1GC
可以看到 平均响应 时间 只有 4ms
参考资料
深入理解java虚拟机
java程序性能优化指南