java虚拟机内存模型
java虚拟机内存模型堆 heap
- 堆是存放new Class()的对象,是gc的主要区域
- 新生代划分为新生区和两个幸存区。
- -xms是堆的初始容量,-xmx是堆的最大可拓展容量,建议初始值设置为最大值,避免反复拓展或者缩减的开销
- -XX:NewRatio 是老年代 / 新生代的比例,默认值为2
- -XX:SurvivorRatio 是新生区 / survivor#0的比例,survivor#0和survivor#1大小一致
永久代 Permanent Generation
- 存放类信息,常量,大对象,(比如 new byte[n] 对象)
- -XX:PermSize 是永久代的初始容量,-XX:MaxPermSize是永久代的最大容量,建议初始容量为最大容量
类元数据 metaspace
- jdk8中,永久代被完全移除了,相关参数 -XX:PermSize和-XX:MaxPermSize被忽略并改用metaspace
- -XX:MetaspaceSize是类元数据的初始容量(
默认20.8M左右(x86下开启c2模式),主要是控制metaspaceGC发生的初始阈值,也是最小阈值,但是触发metaspaceGC的阈值是不断变化的,与之对比的主要是指Klass Metaspace与NoKlass Metaspace两块committed的内存和
) - -XX:MaxMetaspaceSize是类元数据的最大容量(
默认基本是无穷大,但是我还是建议大家设置这个参数,因为很可能会因为没有限制而导致metaspace被无止境使用(一般是内存泄漏)而被OS Kill。这个参数会限制metaspace(包括了Klass Metaspace以及NoKlass Metaspace)被committed的内存大小,会保证committed的内存不会超过这个值,一旦超过就会触发GC,这里要注意和MaxPermSize的区别,MaxMetaspaceSize并不会在jvm启动的时候分配一块这么大的内存出来,而MaxPermSize是会分配一块这么大的内存的。
) - -XX:CompressedClassSpaceSize是设置Class Metaspace的大小(
默认1G,这个参数主要是设置Klass Metaspace的大小,不过这个参数设置了也不一定起作用,前提是能开启压缩指针,假如-Xmx超过了32G,压缩指针是开启不来的。如果有Klass Metaspace,那这块内存是和Heap连着的
)
实例解析
示例: -Xms300M -Xmx300M -XX:NewRatio=2 -XX:SurvivorRatio:8 -XX:PermSize=100M -XX:MaxPermSize=100M
解析:
永久代固定尺寸为 100M;
整个堆固定尺寸为 300M,其中“老年代 / 新生代”为-XX:NewRatio=2,所以老年代为 200M,新生代为 100M;
新生代总共 100M,其中“Eden / Survivor0”为-XX:SurvivorRatio=8,所以 Eden 为 80M,Survivor0=Survivor1=10M。
Jdk8之后应该是 -Xms300M -Xmx300M -XX:NewRatio=2 -XX:SurvivorRatio:8 -XX:MetaspaceSize=100M -XX:MaxMetaspaceSize=100M
参数 | 含义 | 值示例 |
---|---|---|
Xms | jvm启动时分配的内存 | -Xms300m,表示分配300M |
Xmx | jvm运行过程中分配的最大内存 | -Xms300m,表示jvm进程最多只能够占用300M内存 |
Xss | jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M | -Xss1M |
XX:NewRatio | 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) | -XX:NewRatio=2 |
XX:SurvivorRatio | 设置年轻代中Eden区与Survivor区的大小比值 | -XX:SurvivorRatio:8 |
XX:PermSize | 非堆内存初始值,默认是物理内存的1/64 | -XX:PermSize=100M |
XX:MaxPermSize | 设置最大非堆内存的大小,默认是物理内存的1/4 | -XX:MaxPermSize=100M |
XX:MetaspaceSize | 指定元空间的初始大小 | -XX:MetaspaceSize=100M |
XX:MaxMetaspaceSize | 指定元空间的最大值大小 | -XX:MaxMetaspaceSize=100M |
jvm的内存分配策略
- 对象优先分配在eden,若eden空间不足,则引发YoungGC(YGC),
- 如果幸存对象(被根对象直接或者间接引用),能存放在幸存者区,则存活的对象全部存放在幸存者区
- 如果幸存对象在幸存者区放不下,则存活的对象全部移入老年代
- 如果老年代空间不足,则发起一次FullGC(FGC),整个系统停顿,老年代也将参与回收
- 如果FullGC空间仍然周转不过来,则报 OutOfMemoryError并导致进程结束
- 如果FGC和YGC能周转过来,则新对象分配在eden
- 大对象直接分配在老年代(
所谓的大对象是指,需要大量连续内存的java对象,尤其是长字符串或数组,比如 byte[n] 对象; 可以指定多大才算大对象(只对 Serial/ParNew 有效)
)
-XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1024000
设置对象大小为,1024000 即 1M
不同的gc方式
-XX:+UseParallelGC:代表新生代使用Parallel收集器,老年代使用串行收集器。Parallel Scavenge收集器在各个方面都很类似ParNew收集器,它的目的是达到一个可以控制的吞吐量。吞吐量为CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机运行100分钟,垃圾收集花费1分钟,那吞吐量就99%。Server模式默认开启,其他模式默认关闭
-XX:+UseSerialGC:使用串行回收器进行回收,这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法。Serial收集器是最基本、历史最悠久的收集器,它是一个单线程收集器。一旦回收器开始运行时,整个系统都要停止。Client模式下默认开启,其他模式默认关闭
-XX:+UseParNewGC:Parallel是并行的意思,ParNew收集器是Serial收集器的多线程版本,使用这个参数后会在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法。操作系统是多核CPU上效果明显,单核CPU建议使用串行回收器。打印GC详情时ParNew标识着使用了ParNewGC回收器。默认关闭
其他类型的垃圾收集
- 长期存活对象移入老年代,经历n次YGC仍然存活的对象,下次YGC时将被移入老年代;可以设置该数值:-XX:MaxTenuringThreshold=15,默认15次
- 永久代满了也会导致 FullGC,老年代、永久代的垃圾收集是捆绑在一起的,因此无论两者谁满了,都会触发两者的FullGC
- 动态对象年龄判定:如果 Survivor 相同年龄对象占用空间达到一半,则大于等于该年龄的对象都移入老年代
- 冒险模式:JDK 6u24 之后,总是开启冒险模式(先尝试 YGC,以免 FGC 频繁);每次 YGC 之前,如果老年代最大连续空间,大于新生代所有对象空间之和,则 YGC 肯定成功。否则:如果老年代最大连续空间,大于历次晋升老年代的平均值,则先冒险尝试 YGC;如果老年代最大连续空间,小于历次晋升老年代的平均值,则直接 FullGC;
JVM优化原则
- 尽量减少 YoungGC
- 尽量减少 FullGC:一天最多 FullGC 一次,最好在系统空闲期(如深夜);
优化方法
- 代码角度:缩短对象生命期,尤其是大对象
- JVM参数角度:优化JVM参数以减少YGC/FGC次数,可替换收集器
服务端开启 JMX/jstatd
这两项功能必须开启,可视化工具 VisualVM 会用到
visualvm安装连接jmx和jstatd
开启后,发现其中一台服务器gc异常
gc异常可以看见 非常频繁,应该是由于长连接都连在了同一台服务器上导致的,eden完全不够用
- 机器内存为8g,非本机唯一应用,但是是本机需要消耗最多的应用,故先分配3g(3072M)看下使用效果
- 永久代只需 130M,可指定初始和最大值为256M,避免反复伸缩的开销
- 整个堆(新生代+老年代)目前内存为 2400M,可增加至 384M,指定初始和最大值都为 384M,避免反复伸缩的开销
- YoungGC 过于频繁,原因是 Eden 区过小。老年代/新生代 目前比例为 -XX:NewRatio=2,看图形可知,老年代中长寿对象并不多,可缩减老年代让给新生代,所以调整 -XX:NewRatio=1
- Eden / Survivor0比值目前为 -XX:SurvivorRatio=8,暂不调整
- 优化后的jvm参数 -Xms3072M -Xmx3072M -XX:NewRatio=1 -XX:SurvivorRatio=8 -XX:PermSize=256M -XX:MaxPermSize=256M
优化后的效果
jvm优化后
图像是好看了一点,但是发现YGC还是很频繁,需要寻找下一步的解决办法
jdk8不支持下列参数
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=256M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0
白天的时候,愈加明显,因为分配给api-server1的Eden空间比较大,所以他的ygc还是挺正常的
admin-server1api-server1
admin-server2
api-server2
因为admin-server1和api-server1是在同一台服务器,准备新增服务器进行服务拆分处理
拆分后默认配置下的
image.png
image.png
修改配置后的(扩容)
-Xms6144M -Xmx6144M -XX:NewRatio=1 -XX:SurvivorRatio=8
image.png
image.png
需要找到原因,为什么2台服务器的资源分配不均匀,并且现在使用的资源那么多(这个需要之后考虑下)
运行一段时间后,因为负载均衡把连接分发均匀了
image.png
image.png
参考:
JVM 优化实战
网友评论