JVM调优和深入理解性能优化

JVM调优的本质

JVM调优主要调的是稳定,如果系统出现频繁的垃圾回收,这个时候系统是不稳定的,所以需要我们来进行JVM调优,调整垃圾回收的频次。

GC调优原则

1.大多数的java应用不需要GC优化。
2.大部需要GC调优的不是参数问题,而是代码问题。
3.分析GC情况优化代码比优化GC擦书多得多。
4.GC调优是最后手段。

观察需要调优的条件

MinorGC执行时间不到50ms
MinorGC执行不频繁,约10s一次
FullGC执行时间不到1s
FullGC执行频率不算频繁,大于10分钟1次

GC 调优

项目启动 GC 优化

分析当前 JVM 参数设置,并且分析当前堆内存快照和 gc 日志,根据实际的各区域内存划分和 GC 执行时间,觉得是否进行优化。
以案例ref-comet为例进行GC
1.开启日志分析 -XX:+PrintGCDetails 发现有多次GC包括FullGC

[GC (Allocation Failure) [PSYoungGen: 33280K->5096K(38400K)] 33280K->7256K(125952K), 0.1500904 secs] [Times: user=0.08 sys=0.00, real=0.17 secs] 
...
[GC (Allocation Failure) [PSYoungGen: 38376K->5097K(38400K)] 40536K->12002K(125952K), 0.0070985 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 38377K->5090K(38400K)] 45282K->17660K(125952K), 0.0076426 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 38370K->5106K(71680K)] 50940K->22147K(159232K), 0.0065619 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 71666K->5114K(71680K)] 88707K->31691K(159232K), 0.0101585 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
....
[GC (Allocation Failure) [PSYoungGen: 71674K->13417K(146944K)] 98251K->40002K(234496K), 0.0140014 secs] [Times: user=0.03 sys=0.02, real=0.01 secs] 
[GC (Metadata GC Threshold) [PSYoungGen: 38417K->4297K(147968K)] 65001K->35363K(235520K), 0.0130236 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 4297K->0K(147968K)] [ParOldGen: 31065K->12806K(67584K)] 35363K->12806K(215552K), [Metaspace: 20786K->20786K(1069056K)], 0.0638099 secs] [Times: user=0.22 sys=0.00, real=0.06 secs] 
...
[GC (Allocation Failure) [PSYoungGen: 132096K->10677K(222208K)] 144902K->23492K(289792K), 0.0125697 secs] [Times: user=0.05 sys=0.02, real=0.01 secs] 
...
[GC (Metadata GC Threshold) [PSYoungGen: 79599K->9130K(275456K)] 92413K->21952K(343040K), 0.0102760 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[Full GC (Metadata GC Threshold) [PSYoungGen: 9130K->0K(275456K)] [ParOldGen: 12822K->16713K(94720K)] 21952K->16713K(370176K), [Metaspace: 34598K->34598K(1081344K)], 0.0696084 secs] [Times: user=0.20 sys=0.00, real=0.07 secs] 
...
Heap
 PSYoungGen      total 275456K, used 95567K [0x00000000d5f00000, 0x00000000e9400000, 0x0000000100000000)
  eden space 259072K, 36% used [0x00000000d5f00000,0x00000000dbc53c70,0x00000000e5c00000)
  from space 16384K, 0% used [0x00000000e5c00000,0x00000000e5c00000,0x00000000e6c00000)
  to   space 15872K, 0% used [0x00000000e8480000,0x00000000e8480000,0x00000000e9400000)
 ParOldGen       total 94720K, used 16713K [0x0000000081c00000, 0x0000000087880000, 0x00000000d5f00000)
  object space 94720K, 17% used [0x0000000081c00000,0x0000000082c527f8,0x0000000087880000)
 Metaspace       used 38764K, capacity 39360K, committed 39848K, reserved 1085440K
  class space    used 4366K, capacity 4523K, committed 4528K, reserved 1048576K

从日志中可以看出有9次MinorGC,2次FullGC。从日志描述Metadata GC Threshold可知,频繁GC的原因是元空间不够了,所以我们需要对原空间的初始空间做设置。从最后一次FullGC可看出元空间为Metaspace: 34598K。
2.将元空的初始空间扩大为64M。加入参数-XX:MetaspaceSize=64m 减少 FullGC。
调整之后FullGC消失,有8次MinorGC
3、 减少 Minor gc 次数,增加参数 -Xms500m GC 减少至 3 次。
4.减少 MinorGC次数,调整参数 -Xms1000m GC 减少至 1次。
5.增加新生代比重,增加参数 -Xmn900m MinorGC 减少至 1 次
6.加大新生代,调整参数 -Xms2000m -Xmn1800m 还是避免不了 GC,没有必要调整这么大,资源浪费。

使用JMeter压测

在这里插入图片描述

使用单线程 GC -XX:+UseSerialGC

在这里插入图片描述

使用多线程 GC -XX:+UseParNewGC

在这里插入图片描述

使用 CMS -XX:+UseConcMarkSweepGC

在这里插入图片描述
CMS 采用了并发收集,所以 STW 的时间较小,吞吐量较单线程有一定提高,最大请求时间 MAX 有明显的下降。

使用 G1 -XX:+UseG1GC

在这里插入图片描述
G1 的吞吐量是最大的,最大请求时间 MAX时间缩短。

逃逸分析

逃逸分析是JVM优化的一种手段。默认情况下逃逸分析是开启的。下面使用一个案例来理解什么是逃逸分析。

/**
 *  栈上分配 -XX:+PrintGC
 */
public class StackAlloc {

	public static class User{
		public int id = 0;
		public String name = "";
	}


	public static void alloc() {
		User u = new User();  //Object  在堆上分配的() ,有逃逸分析的技术 ,在栈中分配的
		u.id = 5;
		u.name = "King";
	}

	public static void main(String[] args) {

		long b = System.currentTimeMillis(); //开始时间
		for(int i=0;i<100000000;i++) {//一个方法运行1亿次()
			alloc();
		}
		long e = System.currentTimeMillis(); //结束时间
		System.out.println(e-b);//打印运行时间:毫秒
	}
}

执行结果没有发生GC:

4

不断的new对象,为什么没有被回收?

User一般会认为分配在堆里边,但是案例中的User u= new User()是分配的栈中,这是JVM自动优化的一种手段。因为如果是逃逸分析出来的对象可以在栈上分配的话,那么该对象的生命周期就跟随线程了,就不需要垃圾回收,如果是频繁的调用此方法则可以得到很大的性能提高。没有逃逸分析—对象都在堆上分配(触发频次 GC,加重负担)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值