028-86261949

当前位置:首页 > 技术交流 > Jvm优化指南

Jvm优化指南

2020/01/07 17:50 分类: 技术交流 浏览:1

Jvm简介

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

 

Java Hotspot vm参数配置

-client -server -classpath / -cp

-verbose :class 输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断。

-verbose:gc 输出每次GC的相关情况

-verbose:jni 输出native方法调用的相关情况,一般用于诊断jni调用错误信息。

 

jvm参数

Heapsize 堆栈大小

-Xms 定义最小值 -Xmx 定义最大值  建议设置相同,防止垃圾收集器在最小、最大之间收缩堆而产生额外开销。

GC垃圾回收--新生代New配置

 -XX:newSize

-XX:MaxNewSize

建议设置相同,防止年轻代的堆收缩。

GC垃圾回收—旧生代Old配置 -XX:NewRadio=2表示新生代:老年代 = 1:2

 

Jit编译器:c1/c2

在HotSpot VM中内嵌有两个JIT编译器,分别为Client Compiler和Server Compiler,但大多数情况下我们简称为C1编译器和C2编译器。开发人员可以通过如下命令显式指定Java虚拟机在运行时到底使用哪一种即时编译器,如下所示:

C1编译器会对字节码进行简单和可靠的优化,以达到更快的编译速度;而C2编译器会启动一些编译耗时更长的优化,以获取更好的编译质量。

 

垃圾回收

Java的堆内存模型

 

 

新生代(Young generation)

绝大多数被新建的对象会分配到这里,由于大部分对象在创建后会很快变得不可达,所以很多对象被创建在新生代,然后消失,对象消失的过程称为”minor GC”

新生代存在1个eden区和2个survivor区,新对象会首先分配到eden区,当然如果对象过大会直接分配到老年代,在gc中,eden中的对象会被移动到survivor中,直到对象满足一定年纪(熬过gc的次数),会被移动到老年代

可以设置新生代和老年代的相对大小,这种方式的优点是新生代的大小随着堆内存的动态扩展,设置方式为-XX:NewRatio,例如-XX:NewRatio=8代表老年代/新生代为8/1,老年代占堆内存的7/8,新生代占1/8

 

老年代(Old generation)

对象没有变的不可达,在新生代中存活下来的对象会被拷贝到这里,其占用的空间比新生代多,因为其占用空间大,所以发生在老年代上的gc比新生代少,对象从老年代消失的过程称之为”major GC”或者”full GC”

 

永久代(permanent generation)

像一些类级的层次信息,方法数据和方法信息,运行时常量(jdk1.7之后移除永久代),已确定的符号引用和虚方法表等等,它几乎是静态的很少被回收,在jdk8之前的hotspot虚拟机中,类的这些”永久的”数据存放在一个叫做永久代的区域,在jvm启动时可以设置-XX:MaxPermsize的值来控制永久代的大小,但是jdk1.8之后取消了永久代,这些元素被移到了与堆不相连的本地内存区域

 

Gc参数

 

hotspot实现垃圾回收细节

一致性

一致性要求gc进行时必须停顿所有java执行线程

安全点

程序只有达到安全点才能暂停,安全点的标准就是是否让程序长时间执行的特征,比如方法调用和循环跳转这种,具有这些才会产生安全点

程序暂停的2种方式

抢先式中断:在gc发生时,主动中断所有线程

主动式中断:设一个标志,各线程去轮询这个标志,遇到中断则暂停,轮询地方与安全点重合

 

垃圾收集器

 

Serial收集器

Serial收集器是最基本,发展历史最悠久的收集器,曾经(在jdk1.3.1之前)是虚拟机新生代收集的唯一选择

这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。

它的优势在于简单而高效,对于限定单个CPU来说,由于serial收集器没有线程交互的开销,专心做垃圾收集自然能获得最高的单线程收集效率

 

ParNew 收集器

ParNew收集器其实就是serial收集器的多线程版本,除了使用多线程执行垃圾回收外,其余行为跟serial收集器一模一样,例如在回收算法和回收策略,分配规则都是一样的,在实现上这2种收集器共用了很多代码

Parnew的应用场景:是运行在server模式下的虚拟机的首选新生代垃圾回收器,因为它是除serial收集器外唯一能与cms收集器配合工作

 

Serial收集器与parnew收集器比较

Parnew收集器在单cpu环境下绝对不会比serial收集器有更好的效果,因为它存在线程的交互开销,但是随着可以使用的CPU数量增加,它对于gc时系统资源的有效利用还是很好的

 

Parallel Scavenge 收集器

是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。应用场景主要是针对那些需要频繁与用户交互的程序,良好的响应速度能提升用户体验

 

Serial Old 收集器

Serial收集器的老年代版本,单线程,使用 标记 —整理算法。

应用场景:

Client模式:serial old收集器主要意义在于给client模式下的虚拟机使用

Server模式:在server模式下有2大用途:一种用途是在jdk1.5之前与Parallel Scavenge 收集器配合使用,另一种用途是作为cms收集器的备用预案,在并发收集失败时使用

 

Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多线程,使用 标记 — 整理算法

应用场景:在注重吞吐量以及CPU资源敏感的场合,可以优先考虑Parallel Old 和 Parallel Scavenge 收集器

这个收集器是在jdk1.6中才提供的

CMS 收集器

CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记 —清除算法实现。

运作步骤:

初始标记(CMS initial mark):标记 GC Roots 能直接关联到的对象,需要”stop the world”

并发标记(CMS concurrent mark):进行 GC Roots Tracing

重新标记(CMS remark):修正并发标记期间的变动部分,仍然需要”stop the world”

并发清除(CMS concurrent sweep):会清除对象

 

缺点:

1,cms收集器对CPU资源比较敏感,cms默认启动的回收线程数是(CPU数量+3)/4,也就是CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并随着CPU数量的增加而下降,但是,如果CPU不足4个时,cms对用户程序的影响变得很大

2,cms收集器无法处理浮动垃圾,可能出现”concurrent mode failure”失败而导致另一次full gc产生

3,cms收集器会产生大量空间碎片,因为它是基于标记-清除算法的收集器,故在收集结束时会产生大量空间碎片,空间碎片过多会给大对象分配带来很大麻烦,比如老年代还有很大空间剩余,但是无法找到更大的连续空间来分配对象从而不得不提前触发一次full gc

G1 收集器

G1(garbage first)是一款面向服务端应用的垃圾收集器,Hotspot开发团队是希望用它替换掉jdk1.5中发布的cms收集器

G1的优点:并行与并发、分代收集、空间整合、可预测停顿。

运作步骤:

 

初始标记(Initial Marking)

并发标记(Concurrent Marking)

最终标记(Final Marking)

筛选回收(Live Data Counting and Evacuation)

 

实例分析

实例1

java.lang.OutOfMemoryError: GC overhead limit exceeded

通过Linux查看进程的命令ps -ef|grep java发现

-xms768m -xmx768m -xx:NewSize=320m, -xx:MaxNewSize=320m

通过分析应用堆区内存设置只有768m,机器内存有2g,机器上只跑了一个java应用,没有其他需要占内存的地方,另外,这个应用比较大,需要占得内存比较比较多所以修改配置如下

-xms1280m -xmx1280m -xx:NewSize=500m, -xx:MaxNewSize=500m

跟踪运行情况发现,异常没有再出现,问题解决

 

实例2

一个服务系统,经常出现卡顿,分析原因,发现full gc时间太长

Jstat -gcutil:

S0     S1    E     O     P       YGC     YGCT   FGC   FGCT     GCT

12.16  0.00  5.18   63.78  20.32    54    2.047    5     6.946    8.993

分析上面的数据,发现YGC执行54次,耗时2.047秒,每次YGC耗时37ms,在正常范围,而FGC执行了5次,耗时6.946秒,每次耗时1.389秒数据显示问题

Full gc耗时较长,分析该系统发现-XX:NewRadio=9,也就是新生代和老年代的比例是1:9

这会造成:

1,新生代内存太小,导致对象提前进入老年代,从而触发full gc

2,老年代较大,进行full gc耗时较长

解决方法是将-XX:NewRadio=4,意思是将对象控制在新生代就清理掉,没有进入老年代(这种做法针对一些应用有效,并不是所有应用都有效)

 

总结

1,多数的Java应用不需要在服务器上进行GC优化;

2,多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;

3,在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合)

4,减少创建对象的数量

5,减少使用全局变量和大对象;

6,GC优化是到最后不得已才采用的手段

7,在实际使用中,分析GC情况优化代码比优化GC参数要多得多

GC优化的目的有2个

1,将转移到老年代的对象数量降低到最少

2,减少full gc的执行时间

为达目的可以这样做:

1,减少使用全局变量和大对象

2,调整新生代的大小到最合适

3,设置老年代的大小为最合适

4,选择合适的GC收集器

 

 

 

 

 

 

 

#标签:Jvm优化指南,jvm参数