0033-堆

文章目录

      • 1. 简述
      • 2. 堆空间的设置
      • 3. 年轻代与老年代
      • 4. 对象的分配过程
      • 5. Minor GC/ Major GC/ Full GC
      • 6. 堆空间分代思想
      • 7. 对象提升规则
      • 8. TLAB(Thread Local Allocation Buffer)
      • 9. 常用参数总结
      • 10. 堆是分配对象的唯一选择吗

1. 简述

1. 基本规则

  1. 堆是jvm管理的一块最大内存空间,且一个jvm只存在一个堆内存

  2. 堆空间被所有的线程共享,同时也可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)

  3. 几乎所有的对象都在堆空间上分配

  4. 方法执行时,栈帧中保存对象引用,对象在堆空间上分配,方法结束后,局部变量被销毁,
    堆上的对象不会立即销毁,仅在垃圾收集的时候才会被移除

  5. 堆是GC(Garbage Collection)的重点区域

2. 堆空间分类
0033-堆_第1张图片

  1. 新生区 Young Generation Space
  • Eden区
  • Survivor区(from和to两个)
  1. 养老区 Old Generation Space

  2. 元空间 Meta Space

元空间是方法区的一个实现,放在方法区讲解,jdk1.7以前称为永久代

2. 堆空间的设置

1. java堆空间的大小,在jvm启动时设置,默认大小

  1. 初始内存大小:物理内存/64

  2. 最大内存大小:物理内存/4

2. 设置堆内存大小

  1. 初始内存大小:-Xms 等价于-XX:InitialHeapSize

  2. 最大内存大小: -Xmx 等价于MaxHeapSize
    注: -Xms和-Xmx设置为同一个值,垃圾回收器清理完堆区以后不用重新分隔计算堆区大小,提升效率

3. 当堆区内存超过-Xmx设定的值时,将会抛出OutOfMemoryError:Java heap space异常

3. 年轻代与老年代

1. java堆区划分为新生代和老年代,新生代又可以分为Eden区和Survivor0区和Survivow1区(from和to区)
0033-堆_第2张图片

2. 年轻代与老年代的空间比例
规则如下,一般不用改,使用默认即可

可以通过-XX:NewRatio来设置老年代与新生代的比例
默认-XX:NewRatio=2表示老年代:新生代=2:1,新生代占1/3,老年代2/3
设置-XX:NewRatio=4表示老年代:新生代=4:1,新生代占1/5,老年代占4/5

3. 年轻代内部空间比例
Eden和两个Survivor区的比例默认是8:1:1
可以通过-XX:SurvivorRatio=8,表示Eden:Survivor=8:1,Surivivor有两个8:1:1

4. 分代切换
0033-堆_第3张图片

  1. 几乎所有的对象都在Eden被创建
  2. GC时,Eden剩余对象被转移到Survivor区
  3. 每经历过一次GC对象的年龄加1,当年龄为15时,对象被放到老年代

4. 对象的分配过程

1. 分配步骤

  1. new的对象先放伊甸园区,此区有大小限制

  2. 当伊甸园区的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),
    将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区

  3. 然后将伊甸园区中的剩余对象移动到幸存者0区

  4. 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有被回收,就会放到幸存者1区,对象年龄加1

  5. 如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去1区,每次交换对象年龄加1

  6. 当年龄达到15时,将对象放入老年代
    (临界年龄可指定 -XX:MaxTenuringThreshold=)

  7. 当养老区空间不足时,会触发Major GC,进行养老区内存清理

  8. 若养老区执行了Major GC之后依然无法进行对象的保存,就会产生OOM异常
    (java.lang.OutOfMemoryError:Java heap space)

0033-堆_第4张图片

总结

  1. 针对幸存者s0,s1区的总结,复制之后有交换,谁空谁是to

  2. 关于垃圾回收,频繁发生在新生代,很少发生在老年代,几乎不在永久区/元空间收集

0033-堆_第5张图片

5. Minor GC/ Major GC/ Full GC

1. 简述

JVM在GC时不会每次都对新生代、老年代、方法区三个区域一起回收,HotSpot VM按照回收区域分为部分收集(Partial GC)和整堆收集(Full GC)

  • 部分收集
  1. 新生代收集(Minor GC/ Young GC),只是新生代(Eden/s0/s1)收集

  2. 老年代收集(Major GC/ Old GC),只是老年代收集
    (HotSpot VM 没有单独堆老年代收集的行为,Full GC会触发Major GC)

  • 整堆收集
    收集整个java堆和方法区的垃圾收集

2. 年轻代GC(Minor GC)的触发机制

  1. 当年轻代空间不足时,会触发Minor GC,这里的年轻代指的是Eden,Survivor满不会触发GC,但是Minor GC会收集Survivor区

  2. 由于对象朝生夕灭的特性,所以Minor GC会很频繁,回收速度也比较快

  3. Minor GC会引发STW(stop the word),暂停其它用户线程,等垃圾回收结束,用户线程才恢复运行

3. 老年代GC(Major/ Full GC)触发机制
注:HotSpot虚拟机没有Major GC

  1. 当老年代空间不足时,触发Major GC,但是HotSpot VM没有针对这个区的单独回收机制

  2. Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长

  3. Major GC后内存还不足,就报OOM

4. Full GC触发机制

  1. 调用System.gc(),系统建议执行Full Gc,但不是必然执行

  2. 老年代空间不足

  3. 方法区空间不足

  4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存

  5. 由Eden区,s0区向s1区复制时,对象大小大于s1(to)区可用内存,则把对象转到老年代,且老年代的可用内存小于该对象大小

6. 堆空间分代思想

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

7. 对象提升规则

1. 正常通道
如果对象在Eden出生过第一次MinorGC后仍然存活,并且能被Survivor容纳,将被移动到Survivor空间中,并将对象年龄设为1,对象在Survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁),就会晋升到老年代中
晋升的年龄阈值可以通过–XX:MaxTenuringThreshold来设置

2. 特殊通道

  1. 对象优先分配到Eden

  2. 大对象直接分配到老年代,Eden放不小的对象

  3. Surivivor放不小的对象,直接进入老年代

  4. 动态对象年龄判断
    如果Survivor区中相同年龄的所有对象大小总和大于Survivor空间的一半,
    年龄大于或等于该年龄的对象直接进入老年代,无需等待MaxTenuringThreshold中要求的年龄

8. TLAB(Thread Local Allocation Buffer)

1. 为什么有TLAB

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

  2. 并发环境下从堆区中划分内存空间是线程不安全的

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

2. 什么是TLAB

  1. TLAB是Eden的一个部分,这个部分是线程私有的,每个线程都有自己独立的TLAB

  2. TLAB是线程私有的,可以提高内存分配的吞吐量,这种方式称为快速分配策略

  3. TLAB是jvm内存分配的首选,但是不是所有的对象实例都能够在TLAB中成功分配(TLAB内存大小有限)

  4. 通过-XX:UserTLAB设置是否开启TLAB空间,默认开启

  5. 默认情况TLAB内存很小,占Eden空间的1%,可以通过-XX:TLABWasterTargetPercent设置TLAB所占用Eden空间的大小

  6. TLAB分配内存失败时,JVM会尝试通过加锁机制,从Eden空间中分配内存

0033-堆_第6张图片
0033-堆_第7张图片

9. 常用参数总结

官方文档

1. 常用配置

  1. 测试堆空间常用的jvm参数:
    -XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
    -XX:+PrintFlagsFinal :查看所有的参数的最终值(可能会存在修改,不再是初始值)
    具体查看某个参数的指令: jps:查看当前运行中的进程
    jinfo -flag SurvivorRatio 进程id

  2. -Xms:初始堆空间内存 (默认为物理内存的1/64)

  3. -Xmx:最大堆空间内存(默认为物理内存的1/4)

  4. -Xmn:设置新生代的大小。(初始值及最大值)

  5. -XX:NewRatio:配置新生代与老年代在堆结构的占比

  6. -XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例

  7. -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄

  8. -XX:+PrintGCDetails:输出详细的GC处理日志
    打印gc简要信息:① -XX:+PrintGC ② -verbose:gc

  9. -XX:HandlePromotionFailure:是否设置空间分配担保

2. 空间担保

如果老年代最大可用的连续空间大于新生代所有对象的总空间
或者老年代最大可用连续空间大于历次晋升到老年代的对象的平均大小进行MinorGC
否则进行FullGC

10. 堆是分配对象的唯一选择吗

1. 背景

在《深入理解Java虚拟机》关于Java堆内存有这样一段描述:随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么绝对了

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

2. 逃逸分析

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

  2. 当一个对象在方法中被定义,被外部方法引用,则发生逃逸
    0033-堆_第8张图片
    0033-堆_第9张图片
    3. 代码优化

  • 栈上分配(暂不支持)
    方法中的对象经过逃逸分析,不会逃逸则对象可以在栈上分配

  • 同步省略(暂不支持)
    经过逃逸分析,方法内的锁不发生逃逸,则可以不考虑同步
    0033-堆_第10张图片

  • 分离对象或标量替换(支持)
    经过逃逸分析,不发生逃逸的对象,可以被分解为若干个成员变量代替
    0033-堆_第11张图片


    经过标量替换以后


    在这里插入图片描述

4. 结论
逃逸分析技术尚不成熟,目前的对象还都是分配在堆空间上

你可能感兴趣的:(#,Java虚拟机,jvm,堆)