JVM系列之内存分配和回收策略

堆内存

JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指 定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

堆中内存区域按分代收集算法分为老年代和年轻代,老年代和年轻代的比值默认为2,即老年代Old区占堆内存的2/3,年轻代占1/3。年轻代中又分为Eden区和两个Survivor区,分别占内存比为8:1:1。 -Xmn可以设置年轻代内存大小。

 

非堆内存

JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。这两参数在java8中被移除了。

JVM内存限制(最大值)

JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然 可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统 下为2G-3G),而64bit以上的处理器就不会有限制了。

内存申请过程

  1. JVM会试图为相关Java对象在Eden中初始化一块内存区域;
  2. 当Eden空间足够时,内存申请结束。否则到下一步;
  3. 当Eden空间不足时,则进行Minor GC,JVM采用复制算法将Eden和在用的Survivor区中的存活对象复制到另一个空闲Survivor区中(若Survivor区无法存放则直接移入老年代),同时清空Eden和在用的Survivor区;
  4. 当Old区空间不够时,JVM会在Old区进行Full GC,采用标记-清理或者标记-整理算法释放已死的对象;
  5. 当Full GC后,若Old区仍然无法存放从Eden和Survivor复制过来的对象,导致JVM无法在Eden区为新对象创建内存区域,则出现OOM错误。

对象衰老过程

JVM采用分代收集的思想来管理内存,那么在内存回收时就必须能识别哪些对象放在新生代,哪些放在老年代,为此JVM给每个对象定义了对象年龄计数器(该计数器在对象头的Mark Word中---具体参看JVM系列之对象的内存布局)。

首先新创建的对象的内存一般都分配自Eden区。因为Java对象大多数都具有朝生夕灭的特性,也就意味着Minor GC会回收绝大部分对象的内存,所以非常频繁,回收速度也比较快。

Minor GC的过程就是将Eden区和在用的Survivor区中的活对象复制到另一个空闲Survivor区中(前提是空闲Survivor区可以容纳),此时该存活对象的分代年龄加1。每进行一次Minor GC年龄就增加1。

当存活的对象在年轻代中经历了一定次数(可以通过-XX:MaxTenuringThreshold参数配置,默认为15,设置值在0到15之间)的Minor GC后,就会被移到Old区中,称为tenuring。

内存回收策略 

1、对象优先在Eden区分配

2、大对象直接进老年代

大对象需要大量连续的内存空间进行存储,最典型的大对象就是很长的字符串以及数组。

JVM提供了-XX:PretenurSizeThreshold参数,大于这个参数值的对象直接在老年代中分配,这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存复制。

-XX:PretenurSizeThreshold默认值是0,意味着任何对象都会现在新生代分配内存。

注意:设置的PretenurSizeThreshold参数可能会影响JVM性能,如果该参数过小则意味着会有更多的对象直接进入老年代,也就意味着Full GC的频率会变高,性能更差。

3、长期存活的对象进老年代

这一条策略参看对象的衰老过程

4、动态对象年龄判断

为了更好地适应不同程序的内存状况,虚拟机并不是永远要求对象的年龄必须达到了MaxTenuringThreshold才晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

5、空间分配担保

JDK 6 Update 24之前

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果是true允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置为false不允许冒险,那这时也要改为进行一次Full GC。

为什么需要分配担保?新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。与生活中的贷款担保类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

取平均值进行比较其实仍然是一种动态概率的手段,也就是说,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败(Handle Promotion Failure)。如果出现了HandlePromotionFailure失败,那就只好在失败后重新发起一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开设置为true,避免Full GC过于频繁。

JDK 6 Update 24之后

-XX:HandlePromotionFailure参数无效,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC,否则将进行Full GC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值