JVM学习笔记——堆

一个JVM实例只有一个堆空间,堆也是java内存管理的核心区域。堆可以处于物理上不连续的内存空间中,但是逻辑上应该视为连续的。
所有线程共享堆空间,但是还可以划分线程私有的缓冲区(TLAB)

堆空间大小设置

-Xms:用于表示堆区起始内存;-Xmx:表示堆区最大内存
通常-Xms和-Xmx设置相同值,为了在java垃圾回收之后不需要重新分割计算堆区大小,减小内存震荡,从而提高性能
默认情况,初始大小:电脑物理内存/64;最大大小:电脑物理内存/4

查看设置参数

方式一

在这里插入图片描述

方式二

加参数,-XX:+PrintGCDetails
在这里插入图片描述

设置新生代和老年代比例

-NewRatio,默认是新生代:老年代=1:2,也就是新生代占三分之一

-XX:SurvivorRation 设置伊甸区和幸存者区比例,默认8:1:1

设置新生代大小

-Xmn 优先级高于NewRatio

对象分配过程

1、new的对象先放到伊甸园区。
2、当伊甸园区满时,将伊甸园区进行垃圾回收(Minor GC),将伊甸园中不被引用的对象销毁,再加载新对象到伊甸园区
3、将剩余对象加载到幸存者0区
4、如果再次触发垃圾回收,幸存者0区中未被回收的对象以及伊甸园区本次幸存的对象统一加载到幸存者1区
5、如果再次垃圾回收,幸存者1区未被回收的对象以及伊甸园区本次幸存的对象统一加载到幸存者0区
6、。。。。。。(0区和1区来回折腾)
7、如果幸存对象躲过了15次垃圾回收则会被加载到老年代
-Xx:MaxTenuringThreshold=<.N> 进行设置躲避多少次回收后加载到老年代
在这里插入图片描述

GC

JVM在进行GC时,并非每次都对三个内存区域(新生代、老年代、元空间)一起回收,大部分时候回收指的是新生代。
针对HotSpot是按照回收区域又分为两大类:部分收集(partial gc)、整堆收集(full gc)
部分收集:不是完整收集整个java堆的垃圾收集。又分为:
· 新生代收集(minor gc/young gc):知识新生代(eden/s0、s1)的垃圾收集
· 老年代收集(major gc/old gc):只是老年代的垃圾收集
目前只有CMS GC有单独收集老年代的行为
很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收
· 混合收集(Mixed GC):收集整个新生代以及部分老年但垃圾收集
目前只有G1 GC会有这种行为
整堆收集(Full GC):收集整个java堆和方法区的垃圾收集

年轻代Minor GC触发机制

当年轻代空间不足则会出发Minor GC,这里年轻代满指的是Eden区,survivor满不会触发GC(每次Minor GC会清理年轻代内存)
因为java对象大多数朝生夕死,所以Miner GC频繁,回收速度也较快
Miner GC会引发STW,暂停用户其他线程,等垃圾回收结束,用户线程才回复运行

老年代(Major GC/Full GC)触发机制

出现Major GC经常至少伴随一次Miner GC(非绝对),也就是先触发一次Miner GC,如果空间依旧不足则触发Major GC
Major GC速度比Miner GC慢10倍以上,STW时间更长
Major GC之后空间依然不足就报oom

为什么要进行分代

分代的唯一理由就是优化性能。如果没有分代,所有对象都在一起,如同把全校的同学关在了一间教室,每次gc都要扫描全体同学。很多对象都是朝生夕死,分代之后把新创建的对方放在一个地方,当GC的时候先把这块朝生夕死的对象进行回收,可以快速腾出空间

内存分配策略

  • 优先分配到Eden
  • 大对象直接分配到老年代(尽量避免出现过多大对象)
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断,如果survivor区中相同年龄的所有对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需等待MaxTenuringThreshold中要求年龄

TLAB(每个线程分配了一个私有缓存区)

尽管不是所有对象都能够在TLAB中成功分配内存,但jvm确实是将TLAB作为内存分配的首选
在开发中可以选择:-XX:UseTLAB设置是否开启TLAB空间
默认情况下,TLAB空间非常小,仅占全部Eden空间的1%,我们可以通过选项-XX:TLABWasteTargetPercent(默认开启)设置占Eden空间的百分比
一旦最TLAB空间分配内存失败,jvm尝试通过加锁机制确保数据原子性,从而直接在Eden空间分配内存

为什么要有TLAB,TLAB的作用是什么?

假设JVM虚拟机上,堆内存都是规整的。堆内存被一个指针一分为二。指针的左边都被塞满了对象,指针的右变是未使用的区域。每一次有新的对象创建,指针就会向右移动一个对象size的距离。这就被称为指针碰撞。
在这里插入图片描述
如果此时一个线程正在给A对象分配内存,指针还没有来的及修改,同时为B对象分配内存的线程,仍引用这之前的指针指向。这样就出现毛病了。
在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。

TLAB的本质其实是三个指针管理的区域:start,top 和 end,每个线程都会从Eden分配一块空间,例如说100KB,作为自己的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配。

TLAB只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。从这一点看,它被翻译为 线程私有分配区 更为合理一点
——参考文章

堆空间常用参数

-XX:+PrintFlagsInitial 查看所有参数默认初始值
-XX:+PrintFlagsFinal 查看所有参数最终值
-Xms: 初始堆空间大小
-Xmx:最大堆空间内存
-Xmn:设置新生代大小
-XX:NewRatio 配置新生代和老年代在堆结构的占比
-XX:SurvivorRatio 设置新生代中Eden和S0、S1空间的比例
-XX:MaxTenuringThreshold 设置新生代垃圾的最大年龄
-XX:+OrintGCDetails 输出详情的GC入职
打印gc简要信息1、 -XX:+PrintGC 2、-verbose:gc
-XX:HandlePromotionFailure 是否设置空间分配担保

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

经过逃逸分析后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配

代码优化

栈上分配

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

同步省略

如果一个对象只对一个线程访问,那么对于这个对象的加锁行为将被忽略
在这里插入图片描述
每个线程进来都会新new一个hollis,所以针对hollis的锁完全没有必要

分离对象(标量替换)

有的对象可能不需要作为一个连续的内存结构也能被访问,那么对象的部分/全部可以不存储在内存,而是存在cpu寄存器
也就是如果没有发送逃逸,对象的基本数据类型直接放栈里

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值