上一篇博客我简单介绍了下如何手动计算一个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. 深入浅出jsonp

    前言 第一次听说jsonp,其实早在2年之前.当时在做一个活动页面的抽奖模块,要从服务端get一个概率,当时什么都不懂,同事说用ajax,我就用ajax,同事说dataType改成jsonp,我就改成 ...

  2. 一个基于.NET平台的自动化/压力测试系统设计简述

    AutoTest系统设计概述 AutoTest是一个基于.NET平台实现的自动化/压力测试的系统,可独立运行于windows平台下,支持分布式部署,不需要其他配置或编译器的支持.(本质是一个基于协议的 ...

  3. 傻傻分不清楚的php脚本路径

     闲话就不说了,还是直接提出今天的问题,准确的说,对多个相似的 有关当前脚本信息的全局变量常量的区分. 先贴上代码: <?php echo $_SERVER['PHP_SELF']; echo ...

  4. CodeIgniter框架中关于URL(index.php)的那些事

    最近,在做自己的个人网站时,采用了轻量级的php框架CodeIgniter.乍一看上去,代码清晰简洁,MVC模型非常容易维护.开发时我采用的工具是Netbeans IDE 8.0,当然,本文的内容和开 ...

  5. javascript继承(四)—prototype属性介绍

    js里每一个function都有一个prototype属性,而每一个实例都有constructor属性,并且每一个function的prototype都有一个constructor属性,这个属性会指向 ...

  6. AngularJS开发指南5:AngularJS表达式详解

    AngularJS表达式类似Javascript的代码片段,通常在数据绑定中用到,写在双大括号中,如:{{表达式}}.表达式是用$parse方法来处理的. 下面是一些合法的AngularJS表达式 1 ...

  7. python 读写文件和设置文件的字符编码

    一. python打开文件代码如下: f = open("d:\test.txt", "w") 说明:第一个参数是文件名称,包括路径:第二个参数是打开的模式mo ...

  8. git托管代码随笔--运用ssh传输,不用每次提交频繁输入github账号密码

    遇到问题:在使用git bash的时候 每次git push均要输入账号密码. 问题原因:使用的是http传输,需用ssh传输. 解决方法: 1.设置密钥 ssh-keygen -t rsa -C & ...

  9. BZOJ-1061 志愿者招募 线性规划转最小费用最大流+数学模型 建模

    本来一眼建模,以为傻逼题,然后发现自己傻逼...根本没想到神奇的数学模型..... 1061: [Noi2008]志愿者招募 Time Limit: 20 Sec Memory Limit: 162 ...

  10. BZOJ-2748 音量调节 DP+背包(脑残)

    水DP,一开始竟然想错了...水了半天....真可怕 2748: [HAOI2012]音量调节 Time Limit: 3 Sec Memory Limit: 128 MB Submit: 1156 ...