转载

JVM 内存布局与GC算法

整体上来看,JVM的内存分为 堆区非堆区 ,而非堆区又包括了 方法区、JVM栈、本地方法栈、程序计数器 等。

JVM 内存布局与GC算法

2 JVM运行时数据区划分

2.1 JVM堆

其主要作用是用于为几乎所有的 对象实例数组实例 的实例化提供内存空间。说通俗点,所有采用 new关键字 产生的对象的空间都在此分配。 如: Map<String,String> map = new HashMap<>(); 这个map所引用的对象即位于JVM的堆中。 JVM heap的特点是:

  1. 内存不一定连续分配,只要 逻辑上是连续的 即可;
  2. 内存的大小可以通过 JVM的参数 来控制:如 -Xms = 1024M -Xmx = 2048M; 表示JVM Heap的初始大小为1GB,最大可自动伸缩到2GB;
  3. 平时所说的Java内存管理即指的是内存管理器对这部分内存的管理(创建/回收);
  4. 所有线程共享;

2.2 JVM栈

JVM栈是伴随着线程的产生而产生的,属于线程私有区域,生命周期和线程保持一致,所以,JVM的内存管理器对这部分内存无需管理; 从图1.1中可以看出,栈中有包含了多个 栈帧(frame) ,frame 伴随的方法的调用而产生 ,方法调用完毕,frame也就出栈,从JVM栈中被移除。frame主要保存方法的 局部变量、操作数栈、动态链接、方法出口(reternAddress或者抛Exception) 。 虚拟机规范中,对此内存区域规定了两种异常情况:

StackOverflowError
OutofMemoryError

2.3 PC(程序计数器)

简称PC(Program Counter Register),为线程私有,如果执行的是一个Java方法,那么此时PC里保存的是 当前Java字节码的指令地址 ;如果说执行的是Native方法,那么PC的内容则为空。

2.4 方法区

主要用于存储已经由JVM加载的 类信息、常量、静态变量、即时编译器编译后的代码 等数据。在Hotspot JVM中,设计者使用“永久代”来实现方法区,这样带来的好处是,JVM可以不用再特别的写代码来管理方法区的内存,而可以像管理 JVM 堆一样来管理。带来的弊端在于,这很容易造成内存溢出的问题。我们知道,JVM的永久代使用 --XX:MAXPermSize来设定永久代的内存上限。 值得一提的是,运行时常量池也属于方法区的一部分,所以,它的大小是受到方法区的限制的。运行期间也可以将新的常量放入池中,运用的比较多的就是String的intern()方法。 注意:

  • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是 方法区 )中;
  • 在JDK7.0版本,字符串常量池被移到了 中了。

2.5 本地方法栈

与JVM栈非常类似,只不过,本地方法栈是为 Java的Native method方法 调用的时候服务。JVM规范并未定义如何实现该区域,但是,在Hotspot JVM中,本地方法栈和JVM栈合二为一,所以,本地方法栈同样会抛出 StackOverflowErrorOutofMemoryError

3 GC(Garbage Collection)回收机制

3.1 基本概念

垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收 无任何对象引用的对象占据的内存空间 的一种机制。

  • 引用 :如果Reference类型的数据中存储的数值代表的是 另外一块内存的起始地址 ,就称这块内存代表着一个引用。(引用都有哪些?对垃圾回收又有什么影响?)
  • 垃圾无任何对象引用 的对象(怎么通过算法找到这些对象呢?)。
  • 回收 :清理“垃圾”占用的 内存空间 而非对象本身(怎么通过算法实现回收呢?)。
  • 发生地点 :一般发生在 堆内存 中,因为大部分的对象都储存在堆内存中(堆内存为了配合垃圾回收有什么不同区域划分,各区域有什么不同?)。
  • 发生时间 :程序 空闲时间 不定时回收(回收的执行机制是什么?是否可以通过显示调用函数的方式来确定的进行回收过程?)

3.2 判断 “垃圾” 的方法

垃圾收集器会对堆进行回收前,判断对象中哪些是“存活”,哪些是“死亡”。

  1. 引用计数算法

    每当一个地方引用它时,计数器+1;引用失效时,计数器-1;计数值=0 —— 不可能再被引用。 缺点:对象之间相互矛盾 循环引用 的问题。

  2. 可达性分析算法

    把一系列“GC Roots”作为起始点,从节点向下搜索,路径称为 引用链 ,当一个对象 到GC Roots没有任何引用链相连 ,即不可达时,则证明此对象时不可用的。

3.3 垃圾回收算法

  1. 标记清除算法

    标记-清除(Mark-Sweep)算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段: 标记阶段清除阶段 。一种可行的实现是,在标记阶段,首先通过根节点, 标记所有从根节点开始的可达对象 。,未被标记的对象就是未被引用的垃圾对象(好多资料说标记出要回收的对象,其实明白大概意思就可以了)。然后,在清除阶段, 清除所有未被标记的对象

    JVM 内存布局与GC算法
  2. 标记整理算法

    标记整理算法类似与标记清除算法,不过它标记完对象后,不是直接对可回收对象进行清理,而是让所有存活的对象都 向一端移动 ,然后直接 清理掉边界以外的内存

    JVM 内存布局与GC算法
  3. 复制算法

    复制算法可以解决效率问题,它将可用内存按容量划分为 大小相等 的两块,每次只使用其中的一块,当这一块内存用完了,就 将还存活着的对象复制到另一块上面 ,然后 再把已经使用过的内存空间一次清理掉 ,这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可(还可使用TLAB进行高效分配内存)。

    JVM 内存布局与GC算法

3.4 垃圾回收流程

在JVM的内存空间中把堆空间分为 年老代年轻代 。将大量(据说是90%以上) 创建了没多久就会消亡的对象 存储在年轻代,而年老代中存放 生命周期长久的实例对象 。年轻代中又被分为Eden区(圣经中的伊甸园)、和两个Survivor区。新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到年老区。

JVM 内存布局与GC算法
  1. 新建的对象,大部分存储在 Eden区 中。
  2. 当Eden内存不够,就进行Minor GC释放掉不活跃对象;然后 将部分活跃对象复制到Survivor区 中(如Survivor1),同时清空Eden区。
  3. 当Eden区再次满了, 将Survivor1中不能清空的对象存放到另一个Survivor中 (如Survivor2),同时将Eden区中的不能清空的对象,复制到Survivor1,同时清空Eden区。
  4. 重复多次( 默认15次 ):Survivor中没有被清理的对象就会复制到 老年区(Old)
  5. 当Old达到一定比例,则会触发Major GC释放老年代。
  6. 当Old区满了,则触发一个一次完整的垃圾回收(Full GC)。
  7. 如果内存还是不够,JVM会抛出内存不足,发生oom,内存泄漏。
原文  https://juejin.im/post/5da5c966e51d4577e9749bbe
正文到此结束
Loading...