Java内存分配的理解

Java在每次新的对象生成时都会把这个对象放到一个内存区域,什么样的对象放在那一片区域就是根据Java本身的内存分配机制决定的。Java在给对象分配内存是遵循以下几点:

1.实例对象优先进入新生代Eden区

2.大对象直接进入老年代

3.长期存活对象进入老年代

4.空间分配担保

5.栈上分配

1.实例对象优先进入新生代Eden区

这一块很好理解,由于我们在程序中使用变量非常的频繁,因此需要不断有可用的区域供程序创建新对象,Eden区本身的容量是足够存放许多中小对象的,并且在Eden区中会发生许许多多的Minor GC(局部内存清理),这种GC速度快,效率高,会快速的把Eden区中的已死亡对象清理掉并把存活对象取出,这样就可以保证我们有足够的空间创建新对象,而且还能及时的把我们使用完毕的对象清理掉(毕竟大多对象的生命周期都比较短)。

2.大对象直接进入老年代

有时,我们在程序中创建了一些特别大的对象时,这些对象不会进入Eden区,而是会直接被分配到老年代(关于大对象的标准,是jvm根据各个内存块的大小计算的)。仔细想一下,这也是比较合理的,首先在Eden区中经常会进行Minor GC,并且这种GC方式使用的是stop-copy(复制清理)算法,在清理期间程序的运行是被暂停的,这时如果Eden区中有一些特别大的对象,那么在复制的过程中肯定会花费特别多的时间,如果考虑Minor GC执行的频率,那效率就更低了。而且通常我们创建一个超级大的对象应该不可能只使用一下吧,几乎都是会长时间存活的对象,所以一开始就把它放到老年代也是挺合理的。

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

这个就没什么可说的了,既然是长期存活的对象了,如果一直放在Eden区,那么在Minor GC发生时复制来复制去的,多麻烦啊,直接放到垃圾回收次数特别少的老年代就完事了。这里要说的就是jvm有它自己的年龄计数器和判断进入老年代的年龄值,这个年龄计数器是对象在两个Servivor区中复制的次数,当然,我们也可以通过手动指定jvm参数去改变它。

4.内存分配担保

关于内存分配担保,可以先想象一个场景,如果Eden区的大小为10M,已经使用了6M,这6M内存中的对象都是存活的,那么现在程序又要新创建一个5M大小的对象,这时Eden区域放不下了,它就要进行一次Minor GC,GC完了发现这6M的对象都是不可以被回收的,放到Servivor区也肯定放不下(Eden区Servivior区的比例一般是8:1:1)所以他就要请求老年代给它借点内存,让它把已经存在的6M大小的内存放到老年代中,然后再为这新的5M大的对象分配空间。既然Eden区请求了老年代给它做内存分配的担保,那老年代肯定要做出回应的啊。首先,老年代先检查当前一块最大的连续内存是否可以容得下所有的新生代对象(因为向老年代请求内存分配担保是在发生GC之前,这时jvm也不知道GC以后还会剩下多少内存,索性就全算上了),如果可以存的下,那就ok了,老年代就会告诉新生代,你GC一下吧,把剩余的对象放到这块内存中就行。如果不能存的下当前新生代的所有对象,这时就要根据一个叫做HandlePromotionFailure的jvm参数决定是否要冒险进行内存担保了,如果决定要冒险(HandlePromotionFailure参数设置为允许担保失败),那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。Full GC的代价是非常大的,所以我们尽可能的把HandlePromotionFailure设置为允许冒险。

说了一大堆,其实内存分配担保就是当新生代中有大量对象存活,无法为新创建的对象分配空间时,就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

5.栈上分配

我们都知道Java内存大体上分为堆和栈,堆的内存模型想一个大箱子,所以需要经常的去整理他。而Java栈就是一个先进后出的栈模型,内存的申请与释放都是非常有规律的。也不用像Java堆进行GC时要做一大堆工作,所以我们如果能把对象都放置到栈中那一定会提升很多效率,但是Java的栈使用来描述java方法内存模型的,也就是说栈规定其中只能放方法对象。但是在方法中也是会有一些变量的,这些变量就会跟着方法体一起被放置到栈上,在方法执行结束后随着方法一起出栈,然后销毁。在方法中什么样的变量会被分配到栈上呢,这就涉及到逃逸分析的问题,逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。只要我们在方法内部创建一个对象,这个对象没有被外部给拿到(例如通过返回值传给外部引用就不行),它就有可能随着方法被分配到栈内存中。

 

本文是笔者自己的理解,如果有错误的地方,还请大家指出。

文章参考《深入理解java虚拟机》

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值