Java虚拟机专题之内存分配(读书笔记)

一 虚拟机内存分配策略

1.1 对象优先在Eden区域分配

1.2 大对象直接进入老年代

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

1.4 空间分配担保

1.5 动态对象年龄判定

 

二 对象优先在Eden区域分配

假设构建4M大小的数组,通过打印GC详细日志,也可以看到是优先在Eden上分配的。

public class EdenMemoryAllocation {

      public static void main(String[] args) {

           byte[] arr  = new byte[4 * 1024 * 1024];

      }

}

public class EdenMemoryAllocation {

      public static void main(String[] args) {

           byte[] arr1  = new byte[2 * 1024 * 1024];

           byte[] arr2  = new byte[2 * 1024 * 1024];

           byte[] arr3  = new byte[2 * 1024 * 1024];

           byte[] arr4  = new byte[4 * 1024 * 1024];

      }

}

 

三 大对象直接进入老年代

3.1 大对象为什么会放入老年代

大对象一般都需要连续的内存空间,典型的就是很长的字符串或者数组。我们知道在垃圾回收在新生代采用复制算法,如果大的对象继续放在Eden区域,那么Eden区域在执行复制的时候,都需要移动,影响性能,所以放在老年代,很少进行GC。

 

3.2 怎么对大对象定义呢

多大的对象才算大对象呢,虚拟机提供了一个参数可以设置一个阀值,如果超过这个阀值就会分配到老年代。

-XX:PretenureSizeThreshold

情况一:不使用-XX:PretenureSizeThreshold参数

public class EdenMemoryAllocation {

      public static void main(String[] args) {

           byte[] arr1  = new byte[8 * 1024 * 1024];

      }

}

-verbose:gc -XX:+PrintGCDetails -Xmx20m -Xms20m-Xmn10m -XX:SurvivorRatio=8

-Xmn: 指定新生代内存大小

XX:SurvivorRatio:指定Eden和Survivor的比例,这也是默认比例。

上面的参数其实就是指定了虚拟机堆大小为20M,但是新生代只有10M, 那么老年代分配10M.

这样方便我们测试:


老年代10M,使用8M,使用了80%的老年代的内存量。

 

情况二: 使用-XX:PretenureSizeThreshold

-verbose:gc -XX:+PrintGCDetails -Xmx20m -Xms20m-Xmn10m -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=4194304

public class EdenMemoryAllocation {

      public static void main(String[] args) {

           byte[] arr  = new byte[3 * 1024 * 1024];

      }

}

由于没有超过4M,并不算大对象,所以还是在Eden区分配的。


public class EdenMemoryAllocation {

      public static void main(String[] args) {

           byte[] arr1  = new byte[8 * 1024 * 1024];

      }

}


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

我们知道,在新生代中默认情况下,Survivor区中对象的年龄值大于15的就会被转移到老年代中。

 

这个年龄阀值通过参数-XX:MaxTenuringThreshold控制

比如:-verbose:gc-XX:+PrintGCDetails -Xmx20m -Xms20m -Xmn10m -XX:SurvivorRatio=8-XX:MaxTenuringThreshold=12

 

五 空间分配担保

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

设置是否允许担保失败的参数:-XX:+HandlePromotionFailure

 

六 逃逸分析与栈上分配

6.1 什么是逃逸分析

简单的说,逃逸分析就是进行分析对象的作用域,判断是否有可能逃出当前的方法体。举个逃逸例子:

public class EscapeAnalysis {

      private EscapeModel model;

     

      /**

       * 发生逃逸,因为返回EscapeModel对象,可能会被外部对象引用或者访问

       * @return

       */

      public EscapeModel getEscapeModel(){

           return model != null ? model : new EscapeModel();

      }

     

      /**

       * 为全局变量赋值,发生逃逸

       */

      public void setEscapeModel() {

           this.model = new EscapeModel();

      }

     

      /**

       * 没有发生逃逸:作用域只在方法内部,也不会被其他外部所引用或者访问

       */

      public void analysis() {

           EscapeModel m = new EscapeModel();

      }

      class EscapeModel{

 

      }

}

6.2 什么是栈上分配

栈上分配:就是在栈上分配对象,而不是局限于堆上。它是Java虚拟机提供一种优化技术。

我们知道,在执行方法的时候,会创建栈帧,保存局部变量表,操作数栈等信息,然后入栈,到方法结束时,栈帧出栈,自动销毁,而不需要垃圾回收器的介入。

所以栈上分配基本思想就是对于那些线程私有的对象直接在分配在栈里面,而不是堆中。

 

对于小对象,栈上分配是一种很好分配策略,栈上分配的速度快,并且可以有效地避免垃圾回收带来的负面的影响,但由于和堆空间相比,栈空间比较小,因此对于大对象无法也不适合在栈上进行分配。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值