之前看过《深入了解Java虚拟机》感觉容易忘,今天写一篇博客加深一下印象。

JVM的内存分配和垃圾回收(GC)主要发生在Java堆中。而Java堆根据对象的存活时间可以分为新生代和老年代,而新生代又细分为Eden区、From Survivor区、To Survivor区,这是由于新生代中的垃圾回收算法基本都是复制算法

1.对象优先在Eden区中分配

  当Eden区没有足够空间进行分配时,虚拟机会发起一次新生代GC(Minor GC)。因为Java对象大多数都具有朝生夕灭的特性,所以Minor GC非常频繁,回收速度也比较快。

我们可以通过参数-XX:+PrintGCDetails来查看GC日志。下面举个实际例子来看看是不是优先在Eden区中分配内存。

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

设置的参数

package MinorGC;

public class MinorGC {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3;
allocation1 = new byte[1*_1MB];
allocation2 = new byte[2*_1MB];
allocation3 = new byte[2*_1MB];
}
}

可以看到控制台输出

新生代的内存使用情况:total 9216K, used 7482K

老年代的内存使用情况:total 10240K, used 0K

这样可以看出对象优先在Eden区中分配。

2.大对象直接进入老年代

  大对象是指需要大量连续内存空间的Java对象,例如很长的字符串和数组,上面代码中的byte[]数组就是大数组。经常出现大对象容易导致内存中还有不少内存就要提前GC来获取连续的空间来安放它们。

虚拟机中可以设置参数-XX:PretenureSizeThreshold参数来让大于这个设置值的对象直接在老年代分配,这样的目的是避免在Eden区和两个Survivor区之间进行大量的内存复制。

下面测试一下,这时候要注意一下我用的是Java8,默认的垃圾回收器是Parallel Scavenge(新生代)+Parallel Old(老年代),而Parallel Scavenge不认识参数

-XX:PretenureSizeThreshold(Parallel Scavenge不需要设置),所以要先加上-XX:+UseSerialGC来将虚拟机的垃圾回收器设置成Serial / Serial Old回收器。

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

设置的参数

package MinorGC;

public class MinorGC {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
byte[] allocation4;
// allocation1 = new byte[_1MB];
// allocation2 = new byte[2*_1MB];
// allocation3 = new byte[2*_1MB];
allocation4 = new byte[4*_1MB];
}
}

可以发现4MB的对象直接放到老年代中了

3.长期存活的对象将进入老年区

  虚拟机给每个对象定义一个对象年龄计数器。如果对象在Eden区出生并经过第一次Minor GC后仍然存活,并能被Survivor容纳,将被移动到Survivor的空间中,并且对象年龄设为1对象在Survivor区每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁),就将被晋升到老年代中。JVM中可以通过-XX:MaxTenuringThreshold设置。

下面我们测试一下

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

设置的参数

package MinorGC;

public class MinorGC {
private static final int _1MB = 1024*1024;
@SuppressWarnings("unused")
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4];
// 什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}

该方法中allocation1对象需要256KB内存,Survivor区可以容纳,所以讲它放到Survivor区中并让它年龄加1,。在第二次GC发生后它就进入老年代了,而Survivor区刚好被清除干净。

4.动态对象年龄判定

  为了更好地适应不同程序的内存状况,虚拟机并不是永远要求对象的年龄必须达到了MaxTenuringThreshold才晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

测试

配置和前面的一样,只是MaxTenuringThreshold=15

package MinorGC;

public class MinorGC {
private static final int _1MB = 1024*1024;
@SuppressWarnings("unused")
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[_1MB / 4];
// allocation1+allocation2大于survivo空间一半
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
}

发现运行结果中Survivor的空间占用仍然为0%,而老年代比预期增加了6%,也就是说,allocation1、allocation2对象都直接进入了老年代,而没有等到15岁的临界年龄。因为这两个对象加起来已经到达了

512KB,并且它们是同年的,满足同年对象达到Survivor空间的一半规则。

在注释掉allocation2后,发现只有一个256KB进入了老年代。

5.空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。与生活中的贷款担保类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间

取平均值进行比较其实仍然是一种动态概率的手段,也就是说,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败(Handle Promotion Failure)。如果出现了HandlePromotionFailure失败,那就只好在失败后重新发起一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁。

补充一下参数有哪些:

1、-Xmx –Xms:指定最大堆和最小堆

2、-Xmn、-XX:NewRatio、-XX:SurvivorRatio:

  • -Xmn:设置新生代大小
  • -XX:NewRatio:新生代(eden+2*s)和老年代(不包含永久区)的比值

 例如:4,表示新生代:老年代=1:4,即新生代占整个堆的1/5

  • -XX:SurvivorRatio(幸存代)设置两个Survivor区和eden的比值

  例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10

参考自:深入理解JVM

    https://www.jianshu.com/p/fa3569127416

JVM的内存分配垃圾回收策略的更多相关文章

  1. jvm之内存分配与回收策略

    1.java堆中各代分布 (1)Young:主要是用来存放新生的对象. (2)Old:主要存放应用程序中生命周期长的内存对象. (3)Permanent:是指内存的永久保存区域,主要存放Class和M ...

  2. JVM的内存分配和回收策略

    对象的Class加载 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析和初始化过.如果没有,那必须先执行相应 ...

  3. JVM:内存分配与回收策略

    Java技术体系中所提倡的自动内存管理最终可以归结为自动化的解决了两个问题:给对象分配内存以及回收分配给对象的内存. 对象的内存分配,往大方向讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类 ...

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

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

  5. JVM垃圾回收器、内存分配与回收策略

    新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...

  6. JVM学习十 -(复习)内存分配与回收策略

    内存分配与回收策略 对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,分配规则不固定 ...

  7. Java虚拟机内存分配与回收策略

    内存分配与回收策略 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行, 执行的速度一般也会比较快. Full GC ...

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

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

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

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

随机推荐

  1. python zlib模块缺失报错:RuntimeError: Compression requires the (missing) zlib module

    解决方式: # yum install zlib # yum install zlib-devel 下载成功后,进入python2.7的目录,重新执行 #make #make install 此时先前 ...

  2. css 网格布局

    一.概述 网格布局(Grid)是最强大的 CSS 布局方案. 它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局.以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了. 上 ...

  3. Can't generate API documentation in l5-swagger

    Can't generate API documentation in l5-swagger Ask Question Asked 3 months ago Active 1 month ago Vi ...

  4. HTTP协议(待写)

    先来了解了解 TCP/IP TCP/IP(Transmission Control Protocol / Internet Protocol)是计算机通讯必须遵守的规则,是不同的通信协议的大集合,其里 ...

  5. dinoql 支持自定义resovler了

    dinoql 当前版本0.4.0 支持自定义reovler 了,使用也比较简单 环境准备 初始化 yarn init -y 添加依赖 yarn add dinoql graphql-tag packa ...

  6. globing通配符

    匹配标点符号 linux中只要不含有/的文件就可以生成,所以标点符号也是符合要求的 匹配空白 使用\对空白进行转义,这样就可以生成包含空格名称的文件 但是不推荐这样用,容易让别人在使用的时候造成误解 ...

  7. HAVING 搜索条件在进行分组操作之后应用

    HAVING 搜索条件在进行分组操作之后应用: 如:查询帖子访问量大于15的用户id: select t.user_id,u.name,sum(count_view) from t_topic t l ...

  8. 2017.10.1 国庆清北 D1T1 zhx的字符串题

    题目背景 2017国庆清北D1T1 题目描述 你是能看到第一题的 friends 呢. ——hja 何大爷对字符串十分有研究,于是天天出字符串题虐杀 zhx.何大爷今天为 字符串定义了新的权值计算方法 ...

  9. x64内核强删文件.

    目录 x64内核中强删文件的实现 一丶简介 1.步骤 2.Nt驱动代码 x64内核中强删文件的实现 一丶简介 说道删除文件.有各种各样的方法. 有ring3 也有ring0. 而且也有许多对抗的方法. ...

  10. SpringCloud Feign通过FallbackFactory显示异常信息

    SpringCloud Feign可以进行服务消费,而且内置了Hystrix,能够进行熔断. Feign可以通过fallback指定熔断回调的类.代码示例及讲解可见: https://www.cnbl ...