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

http://www.idouba.net/a-simple-example-demo-jvm-allocation-and-gc/

原文链接:最简单例子图解JVM内存分配和回收

一、简介

JVM采用分代垃圾回收。在JVM的内存空间中把堆空间分为年老代和年轻代。将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象。年轻代中又被分为Eden区(圣经中的伊甸园)、和两个Survivor区。新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到年老区。

简单讲,就是生命期短的对象放在一起,将少数生命期长的对象放在一起,分别采用不同的回收策略。生命期短的对象回收频率比较高,生命期长的对象采用比较低回收频率,生命期短的对象被尝试回收几次发现还存活,则被移到另外一个地方去存起来。就像现在夏天了,勤劳的doumadoudoudouba常穿的衣服放在顺手的地方,把冬天的衣服打包放在柜子另一个地方。虽然把doudou的小衣服类比成虚拟机里的对象有点不合适,大致意思应该就是这样。

本文中通过最简单的一个例子来demo下这个过程,代码很短,很简单,希望剖析的细一点,包括每一步操作后对象的分配和回收对内存堆产生的影响。设定上包括对堆中年轻代(年轻代中eden区和survivor区)、年老代大小的设定,以及设置阈值控制年轻代到年老代的晋升。

二、示例代码

下面是最简单的代码,通过代码的每一步的执行来剖析其中的规则。

package com.idouba.jvm.demo;

/**
* @author idouba
* Use shortest code demo jvm allocation, gc, and someting in gc.
*
* In details
* 1) sizing of young generation (eden space,survivor space),old generation.
* 2) allocation in eden space, gc in young generation,
* 3) working with survivor space and with old generation.
*
*/
public class SimpleJVMArg { /**
* @param args
*/
public static void main(String[] args)
{
demo();
} /**
* VM arg:-verbose:gc -Xms200M -Xmx200M -Xmn100M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
*
*/
@SuppressWarnings("unused")
public static void demo() { final int tenMB = 10* 1024 * 1024; byte[] alloc1, alloc2, alloc3; alloc1 = new byte[tenMB / 5];
alloc2 = new byte[5 * tenMB];
alloc3 = new byte[4 * tenMB];
alloc3 = null;
alloc3 = new byte[6 * tenMB];
}
}

三、执行输出

通过jvm 参数设定几个区域的大小,结合代码执行可以观察到对象在堆上分配和回收的过程。执行参数如下:

-verbose:gc -Xms200M -Xmx200M -Xmn100M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+PrintTenuringDistribution

通过设这-Xms200M -Xmx200M 设置Java堆大小为200M,不可扩展,-Xmn100M设置其中100M分配给新生代,则200-100=100M,即剩下的100M分配给老年代。-XX:SurvivorRatio=8设置了新生代中eden与survivor的空间比例是1:8。

执行上述代码结果如下:

[GC [DefNew
Desired survivor size 5242880 bytes, new threshold 15 (max 15)
- age 1: 2237152 bytes, 2237152 total
: 54886K->2184K(92160K), 0.0508477 secs] 54886K->53384K(194560K), 0.0508847 secs] [Times: user=0.03 sys=0.03, real=0.06 secs]
[GC [DefNew
Desired survivor size 5242880 bytes, new threshold 15 (max 15)
- age 2: 2237008 bytes, 2237008 total
: 43144K->2184K(92160K), 0.0028660 secs] 94344K->53384K(194560K), 0.0028957 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 92160K, used 65263K [0x1a1d0000, 0x205d0000, 0x205d0000)
eden space 81920K, 77% used [0x1a1d0000, 0x1df69a10, 0x1f1d0000)
from space 10240K, 21% used [0x1f1d0000, 0x1f3f2250, 0x1fbd0000)
to space 10240K, 0% used [0x1fbd0000, 0x1fbd0000, 0x205d0000)
tenured generation total 102400K, used 51200K [0x205d0000, 0x269d0000, 0x269d0000)
the space 102400K, 50% used [0x205d0000, 0x237d0010, 0x237d0200, 0x269d0000)
compacting perm gen total 12288K, used 360K [0x269d0000, 0x275d0000, 0x2a9d0000)
the space 12288K, 2% used [0x269d0000, 0x26a2a3c0, 0x26a2a400, 0x275d0000)
ro space 8192K, 66% used [0x2a9d0000, 0x2af20f10, 0x2af21000, 0x2b1d0000)
rw space 12288K, 52% used [0x2b1d0000, 0x2b8206d0, 0x2b820800, 0x2bdd0000)

从中可以看到eden 大小为81920K, Survivor中from区域和to区域大小都是10240k。新生代总的92160K指的是eden和一个Survivor区域的和。

即原始的内存如图:

为了演示年轻代对象晋级到年老代的过程。需要设置一个VM参数, 这里设置MaxTenuringThreshold=1。前面不设置的时候,默认MaxTenuringThreshold取值15。当设置不同的阈值,jvm在内存处理会有不同。我们重点观察观察alloc1 这么小块区域在不同的MaxTenuringThreshold参数设置下的遭遇。

这时候JVM的参数中加上MaxTenuringThreshold=1如下:

-verbose:gc  -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution

可以看到输出结果是:

[GC [DefNew
Desired survivor size 5242880 bytes, new threshold 1 (max 1)
- age 1: 2237152 bytes, 2237152 total
: 54886K->2184K(92160K), 0.0641037 secs] 54886K->53384K(194560K), 0.0641390 secs] [Times: user=0.03 sys=0.03, real=0.06 secs]
[GC [DefNew
Desired survivor size 5242880 bytes, new threshold 1 (max 1)
: 43144K->0K(92160K), 0.0036114 secs] 94344K->53384K(194560K), 0.0036418 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
def new generation total 92160K, used 63078K [0x1a1d0000, 0x205d0000, 0x205d0000)
eden space 81920K, 77% used [0x1a1d0000, 0x1df69a10, 0x1f1d0000)
from space 10240K, 0% used [0x1f1d0000, 0x1f1d0000, 0x1fbd0000)
to space 10240K, 0% used [0x1fbd0000, 0x1fbd0000, 0x205d0000)
tenured generation total 102400K, used 53384K [0x205d0000, 0x269d0000, 0x269d0000)
the space 102400K, 52% used [0x205d0000, 0x239f2260, 0x239f2400, 0x269d0000)
compacting perm gen total 12288K, used 360K [0x269d0000, 0x275d0000, 0x2a9d0000)
the space 12288K, 2% used [0x269d0000, 0x26a2a3c0, 0x26a2a400, 0x275d0000)
ro space 8192K, 66% used [0x2a9d0000, 0x2af20f10, 0x2af21000, 0x2b1d0000)
rw space 12288K, 52% used [0x2b1d0000, 0x2b8206d0, 0x2b820800, 0x2bdd0000)

四、过程解析

下面观察每一步语句执行后,jvm内存的变化情况,并给出解析。

1)在执行第一个语句,alloc1分配2M空间

alloc1 = new byte[tenMB / 5];

后,根据分代策略,在新生代的eden区分配2M的空间存储对象。

2)在执行第二语句,alloc2分配50M

alloc2 = new byte[5 * tenMB];

前面alloc1分配2M后,因为eden的80M空间还有80-2=78M还可以容纳下allocation2要求的50M空间,因此接着在eden区域分配。

3)当执行第三句,alloc3分配40M

alloc3 = new byte[4 * tenMB];

还是尝试在eden上分配,但是eden空间只剩下28M,不能容纳alloc3要求的40M空间。于是触发在新生代上的一次gc,将Eden区的存活对象转移到Survivor区。在这个里先将2M的alloc1对象存放(其实是copy,参见java 垃圾回收策略的描述)到from区,然后copy 50M的alloc2对象,显然survivor区不能容纳下alloc2对象,该对象被直接copy到年老代。需要说明的是复制到Survivor区的对象在经历一次gc后期对象年龄会被加一。

在eden区gc后腾出空间可以存放allocation3的40M对象,则alloc3分配40M对象如图:

4)执行第四句,将alloc3置空

alloc3 = null;

这是eden上alloc3分配的的40M对象则变成可被回收状态。

5)执行第5句,对alloc重新分配60M空间

allocation3 = new byte[6 * tenMB];

还是尝试先在eden区上分配,发现超出了eden区域的容量,则再次触发新生代上的一次gc。首先eden上分配的40M对象因为没有被再使用,则直接被回收。而根据的设置不同,这次gc的行为会稍有不同。

先看MaxTenuringThreshold不设置,即取默认值15的时候。eden区上无用的40M回收后,再考察Survivor区域的对象是否满足对象晋升老年代的年龄阈值,发现from中的2M对象,年龄是1,不满足晋升条件,则不被处理,只是把Survivor区域的经历这次回收未被处理的对象age加一,即新的age为2.如图:

通过输出日志也显示:经过这次回收年轻代大小,由43114K变为2184k,总的大小由94344k变为53384k,即反映出回收了40M无用对象。

Desired survivor size 5242880 bytes, new threshold 15 (max 15)
- age 2: 2237008 bytes, 2237008 total
: 43144K->2184K(92160K), 0.0028660 secs] 94344K->53384K(194560K), 0.0028957 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

在年轻代上gc后腾出空间后,新的alloc3的60M空间被分配到eden 区域上。分配后堆如下:

以上是不设置晋升阈值MaxTenuringThreshold情况下进行的gc,以及gc后alloc3的分配。

再看看当MaxTenuringThreshold设置为1的情况。同样eden区上无用的40M回收后,再考察Survivor区域的对象是否满足对象晋升老年代的年龄阈值,发现from中的2M对象,年龄是1,满足晋升条件,则Survivor区域满足年龄的对象被拷贝到年老区。

通过日志显示年轻代的大小被清0了,表示survivor的存活对象因为满足晋升条件被移到被移到年老代了。

[GC [DefNew
Desired survivor size 5242880 bytes, new threshold 1 (max 1)
: 43144K->0K(92160K), 0.0036114 secs] 94344K->53384K(194560K), 0.0036418 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

同样的,gc完后会在eden上分配空间来存储alloc3对象,这种情况下堆结构如图:

比较上面两个图,发现差别就仅仅在于survivor中的2M对象是否被认为生存时间足够长科院被移到年老代中去。从上面日志高亮部分from区域的最终存储也可反映出了这个差别。

比较前面两个日志可以看到:总的大小和上面设置和不设置MaxTenuringThreshold(其实是MaxTenuringThreshold设置1还是15)没有关系,都是由94344k变为53384k,即都是回收了40M eden区域无用对象。第N次gc时存活的满足晋升条件则由survivor移到年老代,不满足的还留在survivor区域,堆的总的大小没有变。

五、最后

上面通过最简单的例子示意了下在jvm堆上对象是如果分配的,当空间不足时,是如何调整回收的。希望可以对jvm的堆上结构和gc思路有个基本的了解。当然相关参数(其实反映的是机制)远比这个复杂,有挺多细节,更多的是在实践中来体会。

最简单例子图解JVM内存分配和回收(转)的更多相关文章

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

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

  2. 图解JVM内存分配和回收

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

  3. JVM内存分配与回收

    1.内存分配与回收策略 内存自动管理:自动化的解决了对象内存分配和回收对象内存的问题. 一般在堆上分配对象,也可能经过JTI编译后间接在栈上分配. 主要分配在新生代的Eden区,如果启动了本地线程分配 ...

  4. JVM内存分配和回收

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

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

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

  6. 窥探JVM内存分配和回收的过程

    一.环境 JDK 垃圾收集器 是否启用TLAB 通用JVM参数(堆内存分配见下图) 1.6.0_65 Serial + Serial Old 否 -Xms20m -Xmx20m -Xmn10m -XX ...

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

    对象的内存分配,主要是在java堆上分配(有可能经过JIT编译后被拆为标量类型并间接地在栈上分配),如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也是直接分配到老年代,分配规则不 ...

  8. A4. JVM 内存分配及回收策略

    [概述] Java 技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题:给对象分配内存以及回收分配给对象的内存. 对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的 Ed ...

  9. JVM——内存分配与回收策略

    1.对象优先在Eden区分配 大多数情况下,对象在新生代Eden区分配.当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC. 虚拟机提供了 -XX:+PrintGCDetails这 ...

随机推荐

  1. 《Microsoft SQL Server 2012 T-SQL Fundamentals》

    书名 <SQL Server 2012 T-SQL基础教程> 图片 时间  2017-8 学习  每章后面有习题很适合我,看完写sql的能力有质的飞跃好书 http://tsql.soli ...

  2. java错误分析之junit测试错误(实验一)

    本文转自:https://www.cnblogs.com/anny0404/p/5275595.html 在原作者的基础上进行部分添加,也很感谢原作者这篇博文,帮我分析与解决问题! 原文: 下载了最新 ...

  3. 原生JS强大DOM选择器querySelector与querySelectorAll

    在传统的 JavaScript 开发中,查找 DOM 往往是开发人员遇到的第一个头疼的问题,原生的 JavaScript 所提供的 DOM 选择方法并不多,仅仅局限于通过 tag, name, id ...

  4. CSS之 :before && :after的用法,伪类和伪元素的区别

    一::before && :after的用法 :before 如同对伪元素的名称一样,:before 是用来给指定的元素的内容前面插入新的内容.举例说明: .before:before ...

  5. 利用火车头采集A67手机电影教程一

    今天将讲述:利用火车头采集A67手机电影,并导入到网站中,如果你要更多更快的教程,请访问:http://www.it28.cn/category-191.html 现在以A67中的电影中的动作片为例: ...

  6. loadrunner 运行脚本-Run-time Settings-Browser Enmulation设置详解

    运行脚本-Run-time Settings-Browser Enmulation设置详解 by:授客 QQ:1033553122 浏览器模拟 所有Internet Vuser Header包含一个标 ...

  7. 安卓开发_浅谈TimePicker(时间选择器)

    TimePicker也继承自FrameLayout类.时间选择控件向用户显示一天中的时间(可以为24小时,也可以为AM/PM制),并允许用户进行选择.如果要捕获用户修改时间数据的事件,便需要为Time ...

  8. 结对项目-四则运算"软件"之升级版

    本次作业要求来自:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE1/homework/2213 github地址为:https://github.com/L ...

  9. AIOps 一场颠覆传统运维的盛筵

    "颠覆传统运维."是 OneAPM CEO 陈旭经常挂在嘴边的一句话.为什么说 AIOps 将颠覆传统运维?如何才能把人工智能和运维管理相结合并落地? 2018年5月,OneAPM ...

  10. 自己写bitmap

    备注:1.原创文章,转载请标明出处: 2.欢迎建议和意见 3.我的实现是C语言,为了保护公司隐私,下述数据类型被我改了.实际上int应改是无符号4个字节的类型,byte是有符号1个字节,才能保证移植性 ...