运行时数据区之堆

堆的核心概述

  对一个进程而言是唯一的。一个进程对应一个JVM实例、一个JVM实例拥有一个运行时数据区,仅拥有一个堆结构,也是Java内存管理的核心区域。

  Java堆区在JVM启动时便被创建,并且设定好了大小。是JVM中最大的一块内存空间(可调节)

通过Java Visual JVM查看 通过 -Xms…m -Xmx…m

  堆在物理上可以存在不连续的内存空间,但逻辑上应当视为连续的。

  所有线程共享堆。堆还能划分为线程私有的缓冲区(TLAB,Thread Local Allocation Buffer)

  所有的对象实例 以及数组 都应当在 运行时 分配在堆上。

  数组和对象可能永远都不会存储在栈上,因为栈帧保存引用,而这个引用指向对象或者数组在堆中的位置。

在这里插入图片描述

  在方法结束后,堆中的对象不会马上移除,而是等待垃圾收集时移除。

  堆 是 垃圾收集器(GC,Garbage Collection)执行垃圾回收的重点区域。

  

内存细分

  现代的垃圾收集器大致基于 分代收集器理论设计。

  1.JAVA7 之前:新生区+养老区+永久区

  2.Java8:新生去 + 养老区 +元空间

在这里插入图片描述

元空间是逻辑上属于堆区。实际存储在方法区

  

设置堆内存大小与OOM

  1.设置堆空间的大小:

    -Xms 用于表现堆区(年轻代+养老代)的起始内存,等价于-XX:InitialHeapSize 【ms memory start】

    -Xmx 用于表示堆区的最大内存,等价于-XX:MaxHeapSize

  一旦堆区的内存大小超过了—Xmx 所指定的最大内存,将会抛出OOM(OutOfMemoryError)异常。

  通常会将 两个参数 配置相同的值,目的:在Java垃圾回收机制清理完堆区后不需要重新分割计算区间的大小,以此提高性能。

  

查看设置的参数

jps

jstat -gc 进程id

or

+XX:+PrintGCDetails

  

OOM错误

  对象与数组创建超过堆空间可容纳空间

  

堆区的结构

  在JVM中的java对象可以划分成两类:

    生命周期较短的瞬时对象(创建与消亡都很快)

    生命周期很长,甚至于JVM生命周期保持一致的对象。

  

  java堆区进一步划分又可以分为年轻代(YoungGen) 和 老年代(OldGen)

    其中年轻代又可划分为Eden空间、Survior0与Survivor1空间【也可称为form区、to区】

在这里插入图片描述

配置新生代与老年代在堆结构中的占比

  默认: -XX:NewRatio = 2 [新生代占1,老年代占2.即新生代占据整个堆的1/3]

  通过 -XX:NewRation = ? 进行修改。eg: = 3。即新生代占1,老年代占3.所以新生代占整个堆的1/4

  

配置新生区中Eden空间与两个Survivor空间缺省占比。 默认比例是8:1:1。可以通过 -XX:SurvivorRatio = ? 来进行调整。还可以通过-Xmn 来设置新生代最大内存大小。

  几乎所有的Java对象都是在Eden区被New出来的。也大多在新生代进行销毁。(大部分对象的使用周期短)
  

对象分配过程

  为新对象分配内存是一项复杂的任务。JVM不仅需要考虑内存如何分配、哪里分配。还需考虑内存分配与内存回收。因此要考虑GC执行完内存回收后是否会在内存空间产生内存碎片。

对象分配的流程

在这里插入图片描述

1.new的对象将存放于Eden区。

2.当Eden区填满,而程序又需要创建对象时,JVM的垃圾回收器会对Eden区进行垃圾回收[Minor GC],将Eden区中不再被其他对象所引用的对象进行销毁。再加载新的对象放到Eden区。

3.将Eden区剩余对象 移动到 Servivor0区。

4.若后续再次触发垃圾回收,则上次幸存下来的对象就会被放到Servivor0区。若无回收,便会存放到Servivor1区。

5.如果在经历垃圾回收,此时将会被放回Servivor0区,再去Servivor1区。

此时Servivor两个区,也可以被称为 TO区 和 Form区

​ TO:下一次垃圾回收的幸存者往哪里存放的区。【每当幸存一次,则会对幸存者赋值+1】

6.何时从新生代去老年代?

  默认为幸存15次。可以通过-XX:MaxTenuringThreshold=<N>进行设置

  

初始GC机制

  GC按照回收区域大致可分为两种类型。

    部分收集(Partial GC):不完整收集整个Java堆的垃圾收集

      新生代收集(Minor GC):仅针对新生代收集

      老年代收集 (Major GC):仅针对老年代收集

1.注意 此处Major GC 时常会和 Full GC混淆使用。具体需要分辨为。老年代回收还是整堆回收

2.只有CMS GC会单独收集老年代的行为。

​       混合收集

​     整堆收集(Full GC):针对整个新生代以及部分老年代的垃圾收集。

  

Minor GC 触发机制:

​ 1.当 年轻代的Eden 空间不足时,便会触发。 【特别注意的是:只有Eden区满才会触发,S区满不会触发】

​ 2.由于Java对象大多 具有 使用周期短 的 特性。因此Minor GC非常频繁,但回收速度也较快。

​ 3.Minor GC 会引发 STW。暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行。

  

Major GC 触发机制:

​ 1.老年区满时,便会触发Major GC

​ 2.当出现Major GC时,通常会伴随至少一次MInor GC。即当老年区满时,会先尝试Minor GC。在此之后空间仍然不足,则会触发Major GC

​ 3.Major GC得速度相较Minor GC会慢许多(10倍以上),STW时间更长。

​ 4.Major GC后,内存不足,则会报OOM。

  

​ Full GC触发机制:

​ 1.调用System.gc()

​ 2.老年代空间不足

​ 3.方法区空间不足

​ 4.通过Minor GC后进入老年代的平均大小 大于 老年代的可用内存

​ 5.由Eden区、From区向To区复制时,对象大小 大于To区可用内存,则会将该对象转存老年代,且老年代的可用内存小于 该对象大小。

​ -Full GC时开发或调优时尽量避免的。-

  

堆空间分代思想

​   在java开发中,不同对象的生命周期不同。但大致都是临时对象

  

为什么要进行分代?

  分代的唯一理由便是优化GC性能。

  若没有分代,则所有对象都在一起。GC时,要分辨哪些对象需要回收,则将对堆的所有区域进行扫描。而许多对象都是临时对象,将加大GC时间。

  若分代,则新创建的对象放到某一地方,而GC时,优先对临时对象的区域进行回收,则效率大大提高。

  

内存分配策略

    如果对象在Eden区出生,并通过第一次MInor GC依然存活,且能被Survivor区容纳,则将被移动到Survivor空间中,并且对象年龄将设为1. 对象每经历一次Minor GC,年龄变+1岁。到一定程度时,便晋升为老年区。【默认为15次,可通过-XX:MaxTenuringThreshold设置】

    针对不同年龄的对象分配原则:

  1. 优先分配至 Eden

  2. 大对象直接分配到老年区【即在开发时,需避免出现过多的大对象】

  3. 长期存活的对象分配到老年区。

  4. 动态对象年龄判断

    如果Survivor区中相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可直接进入老年区。

  5. 空间分配担保 可通过 -XX:HandlePromotionFailure

  

为对象分配内存:TLAB

  为什么需要TLAB?

    由于堆是线程共享区域,任何的线程都可以访问堆区中的共享数据。而对象实例的创建频繁,导致在并发环境中从堆区划分内存空间是不安全的。

    为避免多个线程操作同一地址,则需要使用锁等机制,便会影响分配速度。

  什么是TLAB(Thread Local Allocation Buffer)?

      从内存模型的角度,对Eden区继续划分,JVM为每个线程分配了一个私有缓存的区域。

      此时,当多线程同时分配内存时,使用TLAB可以避免一系列线程安全问题。同时还可以提升内存分配的吞吐量。而这种内存分配方式叫作 快速分配策略。

1.并非是所有的对象实例都分配在TLAB,但JVM将TLAB作为内存分配的首选。

2.开发人员 可用过 -XX:UserTLAV设置开启TLAB空间

3.默认情况下,TLAB空间的内存非常小。可以通过-XX:TLABWasteTargetPercent设置TLAB空间在Eden的占比百分比。

4.一旦对象在TLAB空间分配内存失败时。JVM就是尝试通过 加锁机制 确保数据操作的原子性。从而直接在Eden区中分配内存。

TLAB中对象分配过程:

在这里插入图片描述

  

堆空间的整个流程:

  在发生Minor GC前,JVM会 检查老年代最大可用的连续空间 是否大于 新生代所有对象总空间。

    大于:Minor GC安全

    小于:JVM查看-XX:HandlePromotionFailure设置值是否允许担保失败。

      若为true,则继续老年代最大可用的连续空间 是否大于 历次晋升老年代的对象平均大小。

        如果大于,则尝试Minor GC,但仍有风险。

        如果小于,则进行一次Full GC。

     若为false,则进行一次Full GC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值