上一篇博客我简单介绍了下如何手动计算一个Java对象到底占用多少内存?今天就想聊下这个内存JVM到底是是如何分配和回收的。

Java整体来说还是一个GC比较友好的语言,无论是分代的垃圾收集,还是基于GC Roots的可达性算法都是业界普遍的经典做法,关于Java的内存区域划分以及GC的一些基本知识,我这里就不赘述了,可以看我之前的博客:http://zhanjindong.info/category/note/dsbj/

《深入理解Java虚拟机第2版》这本书非常值得一看,最近几篇读博客都算这本书的读书笔记吧。本人文笔很烂,所以都是记流水账枯燥乏味的文章。进入正文之前还是要交代下环境:以下内容都是基于HotSpot虚拟机Server模式,垃圾收集器用的是默认的Serial和Serial Old。

JVM内存分配和回收策略

话说一图胜千言,本也打算画张活动图就了事了:

但是画完发现:一画图更麻烦,太大了看的累(想看的可以在新建窗口放大了看),其次感觉还是说不清楚(画的不对的地方欢迎批评),最后觉得还是文字描述一下整个流程:

1、当JVM给一个对象分配内存的时候,如果启动了本地线程分配缓存,将按线程优先在TLAB上分片,TLAB只是起缓存作用减少高并发下CAS带来的性能损失,跟GC的分代没有冲突。

2、当分配一个对象的时候会优先在Eden区域分配,如果Eden有足够的空间,那么内存分配很顺利的结束,不会触发任何GC操作;

3、当Eden区域空间不足的时候,会尝试着进行一次Minor GC,之所以说尝试是因为在进行Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象空间总和,如果是的那么可以保证这次Minor GC顺利进行;否则,虚拟机会检查HandlePromotionFailure这个参数是否设置为允许担保失败,如果允许那么虚拟机会根据经验值(这个经验值是历次晋升到老年代对象的平均大小)来决定是否尝试这次GC,如果小于或者JVM觉得不能冒险,那么会进行一次Full GC;

4、Minor GC时会采用复制算法将所有存活的对象复制到Survivor空间中(既包括Eden区域存活的对象,也包括另外一个Survivor存活下来的对象),如果这时发现Survivor空间不足,那么这些存活对象会直接进入老年代,这就是“空间分配”担保,前面说到冒险,是因为老年代的空间仍有可能不够,这时还是要进行一次Full GC,但是除了极端情况,大部分时候通过担保还是能有效避免频繁的Full GC的,如果Full GC后仍然没有足够空间,那只能抛出OutOfMemoryError;

5、对象在Eden空间出生,经过第一次Minor GC后能够顺利的被转移到Survivor的话,那么它的GC年龄就变成1,以后每在Survivor中熬过一次Minor GC,年龄就增加1,直到超过一定程度(-XX:MaxTenuringThreshold,默认15岁)则晋升到老年代;

6、规则是死的,人是活的,虚拟机开发人员还想到了一个“动态对象年龄判定”算法:如果Survivor区域中相同年龄所有对象大小总和超过Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进去老年代;

7、对象也可以直接分配在老年代,这主要是针对那些大对象,因为大对象的内存分配代价比较大(需要连续的内存空间),所以JVM提供了-XX:PretenureSizeThreshold这个参数。

为什么有两块survivor区域

我一开始很纳闷HotSpot虚拟机为什么要搞出两个Survivor区域内,只用一块有何不妥吗?最后在Stack Overflow找到一个答案:

http://stackoverflow.com/questions/10695298/java-gc-why-two-survivor-regions 按照里面的说法是为了减少虚拟机对内存碎片的处理,我想了半天我的理解是:

因为survivor中的对象在达到“老年”(-XX:MaxTenuringThreshold)之前肯定有对象已经变成“垃圾”了,这时候必须要对其进行回收,如果只使用一个survivor的话,那么要不容忍survivor存在内存碎片,要么要对其进行内存整理,出于和对Eden区域同样的考虑,所以实际上对Survivor的GC也是基于复制算法的,不过是从一个Survivor到另外一个Survivor(这也是GC日志中为什么叫from space和to space),所以Survivor的两个区是对称的,没有先后关系,所以Survivor区中可能同时存在从Eden复制过来对象,以及从前一个Survivor复制过来的对象,某一次GC结束时肯定会有一个Survivor是空的。

实例说明

以上都是理论,下面结合一小段代码简单演示下上面的内容,这段代码引自《深入理解Java虚拟机第2版》3.6.3节,我简单展开说明下。为了方便解释,我先把设置的虚拟机参数贴出来:

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=5242880
  • -XX:+UseSerialGC这里使用默认的Serial GC进行说明;
  • -verbose:gc是为了打印出GC日志;
  • -Xms初始Java堆为20M;
  • -Xmx20M JVM最大使用的堆为20M不可扩展;
  • -Xmn10M,新生代的内存空间为10M;
  • -XX:SurvivorRatio=8 Eden与两个Survivor的比例是8:1:1;
  • XX:PretenureSizeThreshold直接在老年代区域分配对象的阈值为5M,其实可以不设置,这里为了明确变量。

测试代码如下:

我们一步步来看,首先只执行48,49两行:

allocation1 = new byte[_1MB / 2];
allocation2 = new byte[6 * _1MB];

通过GC日志发现没有发生任何GC,Eden区域够用(注意:关于一个Java对象导致占用多大内存,参看我前一篇博客。),接着执行51,52两行:

allocation2 = null;
allocation3 = new byte[2 * _1MB];

可以看到了引发了一次GC,这是一次Minor GC,同时可以看到使用的垃圾收集器是默认的(Def,关于GC日志的理解,可以参看《深入理解Java虚拟机第2版》一书)。引发GC的原因是Eden已经没有足够的内存容纳allocation3对象,发生GC之后allocation2对象占用的内存空间被回收了,而allocation1“幸存”下来被转移到了from survivor区域。

接下来我们再执行57,58,59三行代码:

allocation4 = new byte[4 * _1MB];
allocation3 = allocation4 = null;
allocation5 = new byte[2 * _1MB];

第59行代码引发了第二次GC,仍然是一次Minor GC,可以看到allocation3和allocation4都被回收了,allocation5被顺利的分配到了Eden空间,但是为什么from space变成了0%而老年代区域却变成了6%,这6%应该是allocation1占用的,但是为什么跑到老年代了呢?显然它的“年龄”还没有到15岁啊。

啊哈!还记得吗JVM很聪明,它会“动态对象年龄判定”,从上一张图可以看到,Survivor区域已经使用超过了50%(67%),而且显然是同一年龄的对象(就一个对象嘛),所以在第二次GC的时候它晋升到了老年代,大家可以把allocation1对象分配为256kb再试试。

Ok,到目前为止都是Minor GC,想Stop-The-World很简单,我们直接分配一个很大的对象试试:

allocation6 = new byte[20*_1MB];

不仅Full GC了,而且内存溢出了,因为我们设置了-Xmx20M。同时可以看到JVM被逼急了在不同区域进行了GC,首先在新生代(Eden+Survivor0)将内存全部回收,导致对象晋升到老年代,其次在老年代和“永久代(HotSpot)”也都进行了GC,但是一点收获都没有,最后只能OOM。关于JVM的一些其他规则,比如大对象的分配,以及其他虚拟机的分配策略,就留给有兴趣的同学自己试试了。

今天就写到这,最后祝大家“六一快乐”……靠,看了下时间已经不是6.1,童年已经过去了!

PS:文章同步发布在我的个人博客http://zhanjindong.info/2014/06/02/jvm-memory-and-gc/

JVM是如何分配和回收内存?有实例!的更多相关文章

  1. 重读《深入理解Java虚拟机》二、Java如何分配和回收内存?Java垃圾收集器如何工作?

    线程私有的内存区域随用户线程的结束而回收,内存分配编译期已确定,内存分配和回收具有确定性.共享线程随虚拟机的启动.结束而建立和销毁,在运行期进行动态分配.垃圾收集器主要对共享内存区域(堆和方法区)进行 ...

  2. JVM存储位置分配——java中局部变量、实例变量和静态变量在方法区、栈内存、堆内存中的分配

    Java中的变量根据不同的标准可以分为两类,以其引用的数据类型的不同来划分可分为“原始数据类型变量和引用数据类型变量”,以其作用范围的不同来区分可分为“局部变量,实例变量和静态变量”. 根据“Java ...

  3. JVM中内存的设置和分配(最大内存,总内存,剩余内存的区别)

    1.设置分配的内存大小 -vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M -vmargs 说明后面是VM的参数,所以后面的 ...

  4. JVM学习笔记(一):Java内存区域

    由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分.在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程: 首先Java源代码文件(. ...

  5. 最简单例子图解JVM内存分配和回收

    一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分 ...

  6. JVM内存分配和回收

    本文内容来自<Java编程思想(第四版)>第二章<一切都是对象>和第五章<初始化与清理>.作为一个使用了好几年的Javaer,再次看编程思想的前面章节(不要问我为什 ...

  7. jvm内存分配和回收策略

    在上一篇中,已经介绍了内存结构是什么样的. 这篇来介绍一下 内存是怎么分配的,和怎么回收的.(基本取自<深入理解Java虚拟机>一书) java技术体系中所提倡的自动内存管理最终可以归结为 ...

  8. Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法

    在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...

  9. 最简单例子图解JVM内存分配和回收(转)

    本文转自http://ifeve.com/a-simple-example-demo-jvm-allocation-and-gc/ http://www.idouba.net/a-simple-exa ...

随机推荐

  1. 启动页面设置,icon图标设置

    更多尺寸像素如何放置请看:http://chicun.jammy.cc/ 如何设置App的启动图,也就是Launch Image? 新建一个iosLaunchImage文件夹

  2. IOS开发之——友盟社会化分享UMSocial_SDK的使用

    友盟第三方官方网址:http://dev.umeng.com/social/ios/quick-integration

  3. css兼容性的问题

    https://www.douban.com/note/314793848/ 随意的一个博客ie6的兼容 这个博客比较好 http://blog.csdn.net/chuyuqing/article/ ...

  4. 第四十二课:基于CSS的动画引擎

    由于低版本浏览器不支持css3 animation,因此我们需要根据浏览器来选择不同的动画引擎.如果浏览器支持css3 animation,那么就使用此动画引擎,如果不支持,就使用javascript ...

  5. Redis——分布式简单使用

    Redis简介:Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. Redis安装:参考博客http://www ...

  6. Java Web整合开发实战:基于Struts 2+Hibernate+Spring 目录

    第1篇 Java Web开发基础第1章 Web的工作机制( 教学视频:31分钟) 1.1 理解Web的概念 1.1.1 Web的定义 1.1.2 Web的三个核心标准 1.2 C/S与B/S两种软件体 ...

  7. ajax中的application/x-www-form-urlencoded中的使用

    ajax中的application/x-www-form-urlencoded中的使用一,HTTP上传的基本知识 在Form元素的语法中,EncType表明提交数据的格式 用 Enctype 属性指定 ...

  8. ansible 使用方法

    免密钥方式登陆: [root@yizhen ~]# ssh-keygen -t rsa[root@yizhen ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168 ...

  9. .net架构设计读书笔记--第三章 第8节 域模型简介(Introducing Domain Model)

    一.数据--行为转变     很长的时间,典型的分析方法或多或少是以下两种,第一,收集需求并做一些分析,找出有关实体 (例如,客户. 订单. 产品) 和进程来实现. 第二,手持这种理解你尝试推断一个物 ...

  10. hdu1890 伸展树(区间反转)

    对于大神来说这题是水题.我搞这题花了快2天. 伸展树的优点有什么,就是树不管你怎么旋转序列是不会改变得,并且你要使区间反转,只要把第k大的点转到根结点,那么它的左子树就是要交换的区间[l,r),然后交 ...