深入理解Java虚拟机jvm-内存分配与回收

内存分配与回收

对象优先在Eden分配

新生代总可用空间=Eden区+1个Survivor区(from区)的总容量

  • 大多数情况下,对象在新生代Eden区中分配
  • 当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC
  • 存活下来的对象放入Survivor
  • 当对象无法放入Survivor空间时,通过分配担保机制提前转移到老年代去

HotSpot虚拟机提供了-XX:+PrintGCDetails这个收集器日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况。

大对象直接进入老年代

在Java虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复制对象时,大对象就意味着高额的内存复制开销。

大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组。

HotSpot虚拟机提供了-XX:PretenureSizeThreshold=3145728参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作。这个参数不能与-Xmx之类的参数一样直接写3MB,单位是字节。

注意: -XX:PretenureSizeThreshold参数只对Serial和ParNew两款新生代收集器有效,HotSpot的其他新生代收集器,如Parallel Scavenge并不支持这个参数。如果必须使用此参数进行调优,可考虑ParNew加CMS的收集器组合。

长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄(Age)计数器,存储在对象头中(详见深入理解Java虚拟机jvm-对象的内存布局)。

《深入理解Java虚拟机》原话:对象通常在Eden区里诞生,如果经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象年龄设为1岁。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

博主觉得有点不对~~~什么你敢质疑神书???big胆!

博主觉得哪里不对呢?"该对象会被移动到Survivor空间中,并且将其对象年龄设为1岁"这句话!

继续说说为啥本博主觉得有点不对,以下是《深入理解Java虚拟机》的例子:

java 复制代码
//VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
public class TestTenuringThreshold {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        demo1();
    }

    /**
     * 此方法中allocation1对象需要256KB内存,Survivor空间可以容纳。当-XX:MaxTenuringThreshold=1时,allocation1对象在第二次GC发生时进入老年代
     */
    static void demo1() {
        byte[] allocation1, allocation2, allocation3;

        allocation1 = new byte[_1MB / 4]; // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置

        allocation2 = new byte[4 * _1MB];
        allocation3 = new byte[4 * _1MB];
	    allocation3 = null;
        allocation3 = new byte[4 * _1MB];
    }
}

本例中设置了MaxTenuringThreshold=1,即最大占有阈值=1,当对象年龄=1时,将对象存入老年区。但是上述例子中allocation1 对象经过了2次gc才存入老年区,第一次gc时并没有被存入老年区。第一次gc后,allocation1 的年龄并没有+1,age=0,allocation1 被存入了Survivor from区,当第二次gc后,Survivor from存活的对象被复制交换至Survivor to区,此时年龄+1,age=1,allocation1 对象被存入老年区。

所以,让对象年龄+1的行为是发生在Survivor from区和Survivor to区复制交换后!
如果我理解错了,请一定要纠正我!!!

动态对象年龄判定

HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄。

空间分配担保

jdk6.0_24之后,-XX:-HandlePromotionFailure虚拟机中已经不会再使用它,空间分配担保规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行Full GC。

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

取历史平均值来比较其实仍然是一种赌概率的解决办法,也就是说假如某次Minor GC存活后的对象突增,远远高于历史平均值的话,依然会导致担保失败。如果出现了担保失败,那就只好老老实实地重新发起一次Full GC,这样停顿时间就很长了。

相关推荐
奋斗的小花生2 分钟前
c++ 多态性
开发语言·c++
魔道不误砍柴功4 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨7 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程35 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉