JVM常用参数(七)
内存监控
-XX:+PrintGC
跟-verbose:gc效果一样 但是不是稳定版后续可能会删除
-verbose:gc
每次GC(yonggc,fullgc) 打印简单的内存情况
测试代码
public static void main(String[] args){ List<Classes> classes=new ArrayList<Classes>(); int count=0; for(int i=0;true;i++){ classes.add(new Classes()); if(classes.size()>10000){ count++; classes.clear(); classes=new ArrayList<Classes>(); System.out.println("标记为可回收"); } if(count>20){ break; } } }
1253K表示为回收前占用内存 903k表示回收后占用内存 可以发现标记回收后垃圾对象被成功回收, 3584为总内存 最后一个为回收时间
GC (Allocation Failure 表示为新生代回收
如果我们不标记为可回收会怎么样
因为没有标记为已回收 新生代from to 迭代15次或者满了以后直接放到老年代 循环多次后老年代内存快满了时触发fullGC 因为都没有标记为可回收所以每次回收后 占用内存没有变化 最终导致内存溢出
XX:+DisableExplicitGC
禁止代码调用System.gc() 加了此参数则调用无效
-XX:MaxTenuringThreshold
控制在复制区域存活的年龄,超过年龄才到老年代 默认15,注:但是并不表示到了15才会到老年代,如果eden区回收非垃圾对象在复制区域申请不到足够的内存,则直接托管到老年代
-XX:MaxTenuringThreshold=15
-XX:+PrintGCDetails
会自动开启-XX:+PrintGC
-XX:+PrintGCDetails
打印每次gc的回收情况 程序运行结束后打印堆空间内存信息(包含内存溢出的情况)
[PSYoungGen: 512K->400K(1024K)] 表示年轻代占用空间 回收前和回收后
512K->400K(1536K) 表示为java堆的空间总内存的回收签回收后内存
PsYoungGen 为新生代 总内存(total1024k ) 使用(used 462K) 其中:
eden区 512K
from区 512K
to区 521K
年轻代可用空间为1024可以发现eden+from+to大于1024 新生代的内存为eden+from或者+to 因为年轻代采用复制算法 所以复制区域会有一块儿重复的区域512 不能使用
ParOldGen 为老年代 总空间512K
Metaspace PermGen永久代废弃 jdk8使用 Metaspace(元空间) 替代
-XX:+PrintGCTimeStamps
针对GC日志 打印启动到日志打印经历的时间,可以分析每次GC回收的间隔
打印每次gc的间隔的时间戳 full gc为每次对新生代老年代以及整个空间做统一的回收 系统中应该尽量避免
产生fullgc的几种情况 老年代空间不足 持久代(元空间 或者jdk8的元空间)空间不足 手动调用system.gc 可以使用可以DisableExplicitGC来禁止
-XX:+TraceClassLoading
打印类加载情况
-XX:+PrintClassHistogram
打印每个类的实例的内存占用情况
通过按ctrl+Break 会打印 用的mac不知道咋么按 哈哈哈
-XX:+PrintHeapAtGC
每次GC(yonggc,fullgc) 都会打印GC前后的整堆内存占用情况
回收前年轻代eden区使用100% 老年代使用0% 回收后 eden区放到from区 老年代使用14%
-Xloggc
配合上面的使用将上面的日志打印到指定文件
-Xloggc:/Users/liqiang/Desktop/logs/log.log 按启动时间生成gc_-%t.log 如:gc_-2022-04-21_21-19-07.log gc_ewei-com-talk1-2022-04-21_21-11-14.log
-XX:+HeapDumpOnOutOfMemoryError
发生内存溢出将堆信息转存起来 以便分析
-XX:HeapDumpPath=/home/ewei/dump/$HOSTNAME-oom-dump.hprof 按启动时间生成 dump_-%t.hprof
-XX:HeapDumpPath为转存位置 生成的文件使用JProfiler 打开 分析
-XX:OnOutOfMemoryError
也可以在内存溢出时执行脚本 比如发送邮件给系统管理员脚本
-XX:OnOutOfMemoryError ="sh ~/cleanup.sh"
-XX:ErrorFile
只是在针对异常崩溃 如:jdk bug 或者在自己程序有问题调用了调用了native的方法的时候才会有用,什么叫调用了native的方法?比如调用了C/C++写的库文件,比如C/C++里面调用JVM。
针对手动kill 或者linux 自动触发oom kill是是无法触发的
有2种配置
-XX:ErrorFile=./hs_err_pid<pid>.log 指定当异常崩溃保存的错误日志路径 默认是在jdk目录下,如过有权限的话
-XX:OnError
-XX:OnError="<cmd args>;<cmd args>” 出现致命ERROR之后运行自定义命令 如:"sh ~/cleanup.sh"
触发OnError或者ErrorFile的场景比较难模拟
内存分配参数
-xms注意事项
回收后空闲堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;回收后空闲堆内存大于70%时,JVM会减少堆直到-Xms的最小限制多余的内存会归还给操作系统。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
一般我们都会设置一样大,减少归还后申请的性能消耗,如果设置一样大,如果使用内存到达过最大峰值 进程top命令 进程占用内存将不会将来下,由jvm调度
计算公式为:HeapFreeRatio =(CurrentFreeHeapSize/CurrentTotalHeapSize) * 100,值的区间为0到100,默认值为 40。如果HeapFreeRatio < MinHeapFreeRatio,则需要进行堆扩容,扩容的时机应该在每次垃圾回收之后。
-XX:MaxHeapFreeRatio 代表当空闲区域超过该值时,会进行“缩容”,缩容的下限为Xms
空闲堆空间的最大百分比,计算公式为:HeapFreeRatio =(CurrentFreeHeapSize/CurrentTotalHeapSize) * 100,值的区间为0到100,默认值为 70。如果HeapFreeRatio > MaxHeapFreeRatio,则需要进行堆缩容,缩容的时机应该在每次垃圾回收之后。
以上2个默认值 可通过jmap查看具体设置值 参考https://www.cnblogs.com/LQBlog/p/10691903.html#autoid-3-0-0
-Xmx -Xms 堆的最大内存和最小内存(最小内存为初始内存 ,如果满了将不断扩容到最大内存)默认是物理内存的1/64
-Xmx20m -Xms20m 则固定堆空间为20m(年轻代+老年代)
-XX:SurvivorRatio Survivor(from-to)区和eden区的占比
例如 -XX:SurvivorRatio=5 则是5:1:1
-XX:SurvivorRatio=8:则是8:1:1
2560*(2/7) 则是from和to的大小 剩下则是eden区的大小
-XMmn 设置新生代的大小 (绝对值)
如 -Xmn2m 则设置新生为2m
-XX:NewRatio 新生代占老年代的比例
如果是4 则是1:4 如果是5则为1:5 默认为2
如:
虽然新生代空间为2m但是2m通过计算eden和from to的的空间 但是真实新生代则是 eden+from或者to
-XX:PermSize -XX:PermSize 设置永久代的值和最大值
因为jdk8之后溢出了永久代 使用元空间代替
MaxMetaspaceSize 元空间大小受制于操作系统内存
-XX:MetaspaceSize=256m
的含义到底是什么呢?其实,这个JVM参数是指Metaspace扩容时触发FullGC的初始化阈值,也是最小的阈值。这里有几个要点需要明确:
- 如果没有配置
-XX:MetaspaceSize
,那么触发FGC的阈值是21807104(约20.8m),可以通过jinfo -flag MetaspaceSize pid得到这个值; - 如果配置了
-XX:MetaspaceSize
,那么触发FGC的阈值就是配置的值; - Metaspace由于使用不断扩容到
-XX:MetaspaceSize
参数指定的量,就会发生FGC;且之后每次Metaspace扩容都可能会发生FGC(至于什么时候会,比较复杂,跟几个参数有关); - 如果Old区配置CMS垃圾回收,那么扩容引起的FGC也会使用CMS算法进行回收;
- 如果MaxMetaspaceSize设置太小,可能会导致频繁FullGC,甚至OOM
XX:MetaspaceSize太小也会导致频繁扩容 每次扩容都导致【Full GC(Metadata GC Threshold)xxxxx, xxxxx】 建议是不断调整跟max保持一致
当Metaspace超过max
官方推荐堆空间内存分配
新生代占堆的3/8 幸存代占新生代的1/10
栈空间内内存分配
-Xss 默认256
栈空间是保存变量的地址 所以栈空间的大小决定了方法调用的深度 。比如递归方法会产生大量的变量
设置栈大小为108k 运行 几秒后栈溢出
则报栈溢出
我们如果我们将参数改为10m 则会等很久才会内存溢出
内存分配参数默认值
-Xms(minimum memory size for pile and heap) 默认情况下为机器内存的64分之一
-Xmx(maximum memory size for pile and heap) 默认情况下为机器内存的4分之一 等同于-XX:MaxHeapSize
-Xmn(年轻代的大小具体值)默认情况下堆内存的64分之一
-XX:NewRatio(年轻代占老年代的比例)默认为2(1:2) 注:不同垃圾回收器可能不一样,会有一点点差距 比如我测试的CMS和jdk8默认 设置一样的比例和堆大小会有差距,可以通过jstat和jmap查看
-XX:SurvivorRatio(eden区与form to区的比例)默认为8(8:1:1) 设置为4,则表示S0C:S1C:EC=1:1:4 ,设置为1 则是 1:1:! 注:不同垃圾回收器可能不一样,会有一点点差距
jdk永久代大小设置 -XX:PermSize=64M -XX:MaxPermSize=128M
避免Concurrent Mode Failure
年老代剩余空间>=EDEN+SURVIROR,即:
(Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)>=(Xmn-Xmn/(SurvivorRatior+2))
优化相关
-XX:+UseFastAccessorMethods
get,set 方法转成本地代码(对于jvm来说是冗余代码,jvm将进行优化)
-XX:LargePageSizeInBytes
涉及MMU原理和TLB原理。我的理解是默认情况会通过遍历虚拟地址页获得具体物理地址,但是遍历查找过程是耗时的,所以上层增加了一层TLB缓存,缓存访问地址对应的虚拟地址页,
则可以减少虚拟地址页的遍历,TLB缓存有限制,所以通过调大虚拟地址页的大小,让TLB可以容纳更多虚拟地址与虚拟地址页的映射,但是又不能调整太大一般6-64M
-XX:+UseFastAccessorMethods
我理解应该是针对成员方法 返回值值字节码是aload_0; getfield #index; areturn或ireturn 可以理解成简单方法比如我们VO里面的get set这种简单方法,将不会进行方法计数,也表示不会被统计为热点方法,经过即时编译器编译进行优化
如:
-XX:SoftRefLRUPolicyMSPerMB
软引用的存活时间 参考:https://www.cnblogs.com/LQBlog/p/16075841.html
-Xrs
减小jvm对操做系统信号(signals)的使用,该参数从1.3.1开始有效;
从jdk1.3.0开始,jvm容许程序在关闭以前还能够执行一些代码(好比关闭数据库的链接池),即便jvm被忽然终止;
jvm关闭工具经过监控控制台的相关事件而知足以上的功能;更确切的说,通知在关闭工具执行以前,先注册控制台的控制handler,而后对CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT这几类事件直接返回true。
但若是jvm以服务的形式在后台运行(好比servlet引擎),他能接收CTRL_LOGOFF_EVENT事件,但此时并不须要初始化关闭程序;为了不相似冲突的再次出现,从jdk1.3.1开始提供-Xrs参数;当此参数被设置以后,jvm将不接收控制台的控制handler,也就是说他不监控和处理CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, or CTRL_SHUTDOWN_EVENT事件。
-XX:CICompilerCount
设置用于编译的编译器线程数
默认情况下, server JVM的线程数设置为2, clientJVM的线程数设置为1, 如果使用分层编译, 则线程数将缩放为内核数
下面的示例显示如何将线程数设置为2
-XX:CICompilerCount=2
-Dsun.rmi.dgc.client.gcInterva
自动调用system.gc() 的时间间隔 毫秒单位如果设置-XX:+DisableExplicitGC 则是空调用
-Dsun.rmi.dgc.client.gcInterval=3600000
-Dsun.rmi.dgc.server.gcInterval=3600000
-Dfile.encoding
避免中文乱码
-Dfile.encoding=utf-8
从Java 源代码到得到正确的结果,要经过 “Java 源代码-> Java 字节码-> 虚拟机->操作系统->显示设备”的过程。在上述过程中的每一步骤,我们都必须正确地处理中文的编码,才能够使最终显示正确的结果。 "Java 源代码-> Java 字节码":该阶段就是调用javac 进行编译的阶段,javac默认采用系统字符集,比如我们本地机器急就是GBK,如果想用其他的编码,比如UTF-8,可以加上 -encoding UTF-8 "Java 字节码-> 虚拟机->操作系统" :该阶段首先需要JRE或者JDK支持多语言(下载JRE的时候会让你选择英文版还是多语言版),然后就是虚拟机启动的时候使用什么字符集,默认也是采用 当前系统的字符集,如需要修改字符集,加上JAVA的启动参数,-Dfile.encoding=GBK "操作系统->显示设备":该阶段主要就是需要操作系统支持显示中文就可以,就是安装了中文字体。 如果我们在本地编译(Java 源代码-> Java 字节码 阶段)默认采用了GBK字符集,而"Java 字节码-> 虚拟机->操作系统" 阶段采用Cp1252,因字符集不一致,所以就会乱码。
Minor GC
新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具
备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。一般感知不到
Full GC
又称为老年代GC或者Major GC 整堆回收 一般会至少伴随一次Minor GC 的速度一般会比 Minor GC 慢 10
倍以上。
其他
即时编译器类型
对于启动性能有要求的短时运行程序,我们会选择C1编译器,对应参数-client,对于长时间运行的对峰值性能有要求的程序,我们会选择C2编译器,对应参数-server。
Java7引入了分层编译,使用-XX:+TieredCompilation参数开启,它综合了C1的启动性能优势和C2的峰值性能优势。
在Java8中默认开启了分层编译,在Java8中,无论是开启还是关闭了分层编译,-cilent和-server参数都是无效的了。当关闭分层编译的情况下,JVM会直接使用C2。