Day329&330&331&332.堆 JVM

一、堆(Heap)的核心概述

堆空间:新生代+老年代(不包含永久区/元空间)

1、图示

要考虑到内存空间中的最大空间

image-20210715215016767

一个进程对应一个JVM实例,一个JVM实例中对应一个运行时数据区,一个进程只有一份堆,一个进行只有一份方法区,一个进程中对应多个线程,多个线程共享 堆空间、方法去;

每个线程独自拥有一套虚拟机栈、PC程序计数器、本地方法栈

2、概述

image-20210715220854610

image-20210715221754097

  • 引用关系

image-20210715215048431

3、内存细分

image-20210715215106014

4、堆空间内存结构

堆空间:新生代+老年代(不包含永久区/元空间)

  • **(JDK7)**↓

image-20210715215155591

  • **(JDK8)**↓

image-20210715215217163

二、设置堆内存大小与OOM

1、堆空间大小设置

-Xms size #堆空间初始化内存大小
-Xmx size #堆空间最大空内存大小

image-20210715223854447

image-20210715224322727

image-20210715224817034

image-20210715224833860

image-20210715225325903

2、OOM举例

image-20210715215308544

image-20210715225949472

image-20210715230102372

三、新生代&老年代

1、堆空间结构

image-20210716204138230

对象被创建会先放着伊甸区,若没被GC,那后来会进入幸存者区,然后还没有被GC就再到老年区

2、堆空间设置比例大小

image-20210716211046711

-XX:NewRatio=size #设置新生代老年代比例【新生代:老年代】
-XX:SurvivorRatio=size #设置新生代与幸存者区比例【伊甸区:幸存者1区:幸存者2区】
-Xmn size #设置新生代空间大小 (一般不设置)

image-20210716211956521

image-20210716212330056

  • 对象存放流程

先放入Eden区,若还存活;就放入Survivor区,若还存活;就放入Tenured老年代

image-20210716204207350

四、图解对象分配过程

1、概述

-XX:MaxTenuringThreshold=size #设置进入老年区的阈值

image-20210716204231982

image-20210716204238361

  • YGC/Minor GC触发的条件

当伊甸园区满的时候,且还要进行加入对象时触发

  • 当幸存者区满的时候,不会触发YGC/Minor GC
  • 当幸存者区满了后,可能会被直接转移至老年代

2、对象分配的一般过程图示

  • 步骤1:↓

当伊甸园区满的时候,再造对象时,会触发SWT(StopWord停止用户线程),启动YGC/Minor GC(GC线程),去回收伊甸园区里的内存空间,通过可达性分析算法,进行垃圾回收;如果还有被占用/使用的对象,那就会被转移到幸存者区;

并为每一个转移还在使用的对象分配一个年龄计数器,并将每个年龄计数器赋值为1;

这时,S0为FROM区,S1为TO区;(谁空谁是TO)

此时伊甸园区里面就没有数据了,完全情况,垃圾被回收,再使用的被转移至幸存者区S0中,此时S1为空;

image-20210716212704910

  • 步骤2:↓

那么接下来创建的对象,就会再被放入伊甸园区;

再放放放放…,此时伊甸园区又满了;

再次触发,YGC/Minor GC垃圾回收,情况如上;

但是此时还在被使用的对象,就会被转移至S1中;此时S0存之前转移的对象,S0存此时转移来的对象;被设置转移对象的年龄计数器为1,;

但是!!!此时YGC/Minor GC垃圾回收,会对之前S1中存储对象进行判断,他们是否还再被使用;

如果还被使用就转移到S1区,然后给他们的年龄计数器+1;如果不使用,就被垃圾回收;

此时!!!伊甸园区和S0里面已经被GC清空了!此时S0就变成了幸存者TO区。

这时,S0是幸存者TO区,S1是幸存者FROM区;(谁空谁是TO)

之后就一直重复如上的过程!!!!!!

image-20210716213507524

  • 步骤3:↓

重复如上的过程。。。。

如果当幸存者区里面的的对象还有被占用,且他的年龄计数器大于15,他就会被Promotion(晋升);

将其还被占用的对象,且年龄计数器大于15的对象,转移至老年代;

此时就不再考虑年龄计数器了,年龄计数器只用于: 幸存者区===>老年区的指标

这里的年龄计数器的15,为阈值;默认为15;

进入老年代后,再被回收的可能性就会很小了。。。

image-20210716214220333

3、总结

image-20210716215838455

  • 谁空谁是TO
  • 频繁收集新生代,较少收集养老带,几乎不动永久带

4、流程图

image-20210716204318276

  • 代码示例

image-20210716204324621

  • 伊甸园区/幸存者S0/幸存者S1/老年代区的内存使用情况图

image-20210716220735509

5、常用的调优工具

image-20210716204345882

五、Minr GC、Major GC与Full GC

1、概述

image-20210717202152558

2、最简单的分代式GC策略触发条件

image-20210717202502353

image-20210717200330014


image-20210717202957503


image-20210717203109519

image-20210717204409662

六、堆空间分代思想

最主要的原因就是针对对应活跃度的数据做出GC回收,而不是对全部的对象数据进行GC遍历

又浪费性能,又浪费时间提升效率

image-20210717200412700

image-20210717205539945

七、内存分配策略

1、一般情况

image-20210717200437780

2、对象提升原则(内存分配策略)

image-20210717211230406

同年龄对象内存和大于s区的一半,大于等于该年龄对象全部进入老年代

八、为对象分配内存:TLAB

为每一个线程分配缓冲区,大致意思就是把Eden分成小格小格的,每个线程先用自己的小格子来分配对象,避免线程安全问题

1、目的

image-20210717213721977

2、定义

在伊甸园区为每一个线程分配一份独立的缓存区

image-20210717214838237

3、图示

image-20210717214044215

4、说明

默认开启了TLAB空间,JVM会将TLAB区域作为对象内存分配的首选,如果对象被分配到了TLAB中就不用考虑线程安全问题,而当TLAB满了的时候,对象就只能放在外面,此时就需要加锁了来保证线程数据安全,与数据不被覆盖

image-20210717215453866

  • 命令行代码
-XX:UseTLAB #查看是否开启TLAB空间
-XX:TLABWasteTargetParent #设置TLAB空间比例

image-20210717215134748

  • 流程图

image-20210717210157783

5、堆空间一定是共享的吗????

不是的,对堆中有TLAB,线程缓冲区;每一个线程都有一部分独立的空间在堆空间中被划分;

分配空间的时候,先优先分配到对应线程的TLAB区,如果不够,就分配到堆中,并给对于空间加独占锁,保证不会出现线程安全问题

九、堆空间参数设置

image-20210717220437682

  • 空间分配担保参数说明

HandlePromotionFailure

image-20210717221734544

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

  • 是的,先阶段堆是分配对象存储的唯一选择,虽然有如下的栈上分配,但是最主要技术不成熟,所还是用的也是标量分配,而不是栈上分配
  • new的对象默认会放入堆空间进行内存分配

1、前言

image-20210718152704052


2、逃逸分析概念

根据该对象是否只是方法内使用还是在外部方法被引用,来判断它是否发生逃逸

如果没有发生逃逸,就使用栈上分配;反之使用堆上分配

image-20210718152949460

  • 没有发送逃逸的例子

V对象的作用范围只在方法内部,所以没有发生逃逸

//没有发生逃逸
public void m1(){
	V v = new V();
    //....
}

没有发生逃逸的对象,则可以分配到栈上,随着方法执行结束,栈空间就被移除,栈空间线程私有,因为一个方法对应一个栈中的栈帧,栈帧执行完毕,会弹出栈,那么栈帧里面对应局部变量表、操作数栈都随之被移除,空间也就被释放了;在栈空间中根本不存在GC


  • 发生逃逸的例子

sb这个对象在createSB()方法里面被创建,但是他的作用域不只是这个方法,他被return了;

所以有可能其他方法会获取到sb这个对象的引用,所以他就发生了逃逸

public static StringBuffer createSB(String s1,String s2){
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}

  • 如何快速的判断这个对象有没有发生逃逸分析???

判断new的对象实体,是否有可能在被方法外调用


  • 参数设置
-XX: +DoEscapeAnalysis #显式开启逃逸分析
-XX: +PrintEscapeAnalysis #查看逃逸分析筛选结果

image-20210718150314516


  • 结论

使用局部变量,少使用成员变量

image-20210718150327122


3、逃逸分析代码优化

image-20210718150354254


4、栈上分配

使用局部变量,少使用成员变量

image-20210718150418178


5、同步省略(消除)

image-20210718155622151

image-20210718155722614


6、标量替换

image-20210718160338684

new Point(1,2)的对象逃逸分析后,发现未发生逃逸

那就会优化将new Point分解成int x =1 , int y =2:

并放置在栈帧中对应的局部变量表中,而不是放在堆空间中,因此就减少GC了

如下:↓

image-20210718160533809

原本需要创建一个对象的内存分配,转而分解成创建两个基本类型,实现的减少堆内存占用


  • 标量替换参数设置
-XX:+EliminateAllocations #开启标量替换,默认打开

image-20210718150530103

image-20210718150538456


7、逃逸分析小结

image-20210718150606271


十一、总结

image-20210718150628015

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿昌喜欢吃黄桃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值