4 内存分配和回收策略

莱莱  金牌会员 | 2022-10-17 22:18:43 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 876|帖子 876|积分 2628

目录

扩展:Gc日志分析工具
GC日志分析工具-GCEasy
GC日志分析神器-GCEasy详解
1 自动内存管理

Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:分配与回收

  • 自动给对象分配内存
  • 自动回收分配给对象的内存
Java的对象内存分配的一般规则:

  • 一般情况下,给对象分配堆内存;即时编译下,也会间接地分配栈内存
  • 新生对象通常会分配在新生代中,少数情况下(例如对象大小超过一定阈值)也可能会直接分配在老年代。
  • 《Java虚拟机规范》并未规定新对象的创建和存储细节,这取决于虚拟机当前使用的是哪一种垃圾收集器,以及虚拟机中与内存相关的参数的设定。
本章节所以用的JVM环境为:
  1. JDK8默认:
  2.     垃圾收集器:
  3.         新生代使用 Parallel Scavenge:标记-复制算法
  4.         老年代配合使用的是 Serial Old:标记-整理算法
复制代码
2 对象优先在Eden分配

2.1 总结


  • 新生代分为一个Eden区和两个survivor区,默认比例为是 8:1:1。(设置参数-XX:-UseAdaptiveSizePolicy可固定该比例)
  • 对象优先被分配在eden区,eden区满了后会触发minor gc,把剩余存活的对象挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区。
  • 如果survivor区空间不足,通过担保机制挪动到老年代
2.2 验证过程

jvm参数
  1. -Xms30M -Xmx30M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-UseAdaptiveSizePolicy -XX:+PrintCommandLineFlags -XX:+PrintGCDateStamps
  2. -Xms30M    #固定堆内存大小为30M
  3. -Xmx30M
  4. -Xmn10M   #新生代为分配10M,实际可用为9M
  5. -XX:+PrintGCDetails
  6. -XX:SurvivorRatio=8
  7. -XX:-UseAdaptiveSizePolicy  #固定8:1:1
  8. -XX:+PrintCommandLineFlags  #打印运行时jvm参数
  9. -XX:+PrintGCDateStamps
复制代码
特别注意:
未手动分配空间前,运行main方法占用:1476KB,即1.44MB

测试代码
  1. public class GcDemo {
  2.     public static void main(String[] args) {
  3.         byte[] a1 = new byte[5 * 1024 * 1024];
  4.         byte[] a2 = new byte[2 * 1024 * 1024];
  5.     }
  6. }
复制代码
GC日志及分析


  • [GC (Allocation Failure) :收集类型是:Minor GC (GC代表MinorGC,System.gc()代表Full GC)
  • [PSYoungGen: 6432K->640K(9216K)] 翻译为 [新生代:新生代收集前内存使用量->新生代垃圾收集后内存使用量(新生代代可用内存总大小)],垃圾收集前新生代已使用 6432K(6.28M),垃圾收集后已使用640K,总可用大小为 9216K(9M) 。

    • 6.28M:初始占用的1.44m+a1先被分配在eden区
    • a2分配时,eden剩余空间不足,触发MinorGC,此时有1476K-640K被回收,a1仍被使用,不被回收。
    • 9216K,MinorGC后,总共可用内存为eden+1个survivor区

  • MinorGC后内存占用情况:
  1.    PSYoungGen      total 9216K, used 2770K      #新生代已使用2770K包含:对象a2+剩余未回收的640K
  2.      eden space 8192K, 26% used     # eden区总大小8192K,已使用26%,包含对象a2
  3.      from space 1024K, 62% used   #S0总大小1024K,已使用62%,包含 剩余未回收的640K
  4.      to   space 1024K, 0% used      #S1总大小1024K,已使用0%
  5. ParOldGen       total 20480K, used 5128K   #对象a1被搬移到老年代
  6.     object space 20480K, 25%
复制代码
综上

  • 未分配任何对象时,系统默认占用了1476KB
  • 创建a1时,a1被分配在eden区
  • 创建a2时,eden区剩余空间不足,触发MinorGC,同时survivor空间不足以存放a1,所以通过分配担保机制将a1提前转移到老年代去。同时将回收了一部分默认占用的1476K,未被回收的640K存放在其中一个survivor
用图例所示

3 大对象直接进入老年代

3.1 总结


  • 在Java虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复 制对象时,大对象就意味着高额的内存复制开销。
  • HotSpot虚拟机提供了-XX:PretenureSizeThreshold 参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区 之间来回复制,产生大量的内存复制操作。
3.2 验证过程

如上:并未触发gc,而是直接分配在老年代
4 长期存活的对象将进入老年代

4.1 总结


  • 虚拟机给每个对象定义了一个对 象年龄(Age)计数器,存储在对象头中
  • 对象通常在Eden区里诞生,如果经过第一次 Minor GC后仍然存活,并且能被Survivor容纳的话,该对象会被移动到Survivor空间中,并且将其对象 年龄设为1岁
  • 对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程 度(默认为15),就会被晋升到老年代中。
  • 可以通过参数-XX: MaxTenuringThreshold 设置“年龄”最大值
4.2 验证过程

todo
5 动态对象年龄判定

5.1 总结


  • HotSpot虚拟机并不是永远要求对象的年龄必须达到- XX:MaxTenuringThreshold才能晋升老年代
  • 如果在Survivor空间中相同年龄所有对象大小的总和大于 Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX: MaxTenuringThreshold中要求的年龄。
5.2 验证过程

todo
6 空间分配担保

6.1 总结


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

todo

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莱莱

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表