一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
    Java堆区在JVM启动的时候即被创建,其空间大小也被确定下来.是JVM管理的最大一块内存空间
    堆的大小是可以进行调节的:Java虚拟机规范规定,堆可以处于物理不连续的内存空间,但是逻辑上它应该视为连续的.
    所有的线程共享Java堆,在这里还可以划分出线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)
    “几乎”所有实例对象都会在堆上分配内存;
    数组和对象可能永远不会存储在栈上,仅仅是在垃圾收集的时候才会被移除;
    堆是GC执行垃圾回收的重点区域;
    image.png
    现代垃圾收集器大部分基于分代收集理论设计,堆空间在不同的JDK版本的细分如下图所示:
    image.png
    设置堆内存大小和OOM

    指令 内容
    -Xms 表示设置堆区的起始内存
    -Xmx 表示设置堆区的最大内存

    一旦堆中的内存超过了”-Xmx”所指定的最大内存时,将会抛出OutOfMemoryError异常;
    通常会将-Xms和-Xmx两个参数配置相同的值,其目的在于:为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能
    默认情况下:
    初始内存大小:物理电脑内存大小 / 64
    最大内存大小:物理电脑内存 / 4

    年轻代和老年代
    存储在JVM中的java对象可以分为两类:
    一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
    另一类对象的生命周期非常长,在某些极端的情况下还能够与JVM的生命周期保持一致
    Java堆区进一步细分,可以分为年轻代(YoungGen)和老年代(OldGen),其中年轻代又可以划分为Eden空间,Survivor0空间和Survivor1空间(有时也被称作from区和to区),如下图所示:
    image.png
    配置新生代与老年代在堆结构中的占比:
    默认情况下:-XX:NewRatio = 2,表示新生代占1,老年代占2,新生代占整个堆的1/3;
    可以修改:利用-XX:NewRatio = n:表示新生代占1,老年代占n,新生代占老年代的1/n
    在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例为8:1:1
    可以利用-XX:SurvivorRatio = n来调整这个空间比例
    几乎所有的Java对象都是在Eden区被new出来的
    绝大部分的Java对象的销毁是在新生代中进行的,数据表明,新生代中80%的对象都是”朝生夕死”的
    可以使用”-Xmn”来设置新生代最大内存大小

    图解对象的分配过程

    对象分配过程步骤:
    1.new的对象先放伊甸园区,此区有大小限制;
    2.当伊甸园区的空间被填满以后,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(GC),将伊甸园区的不再被其他对象所引用的对象进行销毁.再加载新的对象到伊甸园区
    3.然后将伊甸园中剩下的存活的对象移动到0区
    4.如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的如果还没有被回收,此时全部移动到幸存者1区
    5.如果再次经历垃圾回收,此时重新放回幸存者0区,接着再去幸存者1区,如此反复
    6.当幸存者区中的对象的年龄到达阈值(默认15),会在垃圾回收时进入老年代区
    7.在老年代区中,当老年代区被填满时,会触发GC:MajorGC,进行老年代的垃圾回收
    8.若老年代区执行;垃圾回收还是发现无法进行对象的保存,就会产生OOM异常
    总结:
    1.针对幸存者0区,1区来说:复制之后有交换,谁空谁是to
    2.关于垃圾回收:频繁在年轻代收集,很少在老年代收集,机会不再永久代/元空间收集

    Minor GC,Major GC与Full GC
    JVM在进行GC的时候,并非每次都是对上面三个内存(新生代,老年代,方法区)一起进行回收的,大部分时候回收都是指的新生代(年轻代)
    针对HotSpot VM的实现,它里面的GC按照回收区域又分为了两个大类型:一个是部分收集(Partial GC).一个是整堆收集(Full GC)
    部分收集:不是完整收集整个Java堆的垃圾收集,其中又分为
    年轻代收集(Minor GC/Young GC):只是对年轻代进行垃圾收集
    老年代收集(Major GC/Old GC): 只是对老年代进行垃圾收集
    混合收集(Mixed GC):收集真个新生代以及部分老年代的垃圾收集
    PS:目前只有G1 GC会有此种功法!
    整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集

    年轻代GC触发机制
    1.当年轻代空间不足时,就会触发Minor GC,这里的年轻代指的是Eden区满,Survivor区满不会触发GC
    2.因为Java对象大多都是朝生夕死的特性,所以Minor GC会比较地频繁,一般回收速度比较快.
    3.Minor GC会触发STW(Stop The World,即让整个用户线程停止运行的状态),暂停其他用户的线程,等到垃圾回收结束,用户线程才会恢复运行.
    老年代GC触发机制
    1.指的是在老年代的GC,对象从老年代中消失
    2.出现Major GC,经常会伴随至少一次的Minor GC(但也不是绝对的)
    也就是说,在老年代空间不足的时候,会首先考虑触发Minor GC,如果之后空间还是不足,再触发Major GC
    3.Major GC的速度一般比Minor GC慢十倍以上,STW的时间也会更长
    4.如果Minor GC之后,内存空间还是不足,则报OOM
    Full GC触发机制
    1.调用System.gc()时,系统建议执行Full GC,但是不必然执行
    2.老年代空间不足
    3.方法区空间不足
    4.通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    5.由Eden区,Survivor0区向Survivor1区复制时,对象大小大于To Space可用大小,则将该对象转存老年代,且老年代的可用内存小于该对象大小

    PS:Full GC是开发中尽量避免的,这样暂停的时间会短很多

    堆空间的分代思想
    为什么需要将Java堆进行分代处理?不分代就无法正常工作了?
    经研究表明:不同对象的生命周期是不同的,其中70%~99%的对象时临时对象;
    其中对堆进行划分:
    年轻代:由Eden区,两块大小相同的Survivor区(to总为空)构成
    老年代:存放年轻代中经历多次GC仍然存活的对象

    其实,不分代程序能够正常运行的,分代的原因就是为了优化GC的性能,从而提高程序的运行性能.如果不进行分代操作,那么进行GC是会对堆区的所有区域进扫描,而很多对象其实都是“朝生夕死”的,当每次GC都对所有堆区的对象进行扫描回收,将提高GC的时间和降低其效率。但是我们如果分为新生代和老年代,其中新生代存放生命周期较短的对象,即“朝生夕死”或者刚新创建的一些对象,老年代存放生命周期比较长的对象,这样一来进行GC的时候,会先把生命周期较短的临时变量进行回收,不用扫描整个堆区,而是有针对性的进行扫描回收,这样可以节省很多时间和提高回收的效率,大大地提高了jvm的性能。

    内存分配策略
    如果对象在Eden区出生到经历第一次MInor GC后仍然存活,并且能被Survivor所容纳的话,将会被移动到Survivor空间中,并且设置age = 1,对象在Survivor区每熬过一次Minor GC,age就会自动加1,当它增加到一定的程度(default = 15)时,就会晋升老年代中;
    老年代的年龄阈值设置:-XX:MaxTenuringThreshold来设置
    分配策略:
    1.优先分配到Eden
    2.大对象直接分配到老年代中:尽量避免程序中出现过多的大对象(还”朝生夕死”)
    3.长期存活的对象分配到老年代
    4.动态对象年龄判断
    如果Survivor区中相同年龄的对象 > Survivor空间的一半,年龄大于或者等于该对象可以直接进入老年代,无需等到达到阈值以后

    为对象分配内存:TLAB
    为什么会存在TLAB(Thread Local Allocation Buffer)?
    1.堆区是所有线程的共享区域,任何线程都可以访问到堆区中的共享数据
    2.由于对象实例的创建在JVM中非常地频繁,因此在并发环境下从堆区中划分出内存空间是线程不安全的
    3.为了避免多个线程操作同一地址,需要使用加锁等机制,进而影响了分配的速度
    从而引出了TLAB:线程私有的缓冲区
    什么是TLAB?
    1.从内存模型而不是垃圾收集器的角度来说,对Eden区继续进行划分,JVM为每个线分配了一个私有缓存区域,它包含在Eden空间内
    2.多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存的分配的吞吐量,因此我们可以将这种内存分配方式叫做快速分配策略
    image.png
    基于TLAB的对象分配过程图如下所示:
    image.png
    堆空间的参数设置
    测试堆空间常用的jvm参数:
    -XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
    -XX:+PrintFlagsFinal :查看所有的参数的最终值(可能会存在修改,不再是初始值)
    具体查看某个参数的指令: jps:查看当前运行中的进程
    jinfo -flag SurvivorRatio 进程id
    javap -v -p Xxx.class
    -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:是否设置空间分配担保

    空间分配担保:
    image.png
    堆是分配对象存储的唯一选择吗?
    image.png
    逃逸分析概论:
    image.png
    注意:
    1.判断是否逃逸分析:new的对象是否有可能在方法外被调用
    2.开发中能够使用局部变量就不要轻易使用在方法外进行定义
    逃逸分析之代码优化
    image.png
    其中:
    1.栈上分配的场景:分别给成员变量赋值,方法返回值.,实例引用传递等等
    2.同步消除——锁消除(如果一个对象被发现只能从一个线程中被访问到,那么对于这个对象来说,可以不考虑同步)
    3.分离对象或标量替换(HotSpot使用)
    逃逸分析小结:
    image.png

    image.png