JVM——》性能优化

38 篇文章 3 订阅

JVM的性能优化可以分为:
1、代码层面
结合字节码指令进行优化,比如一个循环语句,可以将循环不相关的代码提取到循环体之外,这样在字节码层面就不需要重复执行这些代码了。
2、非代码层面
一般情况可以从内存、gc以及cpu占用率等方面进行优化。

一、内存

1、内存分配

正常情况下不需要设置,那如果是促销或者秒杀的场景呢?
每台机器配置2c4G,以每秒3000笔订单为例,整个过程持续60秒

在这里插入图片描述

2、内存溢出

一般有2个原因:
1)大并发
2)内存泄露导致内存溢出

(1)大并发

浏览器缓存、本地缓存、验证码
CDN静态资源服务器
集群+负载均衡
动静态资源分享、限流(基于令牌桶、漏桶算法)
应用级别缓存、接口防刷限流、队列、Tomcat性能优化
异步消息中间件
Redis热点数据对象缓存
分布式锁、数据库锁
5分钟之内没有支付,取消订单、恢复库存等

(2)内存泄露导致内存溢出

JVM——》内存泄露案例

二、GC

这里以G1垃圾收集器调优为例

1、是否选用G1

官网链接

(1)50%以上的堆被存活对象占用
(2)对象分配和晋升的速度变化非常大
(3)垃圾回收时间比较长

2、G1调优

每一步设置参数后,获取到gc日志,使用GCViewer分析吞吐量和响应时间

(1)使用G1垃圾收集器
-XX:+UseG1GC

(2)调整内存
-XX:MetaspaceSize=100M
-Xms300M
-Xmx300M

(3)调整最大停顿时间
-XX:MaxGcPauseMillis=200

(4)启动并发GC时堆内存占用百分比
-XX:InitiatingHeapOccupancyPercent=45
G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为 0 则表示“一直执行 GC循环)'. 默认值为 45 (例如, 全部的 45% 或者使用了45%).


3、G1调优最佳实战

[官网链接](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#r ecommendations))

(1)不要手动设置新生代和老年代的大小 ,只要设置整个堆的大小

1)G1收集器在运行过程中,会自己调整新生代和老年代的大小
2)其实是通过adapt代的大小来调整对象晋升的速度和年龄,从而达到为收集器设置的暂停时间目标
3)如果手动设置了大小就意味着放弃了G1的自动调优

(2)不断调优暂停时间目标

1)一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样)
2)如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。
3)所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满足

(3)使用-XX:ConcGCThreads=n来增加标记线程的数量

IHOP如果阀值设置过高,可能会遇到转移失败的风险,比如对象进行转移时空间不足。
如果阀值设置过低,就会使标记周期运行过于频繁,并且有可能混合收集期回收不到空间。 
IHOP值如果设置合理,但是在并发周期时间过长时,可以尝试增加并发线程数,调高 ConcGCThreads。

(4)MixedGC调优

-XX:InitiatingHeapOccupancyPercent 
-XX:G1MixedGCLiveThresholdPercent 
-XX:G1MixedGCCountTarger 
-XX:G1OldCSetRegionThresholdPercent

(5)适当增加堆内存大小
(6)不正常的Full GC

有时候会发现系统刚刚启动的时候,就会发生一次Full GC,但是老年代空间比较充足,一般是由Metaspace 区域引起的。可以通过MetaspaceSize适当增加其大小,比如256M。

三、CPU

四、常见问题

在这里插入图片描述

1、内存泄漏与内存溢出的区别

内存泄漏是指不再使用的对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。
内存泄漏很容易导致内存溢出,但内存溢出不一定是内存泄漏导致的。

2、 young gc会有stw吗?

不管什么 GC,都会发送 stop-the-world,区别是发生的时间长短。
而这个时间跟垃圾收集器又有关系,Serial、PartNew、Parallel Scavenge 收集器无论是串行还是并行,都会挂起用户线程,而 CMS和 G1 在并发标记时,是不会挂起用户线程的,但其它时候一样会挂起用户线程,stop the world 的时间相对来说就小很多了。

3、 major gc和full gc的区别

Major GC在很多参考资料中是等价于 Full GC 的,我们也可以发现很多性能监测工具中只有 Minor GC
和 Full GC。

一般情况下,一次 Full GC 将会对年轻代、老年代、元空间以及堆外内存进行垃圾回收。

触发 Full GC 的原因有很多:
  当年轻代晋升到老年代的对象大小,并比目前老年代剩余的空间大小还要大时,会触发 Full GC;
  当老年代的空间使用率超过某阈值时,会触发 Full GC;
  当元空间不足时(JDK1.7永久代不足),也会触发 Full GC;
  当调用 System.gc() 也会安排一次 Full GC。

4、 什么是直接内存

Java的NIO库允许Java程序使用直接内存。

直接内存是在java堆外的、直接向系统申请的内存空间。
通常访问直接内存的速度会优于Java堆。
因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。

由于直接内存在java堆外,因此它的大小不会直接受限于Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。

5、 垃圾判断的方式

引用计数法:指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为0就会回收但是JVM没有用这种方式,因为无法判定相互循环引用(A引用B,B引用A)的情况。

引用链法: 通过一种GC ROOT的对象(方法区中静态变量引用的对象等-static变量)来判断,如果有一条链能够到达GC ROOT就说明,不能到达GC ROOT就说明可以回收。

6、 不可达的对象一定要被回收吗?

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;

可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize 方法。当对象没有覆盖 finalize 方法,或finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

7、 为什么要区分新生代和老年代?

当前虚拟机的垃圾收集都采用分代收集算法,只是根据对象存活周期的不同将内存分为几块。

一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制
成本就可以完成每次垃圾收集。
老年代,对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

8、 G1与CMS的区别是什么

分代回收不一样:
  CMS 主要集中在老年代的回收
  G1 集中在分代回收,包括了年轻代的 Young GC 以及老年代的 Mix GC

实现方式不一样:
G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的 产生;在初始化标记阶段,搜索可达对象使用到的 Card Table

9、 方法区中的无用类回收

方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?

类需要同时满足下面 3 个条件才能算是 “无用的类” :
a-该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
b-加载该类的 ClassLoader 已经被回收。
c-该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值