java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JVM内存分配和回收

JVM之内存分配和回收机制

作者:吃猫的大鱼

本篇主要介绍JVM内存分配和回收策略,内容主要节选自《深入理解java虚拟机》,需要的朋友可以参考下

前言

本篇主要介绍JVM内存分配和回收策略,内容主要节选自《深入理解java虚拟机》。

一、内存分配策略

1. 堆内存模型

在这里插入图片描述

组成:

大多情况下,对象在新生代Eden区中分配。当Eden没有足够的空间分配对象时虚拟机会发起一次Minor GC。

2.2 大对象直接到老年代

大对象即需要大量连续内存空间的对象(例如很长的字符串及数组)。虚拟机提供了一个-XX:PretenureSizeThreshoId参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在Eden区及两个区之间发生大量的内存复制。注意PretenureSizeThreshoId参数只对Serial和ParNew两款收集器有效。

2.3 动态年龄判断

为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进人老年代,无须等到MaxTenuringThreshoId中要求的年龄。

2.4 内存担保机制

在发生Minor GC之前,虚拟机会先检查Survivor空间是否够用,如果够用则直接进行Minor GC。否则进行检查老年代最大连续可用空间是否大于新生代的总和,假如大于,那么这个时候发生Minor GC是安全的。假如不大于,那么需要判断HandlePromotionFailure设置是否允许担保失败。假如允许,则继续判定老年代最大可用的连续空间是否大于平均晋升到老年代对象的平均值,如果大于,这个时候可以发生Minor GC ,如果小于或者设置HandlePromotionFailure不允许担保失败,则需要做一次Full GC。通常会把HandlePromotionFailure开关打开,以减少Full GC。

2.5 长期存活对象

虚拟机给每个对象定义了一个对象年龄(Age)计数器(存在于对象头中)。如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survwor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshoId设置。

二、对象存活

判断对象存活一般有两种方式: 引用计数算法和可达性分析算法。

1.引用计数算法

原理:

  1. 引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。
  2. 对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。

优点:

  1. 实现简单,垃圾对象便于辨识;
  2. 判定效率高,回收没有延迟性。

缺点:

  1. 它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
  2. 每次复制都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
  3. 引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。

2.可达性分析算法

定义:相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。

原理:

  1. 可达性分析算法是以根对象集合(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
  2. 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)。
  3. 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
  4. 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。


在这里插入图片描述

GC ROOT 对象:

3.再谈引用

三、内存回收

1.堆内存回收

是JVM所管理内存最大的一块,也是gc回收的主要区域。

1.1 哪些对象能回收?

堆内存中对象存活是使用可达性分析算法来判断,其中非存活对象由GC回收掉。这个就是虚拟机需要回收堆的对象。

1.2 如何回收?

  1. Minor GC:新生代收集,目标只是新生代的垃圾收集;
  2. Major GC:老年代收集,目标是老年代的垃圾收集(具体说只有CMS会有单独收集老年代的行为);
  3. Full GC:收集整个java堆和方法区的垃圾收集。这里补充说明一下虽然网上很多说什么Full GC就是Major GC,在这里我要重申一下并不是,具体看书上描述如下:


在这里插入图片描述

Mixed GC:收集整个新生代以及部分老年代的垃圾收集,仅G1支持。(类似于Full GC)

1.3 什么时候回收?

Minor GC触发条件:

  1. Eden区域满了,会触发Minor GC;
  2. 新生对象需要分配到新生代的Eden,当Eden区的内存不够时需要进行MinorGC。

Major GC触发条件:

  1. 老年代区域设置的阈值空间满了,会触发Major GC;
  2. 新对象需要分配到老年代,此时老年代设置阈值可用空间不足时触发Major GC。

Full GC触发条件:

2.方法区回收

方法区主要回收废弃的常量池和不再使用类型,但这个2类对象存活的判断还不一样。

2.1 常量池

同堆的对象存活类似-可达性分析法,具体请参考之前的可达性分析法。

2.2 类型数据

以上都是我简单总结,以下是书上关于方法区回收描述的内容:


在这里插入图片描述

其实从书上就可以看出来,关于方法区OOM问题大都是在程序中是有大量使用反射、动态代理、CGLIB等框架,如果在实际开发中遇到关于可以从以上几个维度来定位问题。

总结

本篇所有理论知识都是摘抄于《深入理解java虚拟机》,有部分是自己简单总结,JVM内存分配和回收是我们在分析JVM调优和相关问题的基石,建议看完我本篇的去多看几遍《深入理解java虚拟机》。

您可能感兴趣的文章:
阅读全文