详解JVM的内存分配策略

JVM的内存结构模型由方法区、堆、虚拟机栈、本地方法区和程序计数器五个部分组成。虚拟机栈、本地方法和程序计数器是线程私有的,随着方法或线程的结束,对应的内存也被回收了,而Java虚拟机规范也指出可不对方法区(jdk8叫元空间)做垃圾收集,因而可以说垃圾收集主要关注的是堆空间。

一、对象是否需要被回收

内存回收前需要先确定那些对象已经“死亡”(没有被其他对象引用)。判断对象的存活方式有2中,分别是“引用计数算法”和“可达性分析算法”。

引用计数算法的原理跟它的名字一样明了,通过在对象中维护一个计数器判断对象是否被使用。当有一个地方引用它,则计数器+1,当引用失效,则计数器-1,任何时刻计数器值为0的对象则是不可被使用的。该算法实现简单,效率也高,但无法处理对象之间的相互循环引用的问题。

public class ReferenceCounterGC {
    public Object instance = null;
    private static final int SIZE= 1024 * 1024;
    //占点内存
    private byte[] socket = new byte[2 * SIZE];
    public static void main(String[] args) {
        ReferenceCounterGC objA = new ReferenceCounterGC();
        ReferenceCounterGC objB = new ReferenceCounterGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();  //手动GC
    }
}

通过GC日志发现,内存还是被回收了,可见JVM用的并非引用计数算法。

可达性分析算法,也叫根搜索算法。通过一些“GCRoots”对象做为起点,向下搜索对象,走过的路径称为“引用链”。当一个对象到达“GC Roots”没有任何引用链的时候,表明该对象不可用,可被回收。JVM规定可作为“GC Roots”对象的包括:虚拟机栈中的引用对象、方法区中类静态属性引用的对象,方法区中常量引用的对象,Native方法引用的对象。

二、对象逃脱

但事实上当一个对象不存在引用链时候,也不一定就会被回收。判断对象真正“死亡”还需要经过标记。

第一次标记会进行筛选,判断对象是否有必要执行finalize(),若对象没有覆盖finalize()或者finalize()方法已经被调用过,则被视作没有必要,对象会被直接回收。若有必要执行,该对象会被放置在一个F-Queue队列里,由一条低优先级的线程去执行finalize(),再回收内存。若此时对象获得新的引用链,则可逃脱被清除的命运,不过运行代价高,不确定性比较大,所以一般不推荐这种方法拯救对象。

三、关于引用

垃圾收集算法判定对象的存活其实是跟“引用”有关,一个对象存在被引用和没有被引用这两种状态。若对象没有存在引用状态,此时JVM的内存还很充足,是可以考虑保留对象在内存之中,等到内存不足时再回收。jdk通过定义不同等级的引用对这类“食之无味,弃之可惜”的对象进行划分。

不同等级的引用包含:强引用、软引用、弱引用、虚引用(也叫幻像引用),关于他们的介绍网上资料很多,这里就不再累述了,主要区别在于对象存活生命周期的不同。

四、内存分配策略

Java自动内存管理归结到底就两点:给对象分配内存以及回收对象的内存。内存区域分为新生代和老年代,新生代包括1个Eden区和2个Survivor。运行的时候可以通过添加对应参数分配指定内存空间,举个例子:

-Xms2048M -Xmx2048M -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -Xloggc:../logs/gc.log  -XX:+HeapDumpOnOutOfMemoryError

这里限制了Java堆大小为2048M,且不可扩展,Eden区和Survivor的空间比例是8:1。但整个新生代可用的内存空间其实只有9/10,因为新生代采用的是复制算法,需要保留一个Survivor作为轮换。

对象优先分配在Eden,大对象(很长的字符串以及数组)直接进入老年代。新生代连续的内存空间不足以存储对象时会触发Minor GC,老年代同理则会出发Full GC。出现Full GC会至少伴随一次Minor GC且速度比Minor GC慢10倍,因而应该尽量避免Full GC,减少Minor GC。但Minor GC时也是有可能会引发Full GC,因为存在内存空间分配担保。

新生代中的对象熬过一次Minor GC,年龄就会+1,当年龄大于一定程度(默认是15)就会进入老年代,可通过-XX:MaxTenuringThreshold设置。但也有特殊情况,当Surivor空间中相同年龄所有对象大小总和大于Surivor空间的一半,年龄大于等于该年龄的对象可以直接进入老年代,无须受到限制。

虚拟机也提供了参数 -XX:PretenureSizeThreshold,设置老年代分配的阈值,当对象大于该值则直接分配在老年代,可避免在Eden区和两个Survivor区之间发生大量的内存复制。

五、理解GC日志

最后以JDK1.8版本为例子说一说如何理解GC日志:

//这里是GC类型,Full GC表明这次GC发生了STW
//方括号内6115K->728K(9216K),含义:GC前该区域使用情况->GC后该区域使用情况(该区域总容量)
//方括号外6115K->736K(19456K),含义:GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)
//0.0039696 secs表示该区域GC占用时间,单位是秒
[GC (System.gc()) [PSYoungGen: 6115K->728K(9216K)] 6115K->736K(19456K), 0.0039696 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 728K->0K(9216K)] [ParOldGen: 8K->623K(10240K)] 736K->623K(19456K), [Metaspace: 3426K->3426K(1056768K)], 0.0083509 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
//新生代,包括eden和survivor的大小和使用情况
PSYoungGen      total 9216K, used 166K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 2% used [0x00000000ff600000,0x00000000ff629998,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
//老年代内存使用信息
ParOldGen       total 10240K, used 623K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 6% used [0x00000000fec00000,0x00000000fec9bcc0,0x00000000ff600000)
//元空间使用信息
Metaspace       used 3441K, capacity 4496K, committed 4864K, reserved 1056768K
class space    used 375K, capacity 388K, committed 512K, reserved 1048576K

七、总结

以上便是对JVM内存分配策略的一个总结,学而时习之,不亦乐乎。很高兴你能看到了这里,如果对文章内容有疑问,欢迎留言、私信交流。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芋圆在睡觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值