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可以看到堆内存明显上升:
此时控制台输出为,可见map中的对象与保存至列表中的对象是同一个:
1 // 输入
1637506559
1637506559
此时在jconsole中手动执行gc,情况如下图,可见内存变化不大,之前map中的内存没有被释放。
再键入任意值,让程序执行clear方法,然后再手动执行gc,如下图,可见,内存占用明显减少,map中元素内存被释放。
程序输出:
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中添加元素后,堆内存明显上升:
此时控制台输出如下,可见map中元素与list中对应元素引用同一个地址。
1 // 输入
1637506559
1637506559
-949552894
-949552894
再输入任意字符,执行map的clear方法,然后在jconsole手动执行gc,对内存变化如下图:
可见,这次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,情况如下:
再执行清理方式二,便是每一个map中的小map都执行一次clear方法,之后再手动gc,情况如下:
可见,这次map中元素对象内存被成功回收。