java 局部变量表中Variable Slot复用带来的内存回收问题

java 局部变量表中Variable Slot复用带来的内存回收问题

背景介绍

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域
下图是jdk8后的JVM内存布局,引用于https://www.cnblogs.com/czwbig/p/11127124.html
在这里插入图片描述

从图中可以看到,栈帧是虚拟机栈中的元素,是一个方法在内存中的实体映射,局部变量表则是存储方法中的局部变量的一张表。

局部变量表中一般一个Variable Slot存储一个变量,除了64位的类型变量要用两个slot。
slot可以复用,方法中的变量的作用域不一定是整个方法,在离开变量作用域后,对应的slot可以给其他变量使用。
slot复用可能会引起内存回收的问题,先上代码

public class Main {
    public static void main(String[] args) {
        fun1();
        fun2();
        fun3();
    }


    private static void fun1() {
        System.out.println("fun1 gc");
        byte[] placeholder = new byte[64 * 1024 * 1024];
        System.gc();
    }

    private static void fun2() {
        System.out.println("fun2 gc");
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        System.gc();
    }

    private static void fun3() {
        System.out.println("fun3 gc");
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        int a=0;
        System.gc();
    }
}

执行过程与结果如下

C:\Users\account\IdeaProjects\StackFrameReuseTest\src\main\java>javac Main.java

C:\Users\account\IdeaProjects\StackFrameReuseTest\src\main\java>java -verbose:gc Main
[0.031s][info][gc] Using G1
fun1 gc
[0.221s][info][gc] GC(0) Pause Full (System.gc()) 66M->65M(192M) 2.252ms
fun2 gc
[0.223s][info][gc] GC(1) Pause Young (Concurrent Start) (G1 Humongous Allocation) 66M->65M(192M) 0.349ms
[0.223s][info][gc] GC(2) Concurrent Cycle
[0.278s][info][gc] GC(2) Pause Remark 130M->65M(192M) 0.471ms
[0.282s][info][gc] GC(3) Pause Full (System.gc()) 65M->65M(192M) 1.868ms
[0.282s]fun3 gc[info][gc]
 GC(2) Concurrent Cycle 59.368ms
[0.283s][info][gc] GC(4) Pause Young (Concurrent Start) (G1 Humongous Allocation) 66M->65M(192M) 0.409ms
[0.284s][info][gc] GC(5) Concurrent Cycle
[0.299s][info][gc] GC(5) Pause Remark 130M->65M(192M) 0.502ms
[0.318s][info][gc] GC(6) Pause Full (System.gc()) 65M->0M(10M) 17.656ms
[0.319s][info][gc] GC(5) Concurrent Cycle 34.272ms

代码解析

在三个函数fun1 fun2 fun3都生成64MB的数据,随后做内存回收

  • fun1中是最简单的方式,placeholder的作用域是整个fun1,所以gc后内存没有被回收([0.221s][info][gc] GC(0) Pause Full (System.gc()) 66M->65M(192M) 2.252ms)

  • fun2中加了个花括号限制placeholder的作用域,在括号外gc,结果发现内存还是没被回收([0.282s][info][gc] GC(3) Pause Full (System.gc()) 65M->65M(192M) 1.868ms)

原因:虽然已经离开了placeholder的作用域,但方法fun2的局部变量表中某个slot还存在着对placeholder的引用,在gc前没有任何对局部变量表的读写操作,该slot就没有被复用,此时gc就不会回收placeholder的内存,因为还存在引用

  • fun3基于fun2在gc前定义了另一个局部变量a,此时placeholder所在的slot会被复用,解除对placeholder的引用,随后gc即可回收其内存([0.318s][info][gc] GC(6) Pause Full (System.gc()) 65M->0M(10M) 17.656ms)

适用情景

一个方法中前面定义了占用大量内存,但后面已经不再使用的变量,而方法后面的操作需要大内存或者耗时长,此时使用上述fun3的方法促使变量内存被快速回收,有利于提高方法运行的性能。

参考

《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》第八章

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值