栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

第八章 堆

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

第八章 堆

1、堆的核心概述
  •   一个JVM实例只存在一个堆内存,堆也是内存管理的核心区域。
  • Java堆区在JVM启动的时候被创建,其空间大小也确定好了。堆是JVM管理的最大一块内存空间。(堆内存的大小是可以调节的,启动java程序时使用-Xms -Xmx参数即可)
  • 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上,它应该被视为连续的。
  • 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)
  • 《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。
  • 数组和对象可能永远不会存储在栈上,因为栈中只会保存引用,这个引用指向对象或者数组在堆中的位置。
  • 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
  • 堆,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
  • 现代垃圾收集器大部分都基于分代收集理论设计,堆空间可细分为:
    • Java7及之前堆内逻辑分为三个部分:新生区、养老区、永久区
    • Java8及之后内存逻辑分为三个部分:新生区、养老区、元空间
2、设置堆内存大小与OOM
  • Java堆区用于存储Java对象实例,那么堆的大小在启动JVM的时候就已经确定好了,我们也可以通过-Xms和-Xmx来进行设置堆空间(年轻代+老年代)
    • -Xms用于表示堆区的起始内存,等价于-XX:InitialHeapSize
    • -Xmx则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
  • 一旦堆区的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutOfMemory异常
  • 通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分配计算堆区的大小,从而提高性能。
  • 默认情况下,初始内存大小:物理内存大小/64、最大内存大小:物理内存大小/4
  • 堆内存查看方式:
    • jps    |      jstat -gc pid
    • -XX:+PrintGCDetails
  • OOM的举例与说明:
3、年轻代和老年代

存储在JVM中的Java对象可以被划分为两类:

  • 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
  • 另一类对象的生命周期较长,在某些极端情况下还能够与JVM的生命周期保持一致。

  • JVM堆区进一步划分的话,还可以划分为年轻代(YoungGen)和老年代(OldGen)。其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区,to区)。
  • NewRatio:设置新生代和老年代的比例,默认值是2,即新生代:老年代 = 2
  • 通过 jinfo 来查看当前比例:
  • 在HotSpot中,Eden空间和另外Survivor空间缺省所占比例是8:1:1,开发人员可以通过选项“-XX:SurvivorRation”调整这个空间比例。
  • 几乎所有的Java对象都是在Eden区被new出来的。
  • 绝大部分的Java对象的销毁都在新生代进行
  • 可以使用选项“-Xmn”设置新生代最大内存大小。 
4、对象分配过程
  1. new的对象先放伊甸园区。此区有大小限制。
  2. 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区。并将伊甸园中的剩余对象移动到幸存者0区
  3. 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。触发垃圾回收后在伊甸园区幸存下来的对象也会放到1区
  4. 如果再次经历垃圾回收,此时会重新放回0区,接着再去幸存者1区。
  5. 经历15次转移的对象,会被放到养老区。也可以通过选项“-XX:MaxTenuringThread=”进行设置
5、Minor GC、Major GC、Full GC

JVM在进行GC时,并非每次都对上面三个内存区域(新生代、老年代、方法区)一起回收,大部分都是在新生代进行回收。

针对HotSpot VM的实现, GC按照回收区域又分为两大类型:部分收集(Partial GC)和整堆收集(Full GC)。

  • 部分收集:
    • 新生代收集(Minor GC / Young GC),只针对新生代进行垃圾收集
    • 老年代收集(Major GC / Old GC),只针对老年代的垃圾收集
      • 目前,只有CMS GC会有单独收集老年代的行为
      • 注意,很多时候Major GC 和 Full GC会混淆,具体分辨就看是老年代回收还是整堆回收
    • 混合收集(Mixed GC):收集整个新生代和部分老年代的垃圾回收
      • 目前只有G1 GC会有这种行为
  • 整堆收集:收集整个java堆和方法区的垃圾收集。

年轻代GC的触发机制:

  • 当年轻代空间不足时,就会触发Minor GC,这里的年轻代指的是Eden满,Survior满不会引发GC。
  • 因为Java中对象大多是朝生夕死的,所以Minor GC非常频繁,而且一般回收速度也较快。
  • Minor GC会触发STW,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行。

老年代GC触发机制:

  • 指发生在老年代的GC,对象从老年代消失时,我们说“Major GC”或“Full GC”发生了
  • 出现Major GC,一般至少会发生一次Minor GC(并非绝对的,Parallel Scavenge收集器的收集策略中就有直接进行Major GC的过程)
  • Major GC的速度一般会比Minor GC慢10倍以上,STW时间更长
  • 如果Major GC后,内存还不够,就会OOM

Full GC触发机制:

  • 调用System.gc(),系统建议执行Full GC,但是不是必然执行的。
  • 老年代空间不足
  • 方法区空间不足
  • ...

说明:full gc是开发或调优中尽量要避免的,这样STW的时间更短一些。

6、堆空间分代思想

为什么要进行分代?不分代就不能正常工作吗?

  • 经研究,不同对象的生命周期不同,70%-99%的对象都是临时对象
    • 新生代:有Eden区和两块survivor区,其中to区总为空
    • 老年代:存放新生代中多次GC后还存活的对象、或者新生代中放不下的对象
  • 不分代也是完全可以的,分代只是为了优化GC性能。如果不进行分代,所有对象都在一个空间里,每次进行垃圾回收都要把全部对象都检查一遍,影响性能。分代之后,由于大部分对象都是朝生夕死的,所以在创建对象的新生区进行频繁的Minor GC是合理的,而多次GC后仍然存活的对象预计是长期存在的,所以存放到老年区,这样这一部分对象就不会被频繁地检查了。
7、内存分配策略

如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被survivor容纳的话,将被移动到survivor空间,并将对象年龄设为1。对象每经过一次Minor GC并存活下来后,年龄都会加1。当增长到一定程度之后,将会晋升至老年区。

对象晋升老年区的阈值,可以通过:-XX:MaxTenuringThreshold来设置

不同年龄段对象分配原则:

  • 优先分配到Eden
  • 大对象直接分配到老年代
    • 尽量避免出现太大的对象
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断
    • 如果Survivor区中相同年龄的对象大小的总和大于Survivor空间的一半,大于等于该年龄的对象可以直接进入老年区,无需等到MaxTenuringThreshold设定的值
  • 空间分配担保
    • -XX:HandlePromotionFailure
8、TLAB(Thread Local Allocation Buffer)

为什么要有TLAB?

  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
  • 在并发环境下,从堆区读写数据是线程不安全的。
  • 为避免多个线程操作同一地址的数据,需要使用加锁等机制,影响分配速度

什么是TLAB?

  • 从内存模型而不是垃圾收集的角度,对Eden区继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间中。
  • 多线程同时分配内存时,可以避免线程安全问题,同时还能提高内存分配的吞吐量,因此我们可以将这种内存分配方式称为快速分配策略

相关说明:

  • 尽管不是所有的实例对象都能够在TLAB中分配内存,但JVM确实是将TLAB作为内存分配的首选
  • 可以通过选项“-XX:UseTLAB”来设置是否开启TLAB空间
  • 默认情况下,TLAB空间的内存非常小,仅占Eden空间的1%。可以通过选项“-XX:TLABWasteTargetPercent”设置TLAB所占用空间的百分比大小。
  • 一旦对象在TLAB空间分配内存失败,JVM就会通过加锁的机制来确保数据操作的线程安全性,从而直接在Eden空间分配内存。
9、小结堆空间的参数设置

-XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
-XX:+PrintFlagsFinal  :查看所有的参数的最终值(可能会存在修改,不再是初始值)
     具体查看某个参数的指令: jps:查看当前运行中的进程
                            jinfo -flag SurvivorRatio 进程id
-Xms:初始堆空间内存 (默认为物理内存的1/64)
-Xmx:最大堆空间内存(默认为物理内存的1/4)
-Xmn:设置新生代的大小。(初始值及最大值)
-XX:NewRatio:配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
-XX:+PrintGCDetails:输出详细的GC处理日志
打印gc简要信息:① -XX:+PrintGC   ② -verbose:gc
-XX:HandlePromotionFailure:是否设置空间分配担保

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/318856.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号