Java堆详解

Java堆

堆的对象管理

  • 在《Java虚拟机规范》中堆Java堆的描述是:所有对象实例以及数组都应该在运行时分配到堆上
  • 但是从实际使用的角度来看,不是绝对,存在某些特殊情况下的对象产生不在堆上奉陪内存
  • 这里注意,规范上是绝对,实际上是相对
  • 方法结束后,堆中的对象不会马上被移除,需要通过GC执行垃圾回收后才会回收

堆的概述

  1. 一个JVM进程存在一个堆内存,堆是JVM内存管理的核心区域
  2. Java堆区在JVM启动时被创建,其空间大小也被确定,是JVM管理的最大一块内存(堆的大小可以调整)
  3. 本质上堆是一组在物理上不连续的内存空间,但是逻辑上是连续的空间(参考HSDB分析的内存结构)
  4. 所有线程共享堆,但是堆内对于线程处理还是做了一个线程私有的部分(TLAB)

堆内存的细分

  • java7之前内存逻辑划分为 新生代 + 养老区 + 永久区
  • java8之后内存逻辑划分为 **新生代 + 养老区 + 元空间
  • 新生代也可以叫年轻代,它又分为Eden区和survivor区,其中survivor区又分为 survivor0区和survivor1区 或者 From区和To区
  • 实际上不管永久代与元空间其实都是指方法区中对于长期存在的常量对象的保存

堆空间的分代思想

老年代,年轻代,永久代
  • 年轻代用于放置临时对象或者生命周期短的对象
  • 老年代用于方式生命周期长的对象
  • 永久代或者元空间,用于存放常量
  • minor GC只管年轻代 , Full GC同时管理年轻代和老年代
为什么需要分代?有什么好处?
  • 经研究表明,不同对象的生命周期不一致,但是在具体的使用过程中70%-90%的对象是临时对象
  • 分代唯一的理由是优化GC的性能,如果没有分代,那么所有对象在一块空间,GC想要回收扫描他就必须扫描所有的对象,分代之后,长期持有的对象可以挑出,短期持有的对象可以固定在一个位置回收,省掉很大一部分的空间利用

堆的默认大小

初始大小:电脑物理内存/ 64
最大内存大小:电脑物理内存 / 4

RunTime类

该类对应JVM的运行时数据区

long initialMemory = Runtime.getRuntime().totalMemory();
long maxMemory = Runtime.getRuntime().maxMemory();
long freeMemory = Runtime.getRuntime().freeMemory();

堆区详解

对象在堆区中的位置变化

这里上两张图结构图:

年轻代又分为Eden区和survivor区,
1.所有对象一出生都会在Eden区,并且对象年龄为0。Eden区满了之后会触发minor GC, minor GC只回收年轻代中需要回收的对象。每一次minor GC,若该对象没有被回收,那么对象的年龄就会 + 1。对象到达阈值以后该对象会进入老年代(android阈值为6)
2.当老年代空间满了以后会触发full GC,full GC会同时回收年轻代和老年代中所有需要回收的对象

minor GC

Eden区满了以后,会调用minor GC,该GC只会回收年轻代中需要回收的对象,然后对不可回收的对象进行标记(就是把不可回收对象的年龄 + 1),对象年龄达到阈值后对象会进入老年代空间

full GC

当老年代空间满了以后会触发full GC,full GC会回收年轻代和老年代中所有需要回收的对象

相关命令

jps ,查看当前所有java进程id

jstat -gc 进程id 查看

  • 结尾C代表总量

  • 结尾U代表已使用量

  • S0,S1代表survivor区的from 和 to

  • E代表Eden区

  • OC代表老年代总量

  • OU代表老年代已使用量

Eden区:

对象一创建就会位于该区

Survivor区:
  • Survivor区有两块,分别为Survivor0(S0)和Survivor1(S1),根据作用还可以分为From Survivor区和To Survivor区
  • 而 Survivor0 和 Survivor1 哪一块作为From Survivor区,哪一块作为To Survivor区,是不固定的。
  • 在同一时间内,Survivor0 和 Survivor0 两块区域一定有一块是空的,非空的那块作为From Survivor区,空的那块作为To Survivor区
  • JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的
    因此,年轻代实际可用的内存空间为 9/10 ( 即90% )的新生代空间
Survivor区的作用:
  • 保证对象内存的连续,提高GC的工作效率
对象在年轻代位置的变化:
  • 我们就只看一个对象在年轻代的位置变化:它一出生会位于Eden区,接着会不断在Survivor0和Survivor1区切换,直到进入老年代
  1. 对于一个刚刚创建的对象A,它一开始位于Eden区

  2. 第一次minor GC触发,如果它没被回收,它会进入S0区,并且A年龄 + 1

  3. 第二次minor GC触发,如果它没被回收,它会进入S1区,并且A年龄 + 1

  4. 第三次minor GC触发,如果它没被回收,它会进入S0区,并且A年龄 + 1

  5. 第四次minor GC触发,如果它没被回收,它会进入S1区,并且A年龄 + 1

  6. 第....次 ,当A年龄达到阈值,进入老年代

  • 再来看看多个对象的位置变化
  1. Eden区满后,触发第一次minor GC,对Eden区进行对象回收(第一次minor GC时候survivor0和survivor2区都是空的,所以只回收Eden区),回收后Eden区中的存活对象的内存不再连续,此时将所有的存活对象移到Survivor0区中内存连续的空间
  2. Eden区又满后,触发第二次minor GC,对Eden区和Survivor0区进行对象回收(Survivor1区是空的,所以不用回收),回收后Eden区和survivor0区中的存活对象内存可能不再连续,此时会将所有的存活对象移动到Survivor1区中内存连续的空间
  3. Eden区再次满了后,触发第三次minor GC,对Eden区和Survivor1区进行对象回收(Survivor0区是空的,所以不用回收),回收后Eden区和survivor1区中的存活对象内存可能不再连续,此时会将所有的存活对象移动到Survivor0区中内存连续的空间
  4. Eden区满,触发第N次GC.......................
  5. 之后存活对象会不断在Survivor0区和Survivor1区中切换,直到年龄达到阈值后进入老年代

posted @ 2022-01-22 02:34  Shzy  阅读(772)  评论(0编辑  收藏  举报