作为运维,你不一定要会写Java代码,但是一定要懂Java在生产跑起来之后的各种机制。
本文为《Hi,运维,你懂Java吗》系列文章 第六篇,敬请关注后续系列文章
欢迎关注 龙叔运维(公众号) 持续分享运维经验
前言
本篇对java的JVM线程共享内存中的堆进行一个概括介绍,因为堆的知识点较多,会进行多个篇章的拆分讲解。
堆是各个线程之间共享的一块内存区域,存放的主要是对象实例和数组,几乎所有的对象实例都在这里分配内存。
堆内存再物理上不需要连续,但是在逻辑上视为连续。
1、内存分配过程
一般来说new一个对象都是在年轻代 eden区 分配内存,但是有些情况也会在老年代分配内存,例如下面这些情况:
1、对象大小从超过了 -XX:PretenureSizeThreshold 设置的大小,就会直接在老年代中分配内存,这也是为了避免大内存对象在年轻代反复复制导致不必要的消耗。
2、如果年轻代空间无法存放,则会在老年代进行分配内存
其中-XX:PretenureSizeThreshold 参数只对Serial和ParNew两款收集器有效,对Parallel Scavenge收集器是无效的。
可以用 jinfo -flag UseParallelGC 【PID】查看收集齐类型
【新对象分配内存过程图】
GC相关的知识点我会单独一篇文章总结记录
2、内存分配担保机制
内存分配担保机制是在minor GC开始前触发的一个机制,主要是避免发生极端情况。
在进行minor GC之前,会先检查老年代的可用剩余空间是否大于年轻代所有对象总空间大小,如果大于,那minor GC就不会有任何问题。
如果小于,则如果新生代对象同时都晋升到老年代,老年代空间是不够的,这时候如果配置了HandlerPromotionFailure为true,则代表JVM允许担保失败
则检查历史平均每次minor GC后晋升老年代对象的总大小是否小于老年代身剩余空间总大小,如果小于,则进行minor GC,否则直接进行FULL GC。
这些机制主要就是为了减少FULL GC出现的次数,避免不必要的消耗。
3、年轻代
年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。
分为Eden、survivor(from和to,也叫s0和s1),他们之间的大小比例由参数–XX:SurvivorRatio 来设定
from区是不存放数据的,假如eden和s0,s1的比例是8:1:1,那么新生代可用空间是新生代总空间的10分之9
4、老年代
年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁
对象进入老年代的情况有四种:
A、担保机制触发:当年轻代(survivor区)无法存放minor GC后所有存活下来的对象的时候,就会将放不下的对象放到老年代中
B、大对象(大小大于-XX:PretenureSizeThreshold的对象)直接在老年代分配内存;(只对Serial和ParNew收集器有效,对于Parallel Scavenge收集器无效)
C、年龄达到限定值的对象(age大于-XX:MaxTenuringThreshold)晋升到老年代;(对象每在Survivor区熬过一次,其age就增加一岁)
D、动态年龄判断:当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。
5、堆内存必懂参数
A、初始内存:默认物理电脑内存大小 / 64。-Xms:表示堆区的初始内存,等价于 -XX:InitialHeapSize。
B、最大内存:默认物理电脑内存大小 / 4。-Xmx :表示堆区的最大内存,等价于 -XX:MaxHeapSize。
(将堆的最小值-Xms
参数与最大值-Xmx
参数设置为一样即可避免堆自动扩展)
C、OOM自动打DUMP:-XX:+HeapDumpOnOutOf-MemoryError 可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照以便进行事后分析。