深入理解JVM内存分配策略

点击上方 "程序员小乐"关注公众号, 星标或置顶一起成长

每天早上8点20分, 第一时间与你相约

每日英文

Don’t blame people for disappointing you. Blame yourself for expecting too much.

不要埋怨别人让你失望了。怪你自己期望太多。

每日掏心话

人生之所以精彩,是他愿意全然的接受一切。生命之所以可贵,是他愿意尊重一切的生命。


来自:秃桔子 | 责编:乐乐

链接:cnblogs.com/godoforange/p/11565505.html

640?wx_fmt=jpeg

程序员小乐(ID:study_tech)第 640 次推文   图片来自网络

   01 三大原则+担保机制   


JVM分配内存机制有三大原则和担保机制

  • 优先分配到eden区

  • 大对象,直接进入到老年代

  • 长期存活的对象分配到老年代

  • 空间分配担保

   02 对象优先在Eden上分配   


如何验证对象优先在Eden上分配呢,我们进行如下实验。

打印内存分配信息

首先代码如下所示:

public class A {    	
    public static void main(String[] args) {	
        byte[] b1 = new byte[4*1024*1024];	
    }	
}

代码很简单,就是创建一个Byte数组,大小为4mb。

-verbose:gc -XX:+PrintGCDetails

在我们运行后,结果如下所示。

Heap

手动指定收集器

我们可以看在新生代采用的是Parallel Scavenge收集器

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC

运行结果如下:

Heap

其实JDK默认的不是Parallel收集器,但是JDK会依照各种环境来调整采用的垃圾收集器。

查看环境的代码如下:

java -version

640?wx_fmt=png

而Serial收集器主要用在客户端的。

eden分配的验证

我们看到现在eden区域为34432K,使用了19%,那我们来扩大10倍是否eden就放不下了呢?

public class A {	
    public static void main(String[] args) {	
        byte[] b1 = new byte[40*1024*1024];	
    }	
}

运行结果如下:

Heap

显然,我们还是正常运行了,但是eden区域没有增加,老年代区域却增加了,符合大对象直接分配到老年代的特征。。

所以我们适当的缩小每次分配的大小。

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8

这里我们限制内存大小为20M

然后我们运行我们的代码:

代码如下所示:

public class A {	
    public static void main(String[] args) {	
        byte[] b1 = new byte[2*1024*1024];	
        byte[] b2 = new byte[2*1024*1024];	
        byte[] b3 = new byte[2*1024*1024];	
        byte[] b4 = new byte[4*1024*1024];	
        System.gc();	
    }	
}

运行结果如下:

[GC (Allocation Failure) [DefNew: 7129K->520K(9216K), 0.0053010 secs] 7129K->6664K(19456K), 0.0053739 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

我们可以发现在eden区域为8192K 约为8M

而原先的b1,b2,b3为6M,被分配到了tenured generation。

原先的Eden区域如下所示,在分配完,b1,b2,b3后如下所示。640?wx_fmt=png

而查看日志的时候,我们发生了俩次GC。

[GC (Allocation Failure) [DefNew: 7129K->520K(9216K), 0.0053010 secs] 7129K->6664K(19456K), 0.0053739 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

而在

[DefNew: 7129K->520K(9216K), 0.0053010 secs] 7129K->6664K(19456K), 0.0053739 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

中我们会看到,刚分配的对象并没有被回收。

上面的GC是针对新生代的。

而下面的FullGC是针对老年代的。

如果我们这时候要再分配4m的内存,虚拟机默认将原先的eden区域放到可放的地方,也就是在老年代这里

因此会发生我们这种情况。

640?wx_fmt=png

这就是整个过程。验证了对象有现在Eden区域回收


   03 大对象直接进入到老年代   


指定大对象的参数。

-XX:PretenureSizeThreshold
public class A {	
    private static int M = 1024*1024;	
    public static void main(String[] args) {	
        byte[] b1 = new byte[8*M];	
    }	
}

测试代码:如下

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8

运行结果如下:

Heap

我们可以看到,结果数直接把8M扔到了老年代里面了。

640?wx_fmt=png

被发现7M全部扔到了eden里面。

如果我们制定了参数后,会发现结果变了。

参数如下所示:

-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=6M

运行结果如下:

640?wx_fmt=png

我们会发现7M进到了老年代。


   04 长期存活对象进入老年代   


参数如下:

-XX:MaxTenuringThreshold

每次进行回收的时候,如果没被回收,那对象的年龄+1

如果对象年龄到达阈值,就会进入老年代。

具体测试和上面的Max一样。就不占篇幅了。

   05 空间分配担保   


参数如下:

-XX:+HandlePromotionFailure

步骤如下:

  • 首先衡量有没有这个能力,然后才能进行分配。

  • 如果有这个能力放入,那么这个参数是‘+’号证明开启了内存担保,否则是‘-’号就是没开启。


逃逸分析与栈上分配

如何将内存分配到栈上呢?

逃逸分析的主要目标就是分析出对象的作用域。

 
                                                  

总结,只要定义在方体中,对象的作用域不发生逃逸,否则发生逃逸。

   06 总结   


JVM内存分配策略不是特别复杂,只要一步一步跟着虚拟机走,那么就可以去理解JVM内存分配的机制。

640?wx_fmt=png

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

猜你还想看

GitHub 标星3.5W+,超实用技术面试手册,从工作申请、面试考题再到优势谈判

漫画:一位“坑人”的编程大师

1 行Python代码能干哪些事,这 13个你知道吗?

Java提供的几种线程池

掌握 SpringMVC 运行原理,看这篇就对了!

640?wx_fmt=png

嘿,你在看吗640?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值