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 QQ Sakura SEO shadowrocket Spring Boot SpringMVC SSR Thymeleaf V2ray Vue Web WebSocket Wechat Social WordPress Yoast SEO 代理 分页 图床 小幸运 苹果iOS国外账号 苹果IOS账号 通信原理

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