更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680

1、对象优先在Eden分配

大多情况,对象在新生代Eden区分配。当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC。虚拟机提供了参数 -XX:+PrintGCDetails ,在虚拟机发生垃圾收集行为时打印内存回收日志。

新生代Minor GC 事例

定义了4个字节数组对象,3个2MB大小、1个4MB大小,

通过-Xms20M -Xmx20M -Xmn10M 三个参数限制了Java堆大小为 20M ,不可扩展,其中的 10MB 分配给新生代,剩下 10MB 分配给老年代

-XX:SurvivorRatio=8 新生代 Eden 与 Survivor 区空间比例是 8:1:1

  1. package com.lkf.jvm;
  2. public class MinorGCDemo {
  3. private static final int _1MB = 1024 * 1024;
  4. /**
  5. * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  6. * */
  7. public static void testAllocation() {
  8. byte[] allocation1, allocation2, allocation3, allocation4;
  9. allocation1 = new byte[2 * _1MB];
  10. allocation2 = new byte[2 * _1MB];
  11. allocation3 = new byte[2 * _1MB];
  12. allocation4 = new byte[4 * _1MB]; /出现一次 Minor GC
  13. }
  14. public static void main(String[] args) {
  15. testAllocation();
  16. }
  17. }

结果:

  1. [GC (Allocation Failure) Disconnected from the target VM, address: '127.0.0.1:61454', transport: 'socket'
  2. [PSYoungGen: 6570K->704K(9216K)] 6570K->4808K(19456K), 0.0036571 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  3. Heap
  4. PSYoungGen total 9216K, used 7253K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  5. eden space 8192K, 79% used [0x00000007bf600000,0x00000007bfc657a0,0x00000007bfe00000)
  6. from space 1024K, 68% used [0x00000007bfe00000,0x00000007bfeb0000,0x00000007bff00000)
  7. to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  8. ParOldGen total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  9. object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)
  10. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  11. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

从输出结果可以清晰看到 “eden space 8192K、from space 1024K、to space 1024K”
新生代总可用空间为 9216KB (Eden区空间大小 + 1个Survivor区的总容量)

这次GC发生的原因是给allocation4对象分配内存的时候,发现Eden区已经被占用了6MB,剩余空间已经不足以分配4MB的内存,因此发生了MinorGC。GC期间有发现已有的3个2MB大小的对象已经无法全部放入Survivor空间(只有1MB大小),所以只好通过分配担保机制提前将这三个对象转移到老年代去了。

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

所谓大对象是指,需要大量连续内存空间的Java对象,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来为大对象分配内存。

虚拟机提供了一个-XX:PretenureSizeThreshold 参数,让大于该值得对象直接进入老年代。这样做的目的是避免在新生代Eden区及两个Survivor区之间发生大量的内存复制。

PretenureSieThreshold 参数只对 Serial 和 ParNew 两款收集器有效,Parallel Scavenge 收集器不识别这个参数,并且该收集器一般不需要设置。如果必须使用此参数的场合,可以考虑ParNew加CMS的收集器组合。

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

-XX:+PrintCommandLineFlags 可查看默认设置收集器类型

-XX:+PrintGCDetails 打印的GC日志

  1. package com.lkf.jvm;
  2. public class PretenureSizeThresholdDemo {
  3. private static final int _1MB = 1024 * 1024;
  4. /**
  5. * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  6. * -XX:+PrintCommandLineFlags -XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC
  7. * 因为使用的是jdk1.8,所以此处特指定了使用垃圾收集器Serial
  8. * 大于3M的对象直接进入老年代
  9. */
  10. public static void testPretenureSizeThreshold() {
  11. byte[] allocation;
  12. allocation = new byte[4 * _1MB];//直接分配在老年代
  13. }
  14. public static void main(String[] args) {
  15. testPretenureSizeThreshold();
  16. }
  17. }

结果:

  1. Heap
  2. def new generation total 9216K, used 2643K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  3. eden space 8192K, 32% used [0x00000007bec00000, 0x00000007bee94ee8, 0x00000007bf400000)
  4. from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  5. to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
  6. tenured generation total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  7. the space 10240K, 40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
  8. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  9. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

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

虚拟机使用了分代收集的思想来管理内存,内存回收时为了区分哪些对象应放在新生代,哪些应该放在老年代,虚拟机为每个对象定义了一个对象年龄(Age)计数器。

如果对象被分配在Eden区并经过第一次Minor GC 后仍然存活,并且能被Survivor容乃的情况下,将被移动到Survivor中,对象年龄设为1。在Survivor区每经过一次Minor GC,年龄就加1,当对象的年龄到达一定程度时(默认15岁),就会晋升到老年代。对象晋升到老年代的阈值,可以通过参数:-XX:MaxTenuringThreshold 设置。

  1. package com.lkf.jvm;
  2. /**
  3. * 长期存活对象将进入老年代
  4. */
  5. public class MaxTenuringThresholdDemo {
  6. private static final int _1MB = 1024 * 1024;
  7. /**
  8. * VM 参 数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
  9. * */
  10. public static void testTenuringThreshold() {
  11. byte[] allocation1, allocation2, allocation3;
  12. allocation1 = new byte[_1MB / 4];
  13. //什么时候进入老年代取决于XX:MaxTenuringThreshold设置
  14. allocation2 = new byte[4 * _1MB];
  15. allocation3 = new byte[4 * _1MB];
  16. allocation3 = null;
  17. allocation3 = new byte[4 * _1MB];
  18. }
  19. public static void main(String[] args) {
  20. testTenuringThreshold();
  21. }
  22. }

-XX:MaxTenuringThreshold=1 (jdk1.8)运行结果:

  1. Heap
  2. PSYoungGen total 9216K, used 6994K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  3. eden space 8192K, 85% used [0x00000007bf600000,0x00000007bfcd4b68,0x00000007bfe00000)
  4. from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  5. to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  6. ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  7. object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)
  8. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  9. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

-XX:MaxTenuringThreshold=15 (jdk1.8)运行结果:

  1. Heap
  2. PSYoungGen total 9216K, used 6994K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  3. eden space 8192K, 85% used [0x00000007bf600000,0x00000007bfcd4b68,0x00000007bfe00000)
  4. from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  5. to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  6. ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  7. object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)
  8. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  9. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

4、动态对象年龄判定

虚拟机并不是永远要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果Survivor空间中,相同年龄对象的大小之和大于Survivor空间大小的一半,就可以直接进入老年代。

  1. package com.lkf.jvm;
  2. /**
  3. * 动态对象年龄判定
  4. */
  5. public class TenuringThresholdDemo {
  6. private static final int _1MB = 1024 * 1024;
  7. /**
  8. * VM 参 数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution -XX:+UseSerialGC
  9. * */
  10. public static void testTenuringThreshold() {
  11. byte[] allocation1, allocation2, allocation3, allocation4;
  12. allocation1 = new byte[_1MB / 4];
  13. allocation2 = new byte[_1MB / 4];
  14. //allocation1+allocation2大于Survivo空间的一半
  15. allocation3 = new byte[4 * _1MB];
  16. allocation4 = new byte[4 * _1MB];
  17. allocation4 = null;
  18. allocation4 = new byte[4 * _1MB];
  19. }
  20. public static void main(String[] args) {
  21. testTenuringThreshold();
  22. }
  23. }

输出结果:

  1. [GC (Allocation Failure) [DefNew
  2. Desired survivor size 524288 bytes, new threshold 1 (max 15)
  3. - age 1: 1048568 bytes, 1048568 total
  4. : 7087K->1023K(9216K), 0.0048599 secs] 7087K->5179K(19456K), 0.0048867 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
  5. [GC (Allocation Failure) [DefNew
  6. Desired survivor size 524288 bytes, new threshold 15 (max 15)
  7. : 5283K->0K(9216K), 0.0018311 secs] 9439K->5148K(19456K), 0.0018528 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
  8. Heap
  9. def new generation total 9216K, used 4260K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  10. eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf0290e0, 0x00000007bf400000)
  11. from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  12. to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
  13. tenured generation total 10240K, used 5148K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  14. the space 10240K, 50% used [0x00000007bf600000, 0x00000007bfb07198, 0x00000007bfb07200, 0x00000007c0000000)
  15. Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768K
  16. class space used 338K, capacity 392K, committed 512K, reserved 1048576K

5、空间分配担保

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

JDK 6 Update 24 之后,HandlePromotionFailure参数不会再影响到虚拟机空间分配担保的策略,规则变为只要老年代的连续空间大于新生代对象总大小或者大于历次晋升对象大小的平均值就会进行Minor GC ,否则将进行Full GC。

Minor GC 和 Full GC的区别

    • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都有朝生夕死的特性,所以Minor GC 很频繁,一般回收速度也很快。

    • 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC ,经常会伴随至少一次的Minor GC。Major GC 的速度一般会比Minor GC 慢10倍。
      更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
      原文链接:https://www.cnblogs.com/liukaifeng/p/10052626.html

深入理解JAVA虚拟机原理之内存分配策略(二)的更多相关文章

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

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

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

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

  3. 深入理解java虚拟机---垃圾收集器和分配策略-1

    博文重点: 学习目标:哪些内存需要回收 什么时候回收    如何回收 在基于概念讨论的模型中,主要对Java堆和方法区进行讨论. why?:一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个 ...

  4. 深入理解Java虚拟机(自动内存管理机制)

    文章首发于公众号:BaronTalk 书籍真的是常读常新,古人说「书读百遍其义自见」还是很有道理的.周志明老师的这本<深入理解 Java 虚拟机>我细读了不下三遍,每一次阅读都有新的收获, ...

  5. Java虚拟机创建对象的内存分配以及对象的内存布局

    本博文知识参考周志明<深入理解Java虚拟机> Java虚拟机在创建对象使如果进行内存分配: 1.指针碰撞 2.空闲列表 Java在多线程情况下创建对象的内存分配: Java完成对象内存分 ...

  6. 深入理解JVM(5)——垃圾收集和内存分配策略

    1.垃圾收集对象 垃圾收集主要是针对堆和方法区进行. 程序计数器.虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收. 哪 ...

  7. 深入理解JAVA虚拟机原理之Dalvik虚拟机(三)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 本文是Android虚拟机系列文章的第三篇,专门介绍Andorid系统上曾经使用 ...

  8. Java虚拟机7:内存分配原则

    前言 对象的内存分配,往大的方向上讲,就是在堆上分配,少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节决定于当前使用的是哪种垃圾收集器组合,当然还有虚拟机中与内存相关的参数 ...

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

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

随机推荐

  1. ORACLE切非归档模式:

    C:\Documents and Settings\Administrator>sqlplus /nologSQL> conn / as sysdbaConnected to an idl ...

  2. 14.Jmeter聚合报告各项含义

    Aggregate Report 是 JMeter 常用的一个 Listener,中文为“聚合报告” Label:每个 JMeter 的 element(例如 HTTP Request)都有一个 Na ...

  3. jwt认证登录

    配置文件:#服务配置 server: port: 9002 #spring配置 spring: #应用配置 application: name: ynhrm-system #指定服务名 #数据库连接池 ...

  4. promise的基本用法

    // Promise 对象,可以保存状态 //Promise 第一步 // 异步代码 写在 Promise的函数中 第二步 const promise = new Promise((resolve, ...

  5. 从输入url到显示网页,发生了那些事情?

    作为一个软件开发者,你一定会对网络应用如何工作有一个完整的层次化的认知,同样这里也包括这些应用所用到的技术:像浏览器,HTTP,HTML,网络服务器,需求处理等等. 本文将更深入的研究当你输入一个网址 ...

  6. python base64编码实现

    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" def encode(b ...

  7. Python 基础 4-1 字典入门

    引言 字典 是Python 内置的一种数据结构,它便于语义化表达一些结构数据,字典是开发中常用的一种数据结构 字典介绍 字典使用花括号 {} 或 dict 来创建,字典是可以嵌套使用的 字典是成对出现 ...

  8. Ansible批量自动化管理工具(二)

    Ansible批量自动化管理工具(二) 链接:https://pan.baidu.com/s/1A3Iq3gGkGS27L_Gt37_I0g 提取码:ncy2 复制这段内容后打开百度网盘手机App,操 ...

  9. MHA-Atlas-MySQL高可用(下)

    MHA-Atlas-MySQL高可用(下) 链接:https://pan.baidu.com/s/17Av92KQnJ81Gc0EmxSO7gA 提取码:a8mq 复制这段内容后打开百度网盘手机App ...

  10. SSD网络结构

    SSD算法,其英文全名是Single Shot MultiBox Detector. SSD的网络结构流程如下图所示:SSD总共11个block,相比较于之前的VGG16,改变了第5个block的第4 ...