文章目录

  • 思维导图
  • 对象优先在eden区域分配
    • 理论
    • 案例
      • 虚拟机参数设置及参数说明
      • 代码
      • GC结果分析
  • 大对象直接进入老年代
    • 理论
    • 案例
      • 虚拟机参数设置及参数说明
      • 代码
      • GC日志
  • 长期存活的对象将进入老年代
    • 理论
    • 案例
      • 虚拟机参数设置及参数说明
      • 代码
      • XX:MaxTenuringThreshold=1时的 GC日志
      • XX:MaxTenuringThreshold=15时的 GC日志
  • 动态对象年龄判定
    • 理论
    • 案例
      • 虚拟机参数设置及参数说明
      • 代码
      • GC日志
  • 空间分配担保
    • 理论

思维导图


对象优先在eden区域分配

理论

大多数情况下,对象在新生代的eden区中分配,当eden区没有足够的空间进行分配时,虚拟机将进行一次Minor GC。

虚拟机提供-XX:+PrintGCDetails参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况。

实际应用中,GC日志一般都是输出到文件中,使用GC日志分析工具来进行分析。


案例

虚拟机参数设置及参数说明

JDK1.6

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

  • -Xms20M 初始化堆内存 20M

  • -Xmx20M 最大堆内存20M,结合-Xms20M 即为堆内存不可扩展

  • -Xmn10M 新生代内存分配10M,结合-Xms -Xmx 可知 老年代也是10M

  • -XX:SurvivorRatio=8 默认值,可不配置。 新生代中Eden区与一个Survivor区的比例为8:1,即 Eden: from Survivor:to Survivor = 8:1:1,即8MB:1MB:1MB,新生代的可用空间为9MB。

  • -XX:+UseSerialGC 指定年轻代使用Serial垃圾收集器

  • -verbose:gc 和 -XX:+PrintGCDetails 发生垃圾回收时,打印GC日志


代码

package com.artisan.gc;public class EdenAllocationGC {private int _1M = 1024 * 1024;/*** * @Title: testGCAllocation* * @Description: -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8*               -XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails* * @return: void*/@SuppressWarnings("unused")private void testGCAllocation() {// 如下的分配,仅仅是为了占用些内存空间,方便观察GC回收情况byte[] object1 = new byte[2 * _1M];byte[] object2 = new byte[2 * _1M];byte[] object3 = new byte[2 * _1M];byte[] object4 = new byte[4 * _1M];}public static void main(String[] args) {new EdenAllocationGC().testGCAllocation();}}

GC结果分析

[GC [DefNew: 6487K->159K(9216K), 0.0052344 secs] 6487K->6303K(19456K), 0.0052735 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heapdef new generation   total 9216K, used 4582K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)eden space 8192K,  54% used [0x00000000f9a00000, 0x00000000f9e51f98, 0x00000000fa200000)from space 1024K,  15% used [0x00000000fa300000, 0x00000000fa327c28, 0x00000000fa400000)to   space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)tenured generation   total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)the space 10240K,  60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)compacting perm gen  total 21248K, used 2995K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)the space 21248K,  14% used [0x00000000fae00000, 0x00000000fb0eccf8, 0x00000000fb0ece00, 0x00000000fc2c0000)
No shared spaces configured.

因为内容较少可以直接阅读 。 当然了也可以传到http://gceasy.io 在线生成一份GC分析报告。

这里我们直接来分析下这个GC日志

结合JVM参数的设置,通过GC日志的验证,符合设置。

def new generation   total 9216K ......
eden space 8192K ......
from space 1024K ......
to   space 1024K ......

def new generation 是通过-XX:+UseSerialGC指定的垃圾回收器,显示名称是由收集器决定的。

  • 如果是用的Serial收集器,新生代名为“Default New Generation”,所以显示“[DefNew”。
  • 如果是用的ParNew收集器,新生代名为“Parallel New Generation”,所以显示“[ParNew”。
  • 如果是用的Parallel Scavenge收集器,新生代名则显示为“[PSYongGen”
-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8

新生代分配了10M,又因为SurvivorRatio = 8 。 所以 Eden: from Survivor:to Survivor = 8192K:1024K :1024K


通过new创建对象的方式,虚拟机会将对象的实例分配到堆内存中,具体的说是分配object1 、object2 、object3 三个对象到 Eden区+Survivor From,3个对象占6MB空间,而 Eden + Survivor From 的大小为9M,空间足够,优先分配到Eden区。 所以Eden区的内存被占用6M

分配object4时的时候,发现Eden+Survivor From剩余空间只有3M,而object4占用4M的内存,这个时候就会触发一次Minor GC ,输出的信息如下

[GC [DefNew: 6487K->159K(9216K), 0.0052344 secs] 6487K->6303K(19456K), 0.0052735 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

可以看到6487K->159K(9216K) ,新生代的内存由6487K变为了159K。 而堆内存 6487K->6303K基本没有发生变化,是因为 object1,object2,object3都是存活的对象,无法被GC回收。

GC期间又发现已有的3个2MB的对象都无法放入Survivor To空间(1MB),所以通过担保机制提前转移到老年代区(3个2MB的对象),此时Eden区恢复到8MB空间,然后将object4分配到Eden空间。

GC结束后,4M的object4被顺利的分配到了Eden区中,Survivor空闲。 老年代 tenured generation占用6M(object1,object2,object3占用)。 通过如下日志也可以眼睁这个结论

tenured generation   total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)the space 10240K,  60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)

大对象直接进入老年代

理论

需要大量连续内存空的Java对象,一般称之为大对象。

PretenureSizeThreshold参数,可以在新生代直接分配的对象最大值,0表示没有最大值 。 可以使大于这个值的对象直接在老年代分配,避免在Eden区和Survivor区发生大量的内存复制,该参数只对Serial和ParNew收集器有效,Parallel Scavenge并不认识该参数

使用方法:-XX:PretenureSizeThreshold=1000000


案例

虚拟机参数设置及参数说明

JDK1.6

-Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+UseSerialGC  -verbose:gc -XX:+PrintGCDetails -XX:PretenureSizeThreshold=3145728
  • -XX:PretenureSizeThreshold=3145728 : 单位是byte, 3145728 = 3M,大于3M的对象直接在老年代分配,避免在Eden区和Survivor区发生大量的内存复制.

代码

package com.artisan.gc;public class PretenureSizeThresholdTest {private int _1M = 1024 * 1024;/*** * @Title: testPretenureSizeThreshold* * @Description: -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8*               -XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails*               -XX:PretenureSizeThreshold=3145728* * @return: void*/@SuppressWarnings("unused")private void testPretenureSizeThreshold() {// 如下的分配,仅仅是为了占用些内存空间,方便观察GC回收情况byte[] object4 = new byte[4 * _1M];}public static void main(String[] args) {new PretenureSizeThresholdTest().testPretenureSizeThreshold();}
}

GC日志

JDK1.6

Heapdef new generation   total 9216K, used 507K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)eden space 8192K,   6% used [0x00000000f9a00000, 0x00000000f9a7ee98, 0x00000000fa200000)from space 1024K,   0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)to   space 1024K,   0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)tenured generation   total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)the space 10240K,  40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000)compacting perm gen  total 21248K, used 2985K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)the space 21248K,  14% used [0x00000000fae00000, 0x00000000fb0ea690, 0x00000000fb0ea800, 0x00000000fc2c0000)
No shared spaces configured.

可以看到这里并没有发生Minor GC ,仅仅是打印了堆内存信息。 通过-XX:PretenureSizeThreshold=3145728的设置,4M大小的object4 大于设置的3M阀值,直接分配到了老年代。

 tenured generation   total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)the space 10240K,  40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000)

长期存活的对象将进入老年代


理论

现在商用虚拟机都采用分代收集的思想来管理内存,那么内存回收就必须能识别哪些对象应该放在新生代,哪些对象应该放在老年代。

为了做到这一点,虚拟机给每个对象定义了一个对象年龄计数器。 如果对象在Eden畜生并经过第一次Minor GC后仍然存活,并且能够被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设置为1。 对象在Survivor区中没经历过一次Minor GC且存活下来,年龄就增加1岁。当它的年龄增加到一定程度(默认15岁),就将会被晋升到老年代中。

对象晋升老年代的年龄可以通过-XX:MaxTenuringThreshold设置


案例

我们分别将-XX:MaxTenuringThreshold=1-XX:MaxTenuringThreshold=15 来看下GC日志的区别。


虚拟机参数设置及参数说明

JDK1.6

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

结合这个虚拟机参数设置,我们来构造符合预期的数据

-Xms20M -Xmx20M:java堆内存初始化值和最大值均为20M,不可扩展。
-Xmn10M :同时给新生代分配10M内存,可以推算出老年代也是20-10=10M
通过-XX:SurvivorRatio=8可知,新生代中Eden : Survivor From : Survivor To = 8:1:1 ,所以新生代能用的最大的内存为9M。
通过-XX:MaxTenuringThreshold设置对象在新生代存活的最大年龄。

根据Eden : Survivor From : Survivor To = 8:1:1 来构造对象的大小 。 一个256KB的对象,确保在不符合MaxTenuringThreshold的情况下,Survivor To 区能够有足够的空间存放这个256KB的对象。


代码

package com.artisan.gc;public class MaxTenuringThresholdTest {private static final int _1M = 1024 * 1024;/*** * * @Title: testMaxTenuringThreshold* * @Description: -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8*               -XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails*               -XX:MaxTenuringThreshold=1* * * @return: void*/public void testMaxTenuringThreshold() {// 根据JVM参数的设置,分配合理的大小,达到测试的目的byte[] object1 = new byte[_1M / 4];byte[] object2 = new byte[_1M * 4];// 什么时候进入老年代取决于-XX:MaxTenuringThresholdbyte[] object3 = new byte[_1M * 4];object3 = null;byte[] object4 = new byte[_1M * 4];}public static void main(String[] args) {new MaxTenuringThresholdTest().testMaxTenuringThreshold();}}

XX:MaxTenuringThreshold=1时的 GC日志

堆内存新生代可用空间为9M, 首先在堆内存中分配了object1 256KB的内存,紧接着分配了object2 占用4M内存空间,此时新生代中还剩余8M-( 256KB+4M ) 的内存空间, object3 占用一个4M的内存空间,空间已经不够,提前触发了一次Minor GC

[GC [DefNew: 4695K->415K(9216K), 0.0056811 secs] 4695K->4511K(19456K), 0.0057238 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

发生Minor GC,Survivor From 只有1M的空间可用,也不够存放object2, 所以出发了担保机制,将该对象放到了老年代(10M),可以存放的下4M的object2 。但object1只有256KB,所以Survivor From可以存放的下object1。 同时 MaxTenuringThreshold变为1 。

将object3置为null(这个时候已经没有引用,对象已经死亡,GC可以回收),分配object4 的时候又触发了一次Minor GC 。 此时object1已经达到了MaxTenuringThreshold,符合清理到老年代的要求,可以看到新生代from space已经被清为0了。

from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)

XX:MaxTenuringThreshold=15时的 GC日志

from space 使用了40% ,存放的是object1对象,因没达到XX:MaxTenuringThreshold,暂时还没有清理到老年代。


动态对象年龄判定

理论

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

案例

虚拟机参数设置及参数说明

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

代码

package com.artisan.gc;public class MaxTenuringThresholdTest {private static final int _1M = 1024 * 1024;/*** * * @Title: testMaxTenuringThreshold* * @Description: -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8*               -XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails*               -XX:MaxTenuringThreshold=15* * * @return: void*/public void testMaxTenuringThreshold() {// 根据JVM参数的设置,分配合理的大小,达到测试的目的byte[] object1 = new byte[_1M / 4];byte[] object2 = new byte[_1M / 4];// 什么时候进入老年代取决于-XX:MaxTenuringThresholdbyte[] object3 = new byte[_1M * 4];byte[] object4 = new byte[_1M * 4];object4 = null;object4 = new byte[_1M * 4];}public static void main(String[] args) {new MaxTenuringThresholdTest().testMaxTenuringThreshold();}}

GC日志

设置了MaxTenuringThreshold=15,会发现运行结果中Survivor的空间占用仍然为0%,而老年代比预期增加了6%【和上个案例的结果比对】,也就是说,object1、object2对象都直接进入了老年代,而没有等到15岁的临界年龄。因为这两个对象加起来已经到达了512KB,并且它们是同年的,满足同年对象达到Survivor空间的一半规则。

如果我们只要注释掉其中一个对象new操作,就会发现另外一个就不会晋升到老年代中去了


空间分配担保


理论

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。

如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。

如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。

JDK1.60 Update24之后HandlePromotionFailure参数不会影响虚拟机的空间分配担保策略了。

JVM-09自动内存管理机制【内存分配和回收策略】相关推荐

  1. Android进阶——性能优化之内存管理机制和垃圾采集回收机制(六)

    文章大纲 引言 一.内存泄漏和内存溢出概述 二.Java运行时内存模型 1.线程私有数据区 1.1.程序计数器PC 1.2.虚拟机栈 1.3 本地方法栈 2.所有线程共享数据区 2.1.Java堆 2 ...

  2. java中创建类的时候有没有分配内存,你必须了解的java内存管理机制(一)-内存分配...

    前言 在上一篇文章中,我们花了较大的篇幅去介绍了JVM的运行时数据区,并且重点介绍了栈区的结构及作用,相关内容请猛戳!在本文中,我们将主要介绍对象的创建过程及在堆中的分配方式.from 你必须了解的j ...

  3. 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件

    本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用:根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制. 本 ...

  4. python内存管理机制_python内存管理机制

    python内存管理机制: 引用计数 垃圾回收(引用计数,标记清除,分带回收) 内存池 1. 引用计数 当一个python对象被引用时 其引用计数增加 1 ; 当其不再被变量引用时 引用计数减 1 ; ...

  5. 【Linux】内存管理机制

    Linux内存管理机制 内存的表象层次依次为 逻辑地址------>线性地址----->物理地址 逻辑地址经过段机制转换成线性地址. 线性地址经过页机制转换成物理地址. Linux将所有程 ...

  6. 内存分段分页机制理解_深入理解虚拟机,JVM高级特性-自动内存管理机制

    什么是自动内存管理机制? 对于java程序员来说,有一点是要比C/C++程序员要方便的,那就是程序在运行时,java程序不需要为每一个对象其编写对应的释放内存的代码,JVM虚拟机将为你在合适的时间去释 ...

  7. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  8. JVM内存管理机制线上问题排查

    本文主要基于"深入java虚拟机"这本书总结JVM的内存管理机制,并总结了常见的线上问题分析思路.文章最后面是我对线上故障思考的ppt总结. Java内存区域 虚拟机运行时数据区如 ...

  9. JVM的内存管理机制详解

    一.为什么要学习内存管理? Java与C++之间有一堵由内存动态分配和垃圾回收机制所围成的高墙,墙外面的人想进去,墙里面的人出不来 对于Java程序员来说,JVM给我们提供了自动内存管理机制,不需要既 ...

  10. JVM内存管理机制和垃圾回收机制

    JVM内存管理机制和垃圾回收机制 JVM结构 图片描述: java源码编译成class文件 class文件通过类加载器加载到内存 其中方法区存放的是运行时的常量.静态变量.类信息等,被所有线程共享 堆 ...

最新文章

  1. 包含多个段的程序01 - 零基础入门学习汇编语言29
  2. python的优缺点有哪些-那么多人选择Python,它的优缺点有哪些?
  3. Python常见问题(7):Python图形用户接口 Graphic User Interface FAQ
  4. 设计模式C++实现(16)——状态模式
  5. 【小练习02】CSS--网易产品
  6. python列表教程:多个数列合并,合并后取值的方法
  7. mysql数据库访问编程,mysql 连接数据库
  8. malloc和free
  9. windows server2008服务器文件上传受限制怎么办,windows server 2008 服务器上传限制
  10. RandomAccessFile 随机存取文件任意位置数据
  11. mysql优化之insert/delete/update
  12. android view
  13. 数据结构与算法分析——Hash表
  14. JVM(十) - 性能调优
  15. 数通学习网络协议地图
  16. 无捆绑绿色安装金山打字通教程
  17. 2018PS cc版本最新最实用学习笔记
  18. 数据科学家:大器晚成,是软件工程师成功的唯一捷径
  19. 拍频和混频 PD拍频
  20. C语言-make概述

热门文章

  1. 数字图像处理——第三章 空间域图像增强(空间滤波)
  2. linux 编程博客,Linux系统编程博客参考
  3. 红米note5解锁教程_红米NOTE5如何一键解锁?刷机教程图解
  4. MATLAB应用实战系列(七十七)-【图像处理】COVID-19 防疫应用口罩检测
  5. mysql连接查询之间的差异性
  6. 目标检测(Object Detection)原理与实现
  7. Matlab读取avi视频并播放
  8. Hadoop学习之MapReduce(四)
  9. java swing面试题_Java面试题之AWT、Swing
  10. linux不保存退出命令_面试提问说出16个linux命令,能凑齐不!高频22个Linux命令在这里...