JVM笔记7-内存分配与回收策略
1.对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区中没有足够空间分配时,虚拟机将发起一次Minor GC。虚拟机提供了-XX:PrintGCDetails 这个收集器日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况。
现在我们看如下例子:
package hjc.test9; /**
* Created by cong on 2018/4/2.
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
*/
public class TestGC { private static final int _1MB = * ; public static void testAllocation(){
byte[] allcation1, allcation2, allcation3, allcation4; allcation1 = new byte[ * _1MB];
allcation2 = new byte[ * _1MB];
allcation3 = new byte[ * _1MB];
allcation4 = new byte[ * _1MB];
} public static void main(String[] args) {
TestGC.testAllocation();
} }
运行结果如下:

testAllocation()方法中,尝试分配3个2MB大小和1个4MB大小的对象,在运行时-Xms20M -Xmx20M -Xmn10M 这三个参数限制了Java堆大小为20MB,不可扩展,其中10MB分配给新生代,剩下10MB分配给老年代。-XX:SurvivorRatio=8 决定了新生代中
Eden区与一个Survivor区的空间比例为8:1,从输出的结果可以看到eden space 8192K,from space 1024K,to space 1024K,的信息,新生代总可用空间为9216KB(Eden+1个Survivor的总容量)。
执行testAllocaton()中分配Allocation4对象的语句时发生一次Minor GC,这次GC的结果是新生代6651KB变成148KB,而总内存占用量则几乎没有减多少,因为allocation1,allocation2,allocation3三个对象都是存活的,虚拟机几乎没有找到可回收对象。这次GC
发生的原因是给allocaton4分配内存的时候发现Eden已经被占用了6MB,剩余空间已经不足以分配allocation4所需的4MB,因此,发生Minor GC。GC期间虚拟机又发现已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好分配担保机制
提前转移到老年代去。这次GC结束后,4MB的allocation4对象顺利分配在Eden中,因此程序执行完的结果是Eden占用4MB(被allocation4占用),Survivor空闲,老年代被占用6MB(被allocation1,allocation2,allocation3占用)。
2.大对象直接进入老年代
所谓的大对象是指,需要大量连续内存的Java对象,最经典的大对象就是那种很长的字符串以及数组,大对象对虚拟机的内存分配来说是一个坏消息,经常出现大对象容易导致内存还有不少空间就提前出发垃圾收集以获取足够的练习空间来安置它们。
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在Eden区以及两个Survivor区之间发生大量的内存复制。
例子如下:
package hjc.test9; /**
* Created by cong on 2018/4/2.
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
*-XX:PretenureSizeThreshold=3145728
*/
public class TestGC1 { private static final int _1MB = * ; public static void testPretenureSizeThreshold(){
byte[] allocation;
allocation = new byte[4 * _1MB]; //出现一次Minor GC
} public static void main(String[] args) {
TestGC1.testPretenureSizeThreshold();
} }
运行结果如下:

执行testPretenureSizeThreshold()方法后,我们看到Eden空间几乎没有被使用,而老年代的10MB空间被使用了40%,也就是说4MB的allocation对象直接就分配在老年代中,这是因为PretenureSizeThreshold被设置为3MB(就是3145728,这个参数不能想-Xmx之类的参数一样直接写3MB)
因此超过3MB的对象会直接在老年代进行分配。
3.长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄计数器。如果对象在Eden出生并经历过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1.对象在Survivor区中每熬过一次GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁),
就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。这里分别用-XX:MaxTenuringThreshold=1 和 -XX:MaxTenuringThreshold=15演示。
例子如下:
package hjc.test9; /**
* Created by cong on 2018/4/2.
*
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
*/
public class TestGC2 { private static final int _1MB = * ; @SuppressWarnings("unused")
public static void testTenuringThreshold(){
byte[] allcation1, allcation2, allcation3, allcation4;
allcation1 = new byte[_1MB / ];
//什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allcation2 = new byte[_1MB / ];
allcation3 = new byte[ * _1MB];
allcation4 = new byte[ * _1MB];
allcation4 = null;
allcation4 = new byte[ * _1MB];
} public static void main(String[] args) {
TestGC2.testTenuringThreshold();
} }
结果如下:


testTenuringThreshold()方法中的allocation1对象需要256KB内存,Surivivor空间可以容纳。当MaxTenringThreshold=1时,allocation1对象在第二次GC发生时进入老年代,新生代已使用的内存GC后非常干净的0KB。
自己在修改MaxTenringThreshold=15时,这里就第二次GC发生后,allocation对象则还留在新生代Survivor空间,这时新生代仍然有404KB被占用。如下图:

4.动态对对象年龄判定。
为了能更好的适应不同程序的内存情况,虚拟机并不是永远地要求对象年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就直接进入老年代,
无需等到MaxTenuringThreshold中要求的年龄。
例子如下:
package hjc.test9; /**
* Created by cong on 2018/4/2.
*
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
*/
public class TestGC2 { private static final int _1MB = * ; @SuppressWarnings("unused")
public static void testTenuringThreshold(){
byte[] allcation1, allcation2, allcation3, allcation4;
allcation1 = new byte[_1MB / ];
//allcation1+allcation2 大于Survivor空间的一般
allcation2 = new byte[_1MB / ];
allcation3 = new byte[ * _1MB];
allcation4 = new byte[ * _1MB];
allcation4 = null;
allcation4 = new byte[ * _1MB];
} public static void main(String[] args) {
TestGC2.testTenuringThreshold();
} }
结果如下:

可以看到testTenuringThreshold方法设置-XX:MaxTenuringThreshold=15,会发现运行结果中Survivor的空间占用仍谈为0%,而老年代比预期增加了6%,也就是说allocation1,2 直接进入老年代,没到等到15岁的临界年龄。因为这两对象
加起来已经达到了512KB,并且它们都是同年的,满足同年对象达到Survivor空间的一般规则。
5.空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间付否大于新生代所有对象总空间,如果这条件成立,那么MinorGC可以确保是安全的,如果不成立,则虚拟机会看HandlePromotionFailure设置是否允许担保失败。如果允许
那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年带对象的平均大小,如果大于,则尝试进行一次MinorGC,尽管这次Minor GC是有风险的,如果小于,或者HandlePromotionFailure设置不允许冒险,这是也要进行一次Full GC
取平均值进行比较其实仍然是一种动态概率的手段,如果某次Minor GC存活的对象突增,远高于平均值的话,依然会出现担保失败(Handle Promotion Failure),如果出现了担保失败,那就只好重新发起一次Full GC.虽然担保失败时绕圈子是最大的。
但大部分情况下都还是会将HandlePromotionFailure开关打开。避免Full GC过于频繁。
代码如下:
package hjc.test9; /**
* Created by cong on 2018/4/2.
*
* -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:HandlePromotionFailure
*/
public class TestGC2 { private static final int _1MB = * ; @SuppressWarnings("unused")
public static void testTenuringThreshold(){
byte[] allcation1, allcation2, allcation3, allcation4, allcation5, allcation6, allcation7;
allcation1 = new byte[ * _1MB];
allcation2 = new byte[ * _1MB];
allcation3 = new byte[ * _1MB];
allcation1 = null;
allcation4 = new byte[ * _1MB];
allcation5 = new byte[ * _1MB];
allcation6 = new byte[ * _1MB];
allcation4 = null;
allcation5 = null;
allcation6 = null;
allcation7 = new byte[ * _1MB]; } public static void main(String[] args) {
TestGC2.testTenuringThreshold();
} }
运行结果如下:


JVM笔记7-内存分配与回收策略的更多相关文章
- JVM探秘:内存分配与回收策略
本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 内存分配一般关注的是对象在堆上分配的情况,对象主要分配在新生代的Eden区中,如果启用 ...
- Java虚拟机垃圾回收:内存分配与回收策略 方法区垃圾回收 以及 JVM垃圾回收的调优方法
在<Java对象在Java虚拟机中的创建过程>了解到对象创建的内存分配,在<Java内存区域 JVM运行时数据区>中了解到各数据区有些什么特点.以及相关参数的调整,在<J ...
- JVM垃圾回收器、内存分配与回收策略
新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...
- JVM学习十 -(复习)内存分配与回收策略
内存分配与回收策略 对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,分配规则不固定 ...
- Java虚拟机内存分配与回收策略
内存分配与回收策略 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行, 执行的速度一般也会比较快. Full GC ...
- jvm内存分配和回收策略
在上一篇中,已经介绍了内存结构是什么样的. 这篇来介绍一下 内存是怎么分配的,和怎么回收的.(基本取自<深入理解Java虚拟机>一书) java技术体系中所提倡的自动内存管理最终可以归结为 ...
- JVM的内存分配和回收策略
对象的Class加载 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执行相应 ...
- JVM 内存分配和回收策略
对象的内存分配,主要是在java堆上分配(有可能经过JIT编译后被拆为标量类型并间接地在栈上分配),如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也是直接分配到老年代,分配规则不 ...
- JVM:内存分配与回收策略
Java技术体系中所提倡的自动内存管理最终可以归结为自动化的解决了两个问题:给对象分配内存以及回收分配给对象的内存. 对象的内存分配,往大方向讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类 ...
随机推荐
- A Dream (PKUWC 2018)
A Dream (PKUWC 2018) 这是一个梦. 从没有几分节日气氛的圣诞,做到了大雪纷飞的数九寒天. 忘了罢! 不记得时间,不记得地点.随着记忆的褪去,一切也只会不复存在. Day-34? D ...
- 洛谷P3796 【模板】AC自动机(加强版)(AC自动机)
洛谷题目传送门 先膜一发yyb巨佬 orz 想学ac自动机的话,推荐一下yyb巨佬的博客,本蒟蒻也是从那里开始学的. 思路分析 裸的AC自动机,这里就不讲了.主要是这题太卡时了,尽管时限放的很大了.. ...
- WC2001 高性能计算机
cogs网址 这道题DP 设g[0/1][i][a][b]表示第i个机子做了a个A,b个B,0/1表示当前为A/B的最小代价 N^4转移 设f[i][a][b]表示前i个机子做了a个A,b个B的最小答 ...
- 金三银四,2018最新iOS面试题,由它可以搞定面试官?
序言 这些资料,你一定会用到!我相信很多人都在说,iOS行业不好了,iOS现在行情越来越难了,失业的人比找工作的人还要多.失业即相当于转行,跳槽即相当于降低自己的身价.那么做iOS开发的你,你是否在时 ...
- node,cnpm安装和配置
作为一个前端人员,node已经是必备. 1.下载 下载地址:https://nodejs.org/zh-cn/download/ 选择相应的版本下载 2.解压缩 将文件解压到要安装的位置,并新建两个目 ...
- 使用BigDecimal报的错
错误:java.lang.arithmeticexception: non-terminating decimal expansion; no exact representa 小数位膨胀 解决方法 ...
- python 全栈开发,Day4
python之文件操作 一.文件操作基本流程 计算机系统分为:计算机硬件,操作系统,应用程序三部分. 我们用python或其他语言编写的应用程序若想要把数据永久保存下来,必须要保存于硬盘中,这就涉及到 ...
- pureftpd支持php实现图片上传
安装好php后,可安装pureftp工具 这里我给出一个rpm包可直接安装配置:http://pan.baidu.com/s/1i5OhS3r(包括启动脚本在内) FTP测试:安装ftp客户端,用户名 ...
- 设计模式 --> (13)备忘录模式
备忘录模式 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态 示例 玩游戏时都会保存进度,所保存的进度以文件的形式存在.这样下次就可 ...
- Anagram
Anagram poj-1256 题目大意:给你n个字符串,求每一个字符串所有字符的全排列,按照顺序输出所有全排列. 注释:每一个字符长度小于13,且字符排序的顺序是:A<a<B<b ...