一、垃圾回收日志说明

[GC[DefNew: 7307K->494K(9216K), 0.0043710 secs] 7307K->6638K(19456K), 0.0044894 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

1、日志开发的“[GC”说明了这次垃圾回收的停顿类型,如果有FULL,说明这次GC是发生了Stop-The-World的。

2、接下来的“[DefNew”表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的。例如Serial收集器中的新生代名“Default New Generation”,所以显示的是“[DefNew”。如果是ParNew收集器,显示“[ParNew”。如果是Parallel Scavenge收集器,显示“PSYoungGen”。

3、后面方括号内部的“7307K->494K(9216K)”含义是:GC前该区域已使用容量->GC后该区域已使用容量(该内存区域总容量)。而在方括号之外的“7307K->6638K(19456K)”含义是:GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)。

4、再往后,“0.0044894 secs”表示该内存区域GC所占用的时间,单位秒。

二、对象优先在Eden分配

对象通常在新生代的Eden区分配,当Eden区没有足够空间时,虚拟机会发起一次Minor GC。与Minor GC对应的还有Major GC、Full GC。

Minor GC:指发生在新生代的垃圾收集动作,非常频繁。速度较快。

Major GC:指发生在老年代的GC,出现Major GC,经常会伴随一次Minor GC,同时Minor GC也会引起Major GC,一般在GC日志中统称为GC,不频繁。

Full GC:指发生在老年代和新生代的GC,速度较慢,需要Stop The World。

测试代码:

/**
* 虚拟机参数为“-verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8”,即10M新生代,10M老年代,10M新生代中8M的Eden区,两个Survivor区各1M
* -Xms为堆最小容量,-Xmx为堆最大容量,-Xmn为新生代容量,SurvivorRatio=8为Eden和Survivor(from、to)比例为8:1:1
*/
public class TestAllocation { private static final int _1MB = 1024 * 1024;
public static void test() {
byte[] allocation1,allocation2,allocation3,allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB]; //出现一次minor GC
} public static void main(String[] args) {
test();
}
}

测试结果:

[GC[DefNew: 7307K->494K(9216K), 0.0043710 secs] 7307K->6638K(19456K), 0.0044894 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 4920K [0x04800000, 0x05200000, 0x05200000)
eden space 8192K, 54% used [0x04800000, 0x04c52770, 0x05000000)
from space 1024K, 48% used [0x05100000, 0x0517b970, 0x05200000)
to space 1024K, 0% used [0x05000000, 0x05000000, 0x05100000)
tenured generation total 10240K, used 6144K [0x05200000, 0x05c00000, 0x05c00000)
the space 10240K, 60% used [0x05200000, 0x05800030, 0x05800200, 0x05c00000)
compacting perm gen total 12288K, used 1672K [0x05c00000, 0x06800000, 0x09c00000)
the space 12288K, 13% used [0x05c00000, 0x05da22f0, 0x05da2400, 0x06800000)

说明:

GC前新生代占用了7M,然后来了一个4M,Eden和from仅剩下2M不够分配了,触发了一次Minor GC。但是GC后内存仍然不够,因为application1、application2、application3三个引用还存在,另外一块1M的survivor也不够放下这总共6M的三个对象,那么这次Minor GC的效果其实是通过分配担保机制将这6M的内容转入老年代中。然后再来一个4M的,由于此时Minor GC之后新生代只用了494K,够分配了,所以4M顺利进入新生代。

三、大对象直接进入老年代

大对象是指需要大量连续内存空间的Java对象,最典型的大对象是那种很长的字符串以及数组,大对象对虚拟机的分配来说是个坏消息(比遇到大对象更加怀的消息就是遇到一群“朝生夕灭”的短命大对象,写程序时候应避免)。经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集器回收,以获取足够的连续空间来安置他们。

虚拟机提供了-XX:PretenureSizeThreadshold参数来设置大对象的阀值,超过阀值的对象直接进入老年代。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集)。

测试代码(超过3M直接分配进老年代):

/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:PretenureSizeThreadshold=3145728
*/
public class TestPretenureSizeThreadshold { private static final int _1MB = 1024 * 1024; public static void main(String[] args) {
byte[] application = new byte[4 * _1MB]; }
}

测试结果:

Heap
def new generation total 9216K, used 1327K [0x04600000, 0x05000000, 0x05000000)
eden space 8192K, 16% used [0x04600000, 0x0474bf18, 0x04e00000)
from space 1024K, 0% used [0x04e00000, 0x04e00000, 0x04f00000)
to space 1024K, 0% used [0x04f00000, 0x04f00000, 0x05000000)
tenured generation total 10240K, used 4096K [0x05000000, 0x05a00000, 0x05a00000)
the space 10240K, 40% used [0x05000000, 0x05400010, 0x05400200, 0x05a00000)
compacting perm gen total 12288K, used 1670K [0x05a00000, 0x06600000, 0x09a00000)
the space 12288K, 13% used [0x05a00000, 0x05ba1a50, 0x05ba1c00, 0x06600000)

四、长期存活对象进入老年代

虚拟机给每个对象定义了一个年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能够被Survivor容纳,对象年龄就加1。对象在Survivor区每熬过一次Minor GC,年龄就加1。当年龄超过一定阀值(默认为15),则进入老年代中。阀值可通过-XX:MaxTenuringThreshold来设置。

测试代码:

/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
*/
public class TestTenuringThreshold { private static final int _1MB = 1024 * 1024; public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3;
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}

测试结果:

[GC[DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 768272 bytes, 768272 total
: 5515K->750K(9216K), 0.0050436 secs] 5515K->4846K(19456K), 0.0051715 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 256 bytes, 256 total
: 5094K->0K(9216K), 0.0015457 secs] 9190K->4845K(19456K), 0.0016256 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4178K [0x04400000, 0x04e00000, 0x04e00000)
eden space 8192K, 51% used [0x04400000, 0x04814828, 0x04c00000)
from space 1024K, 0% used [0x04c00000, 0x04c00100, 0x04d00000)
to space 1024K, 0% used [0x04d00000, 0x04d00000, 0x04e00000)
tenured generation total 10240K, used 4845K [0x04e00000, 0x05800000, 0x05800000)
the space 10240K, 47% used [0x04e00000, 0x052bb6e8, 0x052bb800, 0x05800000)
compacting perm gen total 12288K, used 1672K [0x05800000, 0x06400000, 0x09800000)
the space 12288K, 13% used [0x05800000, 0x059a2248, 0x059a2400, 0x06400000)

说明:发生了两次Minor GC,第一次是在给allocation3进行分配的时候会出现一次Minor GC,此时survivor区域不能容纳allocation2,但是可以容纳allocation1,所以allocation1将会进入survivor区域并且年龄为1,达到了阈值,将在下一次GC时晋升到老年代,而allocation2则会通过担保机制进入老年代。第二次发生GC是在第二次给allocation3分配空间时,这时,allocation1的年龄加1,晋升到老年代,此次GC也可以清理出原来allocation3占据的4MB空间,将allocation3分配在Eden区。所以,最后的结果是allocation1、allocation2在老年代,allocation3在Eden区。

五、动态对象年龄判定

虚拟机并未要求对象一定要达到年龄阀值后,才可进入老年代。当Survivor空间中所有相同年龄对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象也可直接进入老年代。

测试代码:

/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
*/
public class TestTenuringThreshold2 { private static final int _1MB = 1024 * 1024; public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3,allocation4;
allocation1 = new byte[_1MB / 4];
//application1+application2大于Survivor空间一半
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
}

测试结果:

[GC[DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 1030592 bytes, 1030592 total
: 5771K->1006K(9216K), 0.0042280 secs] 5771K->5102K(19456K), 0.0043710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 256 bytes, 256 total
: 5350K->0K(9216K), 0.0016450 secs] 9446K->5102K(19456K), 0.0017286 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4178K [0x04400000, 0x04e00000, 0x04e00000)
eden space 8192K, 51% used [0x04400000, 0x04814828, 0x04c00000)
from space 1024K, 0% used [0x04c00000, 0x04c00100, 0x04d00000)
to space 1024K, 0% used [0x04d00000, 0x04d00000, 0x04e00000)
tenured generation total 10240K, used 5101K [0x04e00000, 0x05800000, 0x05800000)
the space 10240K, 49% used [0x04e00000, 0x052fb798, 0x052fb800, 0x05800000)
compacting perm gen total 12288K, used 1672K [0x05800000, 0x06400000, 0x09800000)
the space 12288K, 13% used [0x05800000, 0x059a2260, 0x059a2400, 0x06400000)

结果说明:发生了两次Minor GC,第一次发生在给allocation4分配内存时,此时allocation1、allocation2将会进入survivor区,而allocation3通过担保机制将会进入老年代。第二次发生在给allocation4分配内存时,此时,survivor区的allocation1、allocation2达到了survivor区容量的一半,将会进入老年代,此次GC可以清理出allocation4原来的4MB空间,并将allocation4分配在Eden区。最终,allocation1、allocation2、allocation3在老年代,allocation4在Eden区。

六、空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大连续空间是否大于新生代所有对象大小总和。若成立,则说明Minor GC是安全的。否则,虚拟机需要查看HandlePromotionFailure的值,看是否运行担保失败,若允许,则虚拟机继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,将尝试进行一次Minor GC;若小于或者HandlePromotionFailure设置不运行冒险,那么此时将改成一次Full GC,以上是JDK Update 24之前的策略,之后的策略改变了,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。

冒险是指经过一次Minor GC后有大量对象存活,而新生代的survivor区很小,放不下这些大量存活的对象,所以需要老年代进行分配担保,把survivor区无法容纳的对象直接进入老年代。

具体的流程图如下:

《深入理解java虚拟机》笔记(6)内存分配与回收策略的更多相关文章

  1. Java虚拟机面试重点-------------内存分配和回收策略

    1 对象优先分配在Eden区 对象优先在Eden进行分配,大多数情况下,对象在新生代Eden区进行分配.当Eden区没有足够的空间进行分配时,虚拟机会发起一次Minor GC. 新生代GC(Ninor ...

  2. 《深入理解 java 虚拟机》学习 -- 内存分配

    <深入理解 java 虚拟机>学习 -- 内存分配 1. Minor GC 和 Full GC 区别 概念: 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java ...

  3. 转!!Java虚拟机堆的内存分配和回收

    Java内存分配和回收,主要就是指java堆的内存分配和回收.java堆一般分为2个大的区域,一块是新生代,一块是老年代.在新生代中又划分了3块区域,一块eden区域,两块surviver区域.一般称 ...

  4. 深入理解java虚拟机笔记Chapter3-内存分配策略

    内存分配策略 新生代和老年代的 GC 操作 新生代 GC 操作:Minor GC 发生的非常频繁,速度较块. 老年代 GC 操作:Full GC / Major GC 经常伴随着至少一次的 Minor ...

  5. 深入理解JAVA虚拟机原理之内存分配策略(二)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 1.对象优先在Eden分配 大多情况,对象在新生代Eden区分配.当Eden区没 ...

  6. Java虚拟机学习 - 对象内存分配与回收 ( 5 )

    对象优先在Eden上分配 大多数情况下,对象优先在新生代Eden区域中分配.当Eden内存区域没有足够的空间进行分配时,虚拟机将触发一次 Minor GC(新生代GC).Minor GC期间虚拟机将E ...

  7. 垃圾收集器与内存分配策略——深入理解Java虚拟机 笔记二

    在本篇中,作者大量篇幅介绍了当时较为流行的垃圾回收器,但现在Java 14都发布了,垃圾收集器也是有了很大的进步和发展,因此在此就不再对垃圾收集器进行详细的研究.但其基本的算法思想还是值得我们参考学习 ...

  8. 深入理解Java虚拟机笔记——垃圾收集器与内存分配策略

    目录 判断对象是否死亡 引用计数器算法 可达性分析算法 各种引用 回收方法区 垃圾收集算法 标记-清除算法 复制算法 标记-整理算法 分代收集算法 HotSpot算法实现 枚举根节点 GC停顿(Sto ...

  9. 【深入理解Java虚拟机】自动内存管理机制——垃圾回收机制

      Java与C++之间有一堵有内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来.C/C++程序员既拥有每一个对象的所有权,同时也担负着每一个对象生 ...

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

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

随机推荐

  1. PHP读取xml方法介绍

    一,什么是xml,xml有什么用途 XML(Extensible Markup Language)即可扩展标记语言,它与HTML一样,都是SGML(Standard Generalized Marku ...

  2. Java之类加载器(Class Loader)

    JVM默认有三个类加载器: Bootstrap Loader Bootstrap Loader通常有C编写,贴近底层操作系统.是JVM启动后,第一个创建的类加载器. Extended Loader E ...

  3. bzoj4555: 求和sum 快速傅立叶变换

    题目大意 给定\(S(n,m)\)表示第二类斯特林数,定义函数\(f(n)\) \[f(n) = \sum_{i=0}^n\sum_{j=0}^iS(i,j)*2^j*(j!)\] 给定正整数\(n, ...

  4. dataguard类型转换与模式转化

    修改数据保护模式步骤 前提:是否满足转换模式的配置要求 最大保护(Maximum Protection):Standby Database 必须配置Standby Redo Log,Primary D ...

  5. 11g dataguard 类型、保护模式、服务

    一. Dataguard中的备库分为物理备库和逻辑备库及快照备库 备库是主库的一致性拷贝,使用一个主库的备份可以创建多到30个备库,将其加入到dataguard环境中,创建成功后,dataguard通 ...

  6. 四 Vue学习 router学习

    index.js: 按需加载组件: const login = r => require.ensure([], () => r(require('@/page/login')), 'log ...

  7. Python pip 报错

    1,pip ssl certification ssl: certificate_verify_failed... 2,Could not find a version that satisfies ...

  8. Openstack web 添加和删除按钮

    注:当前已经时候用smaba将openstack环境的源码共享到windows系统上,并使用pycharm进行代码编辑和修改(参见openstack开发环境搭建).如下图:

  9. emacs for OCaml

    (require 'cl) (require 'package) (add-to-list 'package-archives '("melpa" . "https:// ...

  10. Centos7 安装vim8

    一.卸载旧版本的vim yum -y remove vim* 二.安装终端字符处理库ncurses yum -y install ncurses-devel 三.下载Vim8 wget https:/ ...