JVM内存结构中的堆 :一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。堆内存的大小是可以调节的。《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑.上它应该被视为连续的。

1.堆(Heap)概述

  1. 一个JVM实例只存在-一个堆内存,堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。
  2. 堆内存的大小是可以调节的。
  3. 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑.上它应该被视为连续的。
  4. 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(ThreadLocal Allocation Buffer, TLAB) 。
  5. 《Java 虚拟机规范》中对Java堆的描述是::“几乎”所有的对象实例以及数组都应在运行时分配到堆上面。
  6. 在方法结束后,堆中的对象不会立即被清除,仅仅在垃圾回收GC时才会被清除。
  7. 堆(Heap)是GC执行垃圾回收的(Garbage Collection ,垃圾回收)的重点区域。

2.堆结内部构(一)

JVM内存结构中的堆
JVM内存结构中的堆

JDK7和之前堆内存逻辑上分为三部分:新生区+老年区+永久区

Young Generation Space   新生区   Young/new
Tenure generation space  老年区   Old/Tenure
Permanent Space          永久区   Perm

JDK8和之后堆内存逻辑上分为三部分:新生区+老年区+元空间

Young Generation Space   新生区   Young/new
Tenure generation space  老年区   Old/Tenure
MateSpace          元空间    Perm

注意: 虽然将永久区/元空间划分到了堆中,实际上我们分配的堆内存只对新生区、老年区有效。

3.堆结内部构(二)

上面是按照大的分类来区分, Java虚拟机堆区细分可以分为新生代和老年代,而其中又可以细分为Eden(伊甸园区)、Survivor0(幸存区0)、Survivor1(幸存区1),有时候也被叫From、To区。

JVM内存结构中的堆
JVM内存结构中的堆

各部分默认所占比例:

  1. 新生区:老年区=1:2
  2. 伊甸园区:Surviver1:Surviver2=8:1:1

4.新生代和老年代参数设置

  1. -XX:NewRatio=2 :设置新生区和老年区的比值为2;新生区和老年区默认大小比值为2,一般不会修改这个比值。
  2. -XX:SurvivorRatio=8 :设置新生区和幸存区的比值为8(HotSpot默认也是8),由于有一个自适应机制,可能比例会显示异常。
  3. -XX=-UserAdaptivesSizePolicy:-代表关闭,即关闭自适应机制,就严格按照设置的比例进行分配。

关闭自适应使用默认的比值,可能还是比例异常,就需要自己去手动添加配置-XX:SurvivorRatio=8,即手动添加配置,显式设置而不是为空的采用虚拟机默认配置

5.对象在堆区的分配过程

①每次Eden区在Young GC之后存活下来就进入空的Survivor区(To/S0区),然后Survivor1(From区)和Survivor0区(TO区交换),即S0->S1,S1->S0。S0(To区)总是空着的那一块。

每次Eden区在Young GC之后存活下来就进入空的Survivor区(To/S0区),然后Survivor1(From区)和Survivor0区(TO区交换),即S0->S1,S1->S0。S0(To区)总是空着的那一块。

 ②每次YGC之后,Eden区、S0区存活对象的进入S1,然后S0、S1互换,一直这样循环

每次YGC之后,Eden区、S0区存活对象的进入S1,然后S0、S1互换,一直这样循环

 ③新生区对象年龄计数器 大于15 就会晋升(Promotion)到老年区,这个年龄计数器也只对新生区对象有效,进入老年区的对象不考虑年龄。

新生区对象年龄计数器 大于15 就会晋升(Promotion)到老年区,这个年龄计数器也只对新生区对象有效,进入老年区的对象不考虑年龄。

注意:Young/Minor  GC 只会在Eden区满了之后触发,Survivor区满是不会触发YGC的。YGC会同时清理Eden区和Survivor区。

下面是比较完整的流程图

JVM内存结构中的堆下面是比较完整的流程图

6.堆内存分配策略

  1. 优先分配到Eden区
  2. 大对象直接分配到老年区(程序中应该避免出现较多大对象)
  3. 长期存活的对象分配到老年区
  4. 空间分配担保:-XX: HandlePromotionFailure
  5. 动态对象年龄判断:Survivor区中相同年龄对象的总和大于Survivor区空间的一半(另一半总是为空,即S区已经放不下了),这时候大于或等于改年龄的对象无需等到晋升(Promotion)的阈值年龄就可以进入老年区。

7.TLAB

什么是TLAB?

  1. 从内存模型而不是垃圾收集的角度,对Eden区 域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
  2. 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。
  3. 在程序中,开发人员可以通过选项“-XX:UseTLAB”设置是否开启TLAB空间默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1号,当然我们可以通过选项“-XX:TLABWasteTargetPercent"设置TLAB空间所占用Eden空间的百分比大小。
  4. 一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。
JVM内存结构中的堆

为什么要使用TLAB?

  1. 区是线程共享区域,任何线程都可以访问到堆区中的数据
  2. 由于对象的创建在JVM中是很频繁的,因此在并发环境下从堆空间中分配划分内存空间是不安全的
  3. 为了保证线程安全,使用加锁机制,但是会影响分配速度。

TLAB对象分配过程

JVM内存结构中的堆TLAB对象分配过程

8.JVM堆中常用参数设置

  1. -XX: +PrintflagsInitial :查看所有的参数的默认初始值
  2. -XX:+PrintFlagsFinal : 查看所有的参数的最终值(可能会存在修改,不再是初始值)
  3. -Xms:初始堆空间内存(默认为物理内 存的1/64)
  4. -Xmx:最大堆空间内存(默认为物理内存的1/4)
  5. -Xmn:设置新生代的大小。(初始值及最大值)
  6. -XX:NewRatio:配置新生代与老年代在堆结构的占比
  7. -XX:UseAdaptiveSizePolicy:是否使用默认分配策略
  8. -XX:SurvivorRatio:设置新生代中Eden和50/s1空间的比例
  9. -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄(默认值15)
  10. -XX:+PrintGCDetails:输出详细的GC处理日志
  11. -XX:HandlePromotionFailure:是否设置空间分配担保

标签云

ajax AOP Bootstrap cdn Chevereto CSS Docker Editormd GC Hexo IDEA IPA JavaScript jsDeliver JS樱花特效 JVM Linux markdown Maven MyBatis MyBatis-plus MySQL Pictures Sakura SEO shadowrocket Spring Boot Spring Cloud Spring Cloud Alibaba SpringMVC SSR Thymeleaf V2ray Vue Web WebSocket Wechat Social WordPress Yoast SEO 代理 分页 图床 小幸运 苹果iOS国外账号 苹果IOS账号

JVM内存结构中的堆
JVM内存结构中的堆
本文最后更新于2020年6月17日,已超过 3 个月没更新!