Jvm优化参考
version | author | date |
---|---|---|
V1.0.0 | 2020-04-29 | |
目录
Jvm优化参考... 1
一、 预先期望描述... 2
二、 基本概念理解... 3
2.1 gc过程... 3
2.2 gc范围... 3
2.3 栈相关... 4
2.4 堆相关... 4
2.5 Slot复用... 4
三、 图文描述... 5
3.1 优化思维导图... 5
3.2 jvm内存结构... 6
3.3 堆内存分代结构... 8
3.4 栈的结构模型... 9
四、 性能优化实施... 10
4.1 通过分配内存优化... 10
4.2 选择垃圾回收器... 13
4.3 减少gc开销... 14
五、 要点摘录... 16
5.1 内容出处... 16
5.2 摘录内容... 16
六、 常规典例图表... 19
6.1 jvm参数配置项及描述... 19
6.2 jvm常用配置列表... 19
6.3 jvm内存配置典例参考指南... 20
6.4 垃圾收集器选择典例参考指南... 20
一、 预先期望描述
预期:提高资源有效利用率
描述:依据场景而定方案,减少时间/空间/运维成本、降低gc开销
参考指标【待定】
二、 基本概念理解
2.1 gc过程
【内容出处】
http://ifeve.com/jvm-yong-generation/
【简易表述】
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor
GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
2.2 gc范围
在Java语言中,可作为GC Roots的对象包括下面几种:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
d) 本地方法栈中JNI(Native方法)引用的对象。
2.3 栈相关
未展开部分在【要点摘录】部分有补充,下同
2.4 堆相关
2.5 Slot复用
三、 图文描述
3.1 优化思维导图
图表 1【Jvm运行时数据区思维导图】
3.2 jvm内存结构
图表 2【jvm内存结构(Ver>=jdk8)】
图表 3jvm内存结构(Ver>=jdk8)
3.3 堆内存分代结构
图表 4【堆内存分代结构】
3.4 栈的结构模型
图表 5【栈的结构模型】
四、 性能优化实施
4.1 通过分配内存优化
【内容出处】
https://zhidao.baidu.com/question/367248545489149932.html
【参数配置列表与描述】
l
-Xms:指定最小堆内存,
描述:通常设置成跟最大堆内存一样,减少GC;
l
-Xmx:指定java程序的最大堆内存
描述:使用java
-Xmx5000M -version判断当前系统能分配的最大堆内存;
l
-Xmn:设置年轻代内存大小
描述:整个堆大小=年轻代大小+年老代大小。所以增大年轻代后,将会减小年老代大小。此值对系统性能影响 较大,Sun官方推荐配置为整个堆的3/8;
l
-Xss:指定线程的最大栈空间,
描述: 此参数决定了java函数调用的深度,值越大调用深度越深, 若值太小则容易出栈溢出错误 (StackOverflowError);
l
-XX:PermSize:指定方法区(永久区)的初始值,
描述:默认是物理内存的1/64,在Java8永久区移除, 代之的是元数据区,由-XX:MetaspaceSize指定;
l
-XX:MaxPermSize:指定方法区的最大值
描述:默认是物理内存的1/4,在java8中由-XX:MaxMetaspaceSize指定元数据区的大小;
l
-XX:NewRatio=年老代与年轻代的比值
描述:如 -XX:NewRatio=2, 表示年老代与年轻代的比值为2:1;
l
-XX:SurvivorRatio= Eden区与Survivor区的from和to的大小比值
描述:-XX:SurvivorRatio=8表示Eden区与Survivor区的大小比值是8:1:1,因为Survivor区有两个(from, to)。
l
JVM事实上分为三大块,年轻代(YoungGen),年老代(Old Memory),及持久代(Perm,在Java8中被取消)。
年轻代大小选择
【响应时间优先的应用】
尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
【吞吐量优先的应用】
尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
【响应时间优先的应用】
年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在年轻代和年老代回收上的时间比例。减少年轻代和年老代花费的GC时间,一般会提高应用的效率。
【吞吐量优先的应用】
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
l
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
l
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下这里设置多少次Full GC后,对年老代进行压缩。
备注:详情见表
4.2 选择垃圾回收器
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数。
备注:详情见表
4.3 减少gc开销
【内容出处】
https://www.cnblogs.com/zxf330301/p/5366404.html
【理由】
程序的运行会直接影响系统环境的变化,从而影响GC的触发。若不针对GC的特点进行设计和编码,就会出现内存驻留等一系列负面影响。为了避免这些影响,基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。
【措施】
(1)不要显式调用System.gc()
此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。
(2)尽量减少临时对象的使用
临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
(3)对象不用时最好显式置为Null
一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
(4)尽量使用StringBuffer,而不用String来累加字符串(详见blog另一篇文章JAVA中String与StringBuffer)
由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
(5)能用基本类型如Int,Long,就不用Integer,Long对象
基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
(6)尽量少用静态对象变量
静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
(7)分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。
五、 要点摘录
5.1 内容出处
https://blog.csdn.net/rongtaoup/article/details/89142396
5.2 摘录内容
1. 栈是线程私有的,他的生命周期与线程相同。每个线程都会分配一个栈的空间,即每个线程拥有独立的栈空间。
2. 栈帧是栈的元素。每个方法在执行时都会创建一个栈帧。栈帧中存储了局部变量表、操作数栈、动态连接和方法出口等信息。每个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程。
3. 栈帧中,由一个局部变量表存储数据。局部变量表中存储了基本数据类型(boolean、byte、char、short、int、float、long、double)的局部变量(包括参数)、和对象的引用(String、数组、对象等),但是不存储对象的内容。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
4. 局部变量的容量以变量槽(Variable Slot)为最小单位,每个变量槽最大存储32位的数据类型。对于64位的数据类型(long、double),JVM 会为其分配两个连续的变量槽来存储。以下简称 Slot
5. JVM 通过索引定位的方式使用局部变量表,索引的范围从0开始至局部变量表中最大的 Slot 数量。普通方法与 static 方法在第 0 个槽位的存储有所不同。非 static 方法的第 0 个槽位存储方法所属对象实例的引用。
6. 为了尽可能的节省栈帧空间,局部变量表中的 Slot 是可以复用的。方法中定义的局部变量,其作用域不一定会覆盖整个方法。当方法运行时,如果已经超出了某个变量的作用域,即变量失效了,那这个变量对应的 Slot 就可以交给其他变量使用,也就是所谓的** Slot 复用**。
7. 再虚拟机的运行参数中加上“-verbose:gc”,这个参数的作用就是打印 GC 信息StackOverflowError:栈溢出错误
8. 如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出 StackOverflowError
9. OutOfMemoryError:内存不足,栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
如何设置栈参数?
10. 使用 -Xss 设置栈大小,通常几百K就够用了。由于栈是线程私有的,线程数越多,占用栈空间越大。
11. 栈决定了函数调用的深度。这也是慎用递归调用的原因。递归调用时,每次调用方法都会创建栈帧并压栈。当调用一定次数之后,所需栈的大小已经超过了虚拟机运行配置的最大栈参数,就会抛出 StackOverflowError 异常。
12. 堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被所有线程共享。主要存放使用new关键字创建的对象。所有对象实例以及数组都要在堆上分配。垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间(收集的是对象占用的空间而不是对象本身)。
13. 年轻代存储“新生对象”,我们新创建的对象存储在年轻代中。当年轻内存占满后,会触发Minor GC,清理年轻代内存空间。
14. 老年代存储长期存活的对象和大对象。年轻代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。老年代空间占满后,会触发Full GC。
15. 注:Full GC是清理整个堆空间,包括年轻代和老年代。如果Full GC之后,堆中仍然无法存储对象,就会抛出OutOfMemoryError异常。
六、 常规典例图表
6.1 jvm参数配置项及描述
6.2 jvm常用配置列表
6.3 jvm内存配置典例参考指南
6.4 垃圾收集器选择典例参考指南
需要源文件的朋友可以加我微信:erfsfj-wx
doc:
jvm调优典例
jvm调优说明