JVM系列--堆

核心概念

1.一个JVM实例只存在一个堆内存,堆是java内存管理的核心区域
2.JAVA堆区在JVM启动的时候被创建,其空间大小也就被确定了,堆内存的大小是可以调节的
-Xms 最小堆大小 -Xmx最大堆大小
3.堆可以处于物理上不连续的内存空间,但在逻辑上它应该被视为连续的.
4.所有的线程共享java堆,在这里还可以划分出线程私有的缓存区(Thread Loca Allocation Buffer TLAB)
5.几乎所有的对象和实例都应该分配在对上
6.在方法结束后,堆中的对象不会马上被回收,而是在下次垃圾回收时才可能被回收
7.堆 时GC执行垃圾回收的重点区域
8.堆空间细分 为 新生区+养老区 新生区又分为Eden区和两个Survivor区

堆内存大小参数设置

-Xms 等同于-XX:InitialHeapSpace 表示堆区的起始内存
-Xmx 等同于-XX:MaxHeapSpace 表示堆区的最大内存
一旦堆区中的内存大小超过 -Xmx所指定的 最大内存将会抛出OutOfMemoryError异常
通常会将-Xms与-Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区大小,从而提升性能
注意 当设置堆大小后用Runtime类打印可用堆空间大小时总是小于我们设置的值,这是因为新生代中总有一个Survivor区是不放数据的,因此不算在可用内存中
在这里插入图片描述
图片中年轻代带线啊哦179200K 是一个eden区和一个surivior区的大小

年轻代老年代

存储在JVM中的java对象可以被划分为
生命周期较短的,这类对象的创建和消亡都非常迅速
另一类生命周期非常长,在某些极端情况下还能够与JVM的生命周期一致
java堆区进一步区分的话分为年轻代和老年代 新生代又分为Eden区和两个Survivor区
年轻代老年代大小比例
-XX:NewRation
-XX:NewRation = 2(默认)表示新生代占一份 老年代占两份,新生代占堆的三分之一

Eden区和两个Surivior区之间的比例
参数设置SurvivorRation=8
默认为8:1:1
自适应机制会导致比例不是8:1:1 如果需要可以自己显示的指定比例
IBM公司研究表明,新生代80%的对象都是朝生夕死 也是新生代使用复制算法的依据

对象分配

下面探讨一下对象分配的流程
1.逃逸技术分析对象是否可分配在栈上
逃逸技术指的是当在一个栈帧中大量的创建对象,且对象的生命周期只在当前线程中,且对象占用内存不大的情况下可能会将对象直接在栈上分配
2.大对象直接进入老年代,别的则首先分配在线程缓存区TLAB,这个区域在堆空间Eden区 很小的一块区域,主要是为了防止内存分配时的并发问题,可以理解为线程私有的
3.当eden区满后会进行YGC/Minor GC 将eden区还未死亡的对象放入s0区 对象age++如下图
在这里插入图片描述
4.当eden区又满后会将eden区和s0区的未死亡的对象放入s1区 age++,并将s0 s1两个区域调换位置
如此往返
在这里插入图片描述
5.当对象age = 15或者幸存区中一半以上对象的年龄相同,对象进入老年代
在这里插入图片描述

Minor GC Major GC Full GC

minor GC/YGC 只收集新生代
当年轻代空间不足时触发,这里的年轻代指的是eden区,survivor区满不会触发GC
java对象大多都是朝生夕死,一般minor GC非常频繁,回收速度也很快
会引起STW

major GC/Old GC
目前只有CMS垃圾收集器会有单独收集老年代的垃圾行为
出现了major GC,经常会伴随至少依次的minor GC
major GC 执行速度比minot GC执行速度慢十倍以上,STW更长

混合回收 Mixed GC
整个新生代和部分老年代的垃圾回收
目前只有G1会有这种行为

full GC
System.gc
老年代空间不足
方法区空间不足
minor GC后进入老年代的平均大小大于老年代的可用内存

相关参数 -XX:+PrintGCDetails 打印gc日志
下面一段简单的GC日志分析
在这里插入图片描述
PSYoungGen 代表YGC
2027K->510K(2560K) YGC开始前年轻代对象占2027K内存,收集后占510K 年轻代总内存大小2560K
2027K->754K(9728K) YGC开始前堆空间整体对象占2027K内存,收集后又754K对象,整个堆大小9728K

TLAB Thread Local Allocation Buffer

为什么要有TLAB
堆区是线程共享区域,任何线程都能访问到堆区的共享数据
由于对象实例的创建在JVM中非常频繁,在并发的情况下在堆区中划分区域是线程不安全的
为避免多个线程操作同一地址,需要使用加锁机制,进而影响分配速度

什么是TLAB
从内存模型角度,堆eden区域进行划分,JVM为每个线程分配了一个私有缓冲区
多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能提升系统吞吐量,这也叫快速分配策略
TLAB相关参数
-XX:+UseTLAB 开启TLAB 默认就是开启的
-XX:TLABWasteTargetPercent 设置TLAB空间所占edeu区的大小 默认1%
一旦对象在TLAB空间分配失败,JVM会尝试通过加锁机制来确保数据操作的原子性,从而直接在eden区分配内存

堆中基本参数设置

在这里插入图片描述

-XX:+PrintFlagsInitial 查看所有参数初始默认值
-XX:+PrintFlagsFinal 查看所有参数的最终值(可能存在修改,不再是初始值)
-Xms: 初始堆空间内存 默认时物理内存的1/64
-Xmx: 最大堆空间内存 默认为物理内存的1/4
-Xmn: 设置新生代大小
-XX:NewRatio 配置新生代和老年代在堆空间的占比
-XX:SurvivorRatio 配置新生代eden区和两个survivor比例
-XX:MaxTenuringThreshold 设置新生代垃圾的最大年龄
-XX:+PrintGCDetails 输出详细的GC处理日志

栈上分配

随着JIT编译器的发展和逃逸技术的分析,栈上分配和标量替换优化技术将会导致一些微妙的变化,所有的对象都分配在堆上变得不那么绝对了
如果经过逃逸分析后发现一个对象并没有逃逸出方法的话,那么就有可能进行栈上分配
-XX:+DoEscapeAnalysis
开启逃逸分析

同步省略

也叫锁消除
在动态编译同步快的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象,是否只能够被一个线程访问而没有发布到别的线程中,那么JIT编译器在编译该同步代码块的时候就会取消同步操作,也叫锁消除

public void method2(){
        Object o = new Object();
        synchronized (o){
            System.out.println("---");
        }
    }
在运行阶段根据JIT逃逸技术分析 该方法会优化成
public void method2(){
        Object o = new Object();
            System.out.println("---");
    }

标量替换

标量 是指一个无法再分解成更小的数据的数据,java中原始数据类型就是标量
还可以分解的就是聚合量,java中的对象就是聚合量,因为它可以分解为标量和聚合量
再JIT编译阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,经过JIT优化就会把该对象拆解成若干个其中包含若干个成员变量来替代,这就叫标量替换
在这里插入图片描述
以上代码经过标量替换后
在这里插入图片描述
-XX:+EliminateAllocations 开启标量替换 默认就是开启的

逃逸技术

1.逃逸技术并未成熟
2.Hotspot虚拟机并没有实现栈上分配
3.我们开启逃逸分析后效率提升和无GC操作怎么解释?主要是标量替换
4.该技术遇到的问题?
无法保证逃逸分析过程的性能消耗一定高于本身的性能消耗
一个极端的例子,就是经过逃逸分析后发现,没有一个对象是不逃逸的,这样就白白浪费了逃逸分析本身的分析过程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值