java清空缓存实验

在日常开发中我们经常用map等容器作为缓存,当程序执行完时一般都需要清除缓存,这些容器通常也提供clear方法。Java中不需要我们手动释放内存,我们只需不引用不需要的对象,gc自会帮助我们释放这些对象的内存。不引用不需要的对象即是将指向要清除的对象的引用置为null,这正是一般容器的clear方法所做的。 但有些时候 用常规的clear方法可能不会起到想象中的效果,未能正确清除某些对象很可能会造成内存泄漏。
以下用jconsole工具实验一个简单的清除缓存的场景。

示例代码1

public static void main(String[] args) throws IOException {
        HashMap<Integer, Map<Integer, Object>> map = new HashMap<>();
        System.in.read();
        // 向map添加元素
        for (int i = 0; i < 1000; i++) {
            map.put(i, new HashMap<>());
            for (int j = 0; j < 1000; j++) {
                map.get(i).put(j,new Object());
            }
        }
        // 保存map中元素的引用
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(map.get(i).get(i));
        }
        // 打印对象hashcode
        System.out.println(list.get(1).hashCode());
        System.out.println(map.get(1).get(1).hashCode());
        // 挂起等待
        System.in.read();
        System.in.read();

        // 清理方式一
        map.clear();

        // 挂起等待
        System.in.read();
        System.in.read();
        // 打印对象hashcode
        System.out.println(list.get(1).hashCode());
    }

启动该main方法后,用jconsole工具查看之。

输入任意字符,进入向map中添加元素的步骤。

此时jconsole可以看到堆内存明显上升:

java 缓存信息 每天清除 java如何清除缓存_HashMap

此时控制台输出为,可见map中的对象与保存至列表中的对象是同一个:

1   // 输入
1637506559
1637506559

此时在jconsole中手动执行gc,情况如下图,可见内存变化不大,之前map中的内存没有被释放。

java 缓存信息 每天清除 java如何清除缓存_HashMap_02

再键入任意值,让程序执行clear方法,然后再手动执行gc,如下图,可见,内存占用明显减少,map中元素内存被释放。

java 缓存信息 每天清除 java如何清除缓存_java 缓存信息 每天清除_03

程序输出:

1 // 输入
1637506559
分析

list中存有了对map中的小map中value对象的引用,当map执行clear对象后,map中的小map无对象引用,变为不可达状态,便被gc回收释放,而每个小map中的value对象被list引用,所以这些value对象不会被释放,所以程序最后依然能输出对应的hashcode。

示例代码2

public static void main(String[] args) throws IOException {
        HashMap<Integer, Map<Integer, Object>> map = new HashMap<>();
        System.in.read();
        // 向map添加元素
        for (int i = 0; i < 1000; i++) {
            map.put(i, new HashMap<>());
            for (int j = 0; j < 1000; j++) {
                map.get(i).put(j,new Object());
            }
        }
        // 保存map中元素的引用
        List<Object> list = new ArrayList<>();
        List<Map> mapList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(map.get(i).get(i));
            mapList.add(map.get(i));
        }
        System.out.println(list.get(1).hashCode());
        System.out.println(map.get(1).get(1).hashCode());
        System.out.println(mapList.get(1).hashCode());
        System.out.println(map.get(1).hashCode());
        System.in.read();
        System.in.read();

        // 清理方式一
        map.clear();
        
        System.in.read();
        System.in.read();
        System.out.println(list.get(1).hashCode());
        System.out.println(mapList.get(1) == null ? "null" : mapList.get(1).hashCode());
        System.out.println(mapList.get(1));
        System.out.println(mapList.get(1).hashCode());
    }

与示例1中相同的操作,向map中添加元素后,堆内存明显上升:

java 缓存信息 每天清除 java如何清除缓存_System_04

此时控制台输出如下,可见map中元素与list中对应元素引用同一个地址。

1  // 输入
1637506559
1637506559
-949552894
-949552894

再输入任意字符,执行map的clear方法,然后在jconsole手动执行gc,对内存变化如下图:

java 缓存信息 每天清除 java如何清除缓存_java 缓存信息 每天清除_05

可见,这次clear方法执行后gc时并不能将map中的元素回收,因为listMap中持有着对map中元素的引用,所以这些元素是可达状态,不能被回收。

示例代码3

public static void main(String[] args) throws IOException {
        HashMap<Integer, Map<Integer, Object>> map = new HashMap<>();
        System.in.read();
        // 向map添加元素
        for (int i = 0; i < 1000; i++) {
            map.put(i, new HashMap<>());
            for (int j = 0; j < 1000; j++) {
                map.get(i).put(j,new Object());
            }
        }
        // 保存map中元素的引用
        List<Object> list = new ArrayList<>();
        List<Map> mapList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(map.get(i).get(i));
            mapList.add(map.get(i));
        }
        System.out.println(list.get(1).hashCode());
        System.out.println(map.get(1).get(1).hashCode());
        System.out.println(mapList.get(1).hashCode());
        System.out.println(map.get(1).hashCode());
        System.in.read();
        System.in.read();

        // 清理方式二
        for (Map m : map.values()) {
            m.clear();
        }
        map.clear();

        System.in.read();
        System.in.read();
        System.out.println(list.get(1).hashCode());
        System.out.println(mapList.get(1) == null ? "null" : mapList.get(1).hashCode());
        System.out.println(mapList.get(1));
        System.out.println(mapList.get(1).hashCode());
    }

同样的操作,向map中添加内容然后手动gc,情况如下:

java 缓存信息 每天清除 java如何清除缓存_HashMap_06

再执行清理方式二,便是每一个map中的小map都执行一次clear方法,之后再手动gc,情况如下:

java 缓存信息 每天清除 java如何清除缓存_System_07

可见,这次map中元素对象内存被成功回收。