学习笔记--JVM之运行时数据区(4)堆

. 堆的概述

一个JVM实例只存在一个堆内存,也就是说一个进程中只存在一个堆内存,也就是多个线程是共享这个堆内存的。堆也是Java内存管理的核心区域

Java堆区在JVM启动的时候被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间

        堆内存的大小是可调节的

《Java虚拟机规范》规定。堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。

所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区。

《Java虚拟机规范》对Java堆的描述是:(几乎)所有的对象实例以及数组都应当在运行时分配在堆上(有可能栈上分配)

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

在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除

堆,是GC执行垃圾回收的重点区域。

堆的内存细分

现代垃圾回收器大多基于分代收集理论设计,java 8 及之后堆内存逻辑上分为三部分:新生区 养老区 元空间(或者是年轻代老年代)。 年轻代又可以细分位伊甸园区(Eden)  幸存者0区(Survivor1),幸存者1区(Survivor2)

年轻代与老年代

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

        一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速。

        另一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致。

配置新生代与老年代在堆结构的占比:(一般不会修改 明确知道一些对象的生命周期比较长,还偏多可以去修改)

        默认 -XX:NewRation=2,表示新生代占1,老年代占2,新生代占整个堆的三分之一

        可以修改 -XX:NewRation=4,表示新生代占1,老年代占4,新生代占整个堆的五分之一

在HotSpot中,Eden空间和另外两个Survivor空间所占比例是8:1:1。开发人员可以通过选项“-XX:SurivorRation=8”

几乎所有的Java对象都是在Eden区被new出来 当一个对象足够大,Eden装不下时 会直接在老年代里被new出来

绝大部分的Java对象的销毁都是在新生代进行了。   

可以使用选项“-Xmn”设置新生代最大内存大小。

对象分配过程

 对象刚开始分配的时候都会分配在伊甸园区,伊甸园区如果满了就会在新生代执行垃圾回收机制也就是YGC(只有在伊甸园区满的时候才会触发YGC,也叫MinorGC ,幸存者区满的时候不会触发,也就是说幸存者区是被动回收,每次伊甸园区回收的时候那个存储对象的幸存者区也会回收),然后会把把被视为“垃圾”对象回收,然后把还要被使用的对象放到幸存者区(暂定幸存者0区),此时会给被放到幸存者区的对象附上年龄1(通过年龄计数器).这时候伊甸园区会被清空,之后会再次用作存储对象使用。当伊甸园区在满了,会再次执行垃圾回收,把伊甸园区还要被使用的对象放到空的幸存者区,也就是幸存者1区,给这个对象赋上年龄1,然后去判断幸存者0区的对象是否还会被使用,如果还被使用就把他们放到幸存者1区,并且年龄+1也就是年龄变为2.此时伊甸园区和幸存者0区为空。一直这样下去,直到幸存者区有的对象年龄到了15(默认是15,可以通过-XX:MaxTenuringThreshold=<N>进行设置)还仍然要使用,此时会发生晋升(Promotion)操作,把这个对象放到老年代中。如果存对象的幸存者区满了而伊甸园区还没有执行YGC时,也会发生晋升,把一部分对象直接晋升到老年代中。

内存分配策略之动态年龄判断: 

如果幸存者区中相同年龄的所有对象大小的总和大于幸存者区空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

MinorGC MajorGC FullGC

JVM在进行GC时,并非每次都对上面的三个内存区域一起回收的,大部分时候回收的都是指新生代。

针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大中类型:一种是部分收集(PartialGC),一种是整堆收集(FullGC)

部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:

        新生代收集(MinorGC/YoungGC):只是新生代的垃圾收集

        老年代收集(MajorGC/OldGC):只是老年代的垃圾收集

                目前,只有CMS GC会有单独收集老年代的行为。

                很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。

        混合收集(Mixed GC:收集整个新生代以及部分老年代的垃圾收集

                目前,只有G1 GC会有这种行为

整堆收集(Full GC):收集整个java堆和方法区的垃圾收集。

堆空间分代思想

分代的理由就是优化GC性能。如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC的时候要找哪些对象没用,这样就会对堆的所有区域进行扫描。而很多对象都是“朝生夕死”的,如果分代的化,把新创建的对象放到某一地方,当GC的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

对象分配过程:TLAB

为什么有TLAB(Thread Local Allocation Buffer)?

堆区室线程共享区域,任何线程都可以访问到堆区中的共享数据

由于对象实例的创建在JVM中非常频繁,因此在并发环境下的堆区中划分内存空间是线程不安全的

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

什么是TLAB?

从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间中。

多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将,这种内存分配方式称为快速分配策略。

TLAB说明:

尽管不是所有的对象实例都能够在TLAB中成功分配,蛋JVM确实是将TLAB作为内存分配的首选。

在程序中,开发人员可以通过选项“-XX:UserTLAB“设置是否开启TLAB空间。

默认情况下,TLAB空间内存都非常小,仅占有整个Eden空间的1%,当然我们可以通过选项”-XX:TLABWasteTargetPercent“设置TLAB空间所占用Eden空间的百分比大小。

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

堆是分配对象存储的唯一选择吗?

不是。在java虚拟机中,对象是Java堆中分配内存的,这是一个普遍的常识。但是,有一种特殊情况,那就是如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无须在堆上分配内存,也无须进行垃圾回收了。这就是最常见的堆外存储技术。

逃逸分析概述

如何将堆上的对象分配到栈,需要使用逃逸分析手段。

这是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。

通过逃逸分析,JavaHotSpot编译器能够分析出一个新的对象引用的使用范围从而决定是否将要将这个对象分配到堆上。

逃逸分析的基本行为就是分析对象动态作用域:

        当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。

        当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸,例如作为调用参数传递到其他地方中。

没有发生逃逸的对象,则可以分配到栈上,随着方法执行的结束,栈空间被移除。

快速判断逃逸:就看new的对象是否在方法外被调用,是则发生了逃逸,否则反之。

常见的栈上分配场景:给成员变量赋值、方法返回值、实例引用传递 

 逃逸分析:代码优化

使用逃逸分析,编译器可以对代码做如下优化:

1 栈上分配,将堆分配转化为栈分配。如果一个对象在子程序中被分配,要指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。

2 同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

        线程同步的代价是相当高的,同步的后果就是降低并发性和性能。

        在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是只能够被一个线程访问而没有被发布到其他线程。如果没有,那么JIT编译器在编译这个同步块 的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。

        这个取消同步的过程就叫同步省略,也叫锁消除。

3 分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分或全部可以不存储在内存,而是存储在cpu寄存器中。在Java里也就是可以不基于堆空间存储,可以基于栈空间。

        标量(Scalar)是指一个无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。

        相对的,那些还可以分解的数据叫做聚合量(Aggregate),Java中的对象就是聚合量,因为它可以分解成其他聚合量和标量。

        在JIT阶段,经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值