上海:15201841284
广州:020-2989 6995
深圳:0755-23061965
武汉:027-8798 9193
西安:029-8822 8155
Java 通过 GC(一个守护线程)隐性回收内存资源。GC 会定期检查有没有哪个对象是不可达的,准确来说,没有引用指向这个对象。如果有,GC 就会回收这块内存。
更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。
现在的问题是我们应不应该担心内存泄漏问题或者说 Java 怎么处理这个问题的?
关注一下定义:一个对象只有在无可到达(无法使用)且没有任何现有线程可以使用到它时才会被垃圾回收器回收。
所以如果一个对象在应用中没有使用但无意中被引用了,那它是不会被垃圾回收器回收的,也就存在内存泄漏的危险了。
GC关注这些不可达的对象,但是不能判断对象是否不再使用,对于应用中存在可能使用被回收的对象的情形,程序员应该格外注意相应的业务逻辑代码。可笑的小错误将会成为大问题。
内存泄漏会在很多情形下产生,让我们看看下面的例子:
package com.example.memoryleak; public class Adder { public long addIncremental(long l) { Long sum=0L; sum =sum+l; return sum; } public static void main(String[] args) { Adder adder = new Adder(); for(long i;i<1000;i++) { adder.addIncremental(i); } } }
你能指出其中的内存泄漏么?
这儿的代码有个小问题,我没有使用基本数据类型long,而是使用了封装类Long,正是如此,这儿会造成内存泄漏。因为自动装箱机制,sum=sum+l;这句在每一次循环中将会创建一个新的Long对象,因此创建了有1000个无用的对象。请避免混合使用封装数据类型和基础数据类型,如果可以,尽量使用基础数据类型。
package com.example.memoryleak; import java.util.HashMap; import java.util.Map; public class Cache { private Map<String,String> map= new HashMap<String,String>(); public void initCache() { map.put("Anil", "Work as Engineer"); map.put("Shamik", "Work as Java Engineer"); map.put("Ram", "Work as Doctor"); } public Map<String,String> getCache() { return map; } public void forEachDisplay() { for(String key : map.keySet()) { String val = map.get(key); System.out.println(key + " :: "+ val); } } public static void main(String[] args) { Cache cache = new Cache(); cache.initCache(); cache.forEachDisplay(); } }
在这里,发生内存泄漏是由于内部map数据结构。这个类是从缓存中获取显示员工的职位。 一旦它们显示完成,就不必把这些元素存储在缓存中了。
我们忘了清除缓存,尽管程序不在需要缓存中的对象,但它们不能被GC,因为map持有它们的强引用。
因此,但你使用缓存时,缓存中的对象不再需要时记得清楚它们。 另外,,你应该使用 WeakHashMap来初始化缓存。 使用WeakHashMap的好处时,一旦key不被其它对象引用,它就可以被GC回收。
WeakHashMap有许多值得讨论的地方,我将再写一篇文章介绍。务必小心使用WeakHashMap,如果你需要重新使用缓存中的数据,可能发生这样的情况:该数据可能不再被其他对象引用,那么引用可能被回收,他的值也会消失。
try { Connection con = DriverManager.getConnection(); ………………… con.close(); } Catch(Exception ex) { }
在上面的例子中,我们在try代码块中关闭了(昂贵的)连接资源,因此如果一旦发生异常,close语句跳过执行,那么连接将不会被关闭。这样,这个连接不会再返回连接池,就引发了一次内存泄漏。
请养成将关闭语句写在finally代码块中的习惯。
package com.example.memoryleak; import java.util.HashMap; import java.util.Map; public class CustomKey { public CustomKey(String name) { this.name=name; } private String name; public static void main(String[] args) { MapString> map = new HashMapString>(); map.put(new CustomKey("Shamik"), "Shamik Mitra"); String val = map.get(new CustomKey("Shamik")); System.out.println("Missing equals and hascode so value is not accessible from Map " + val); } }
就像在CustomKey中我们忘了提供equals() 和 hashcode() 的实现, 因此,存储在map中的一个键和值都不能被检索, 因为map 的get()方法检查 hashcode() 和 equals(). 但是这个元素不会被GC掉, 因为map有引用它,但应用程序却无法访问它.显示它是内存泄漏.
因些创建自己的Custom key, 总是需要提供 equals 和 hashcode() 实现.
package com.example.memoryleak; import java.util.HashMap; import java.util.Map; public class MutableCustomKey { public MutableCustomKey(String name) { this.name=name; } private String name; public String getName() { return name; } publicvoid setName(String name) { this.name = name; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MutableCustomKey other = (MutableCustomKey) obj; if (name == null) { if (other.name != null) return false; } elseif (!name.equals(other.name)) return false; return true; } public static void main(String[] args) { MutableCustomKey key = new MutableCustomKey("Shamik"); Map map = new HashMap(); map.put(key, "Shamik Mitra"); MutableCustomKey refKey = new MutableCustomKey("Shamik"); String val = map.get(refKey); System.out.println("Value Found " + val); key.setName("Bubun"); String val1 = map.get(refKey); System.out.println("Due to MutableKey value not found " + val1); } }
尽管在这里我们为custom Key提供了 equals() 和 hashcode(), 将它保存在map中后我们没有刻意的改变它.如果它的属性被改变,那么这个元素将永远不会被应用程序找到,但是map持有它的一个引用,因此这里会发生内存泄漏.
总是让你的 custom key 不可变.
package com.example.memoryleak; public class Stack { privateint maxSize; privateint[] stackArray; privateint pointer; public Stack(int s) { maxSize = s; stackArray = newint[maxSize]; pointer = -1; } public void push(int j) { stackArray[++pointer] = j; } public int pop() { return stackArray[pointer--]; } public int peek() { return stackArray[pointer]; } publicboolean isEmpty() { return (pointer == -1); } public boolean isFull() { return (pointer == maxSize - 1); } public static void main(String[] args) { Stack stack = new Stack(1000); for(int ;i<1000;i++) { stack.push(i); } for(int ;i<1000;i++) { int element = stack.pop(); System.out.println("Poped element is "+ element); } } }
在这里,当栈第一次增长后接着收缩时我们面临一个棘手的问题。实际上由于内部实现,栈内部有一个数组,对外部程序来说,栈的活动部分是指向该数组的指针。
所以当堆栈增长到1000时,内部的数组也就被元素填满了,但后面当我们弹出所有元素后,指针指到了0,对应用程序来说它也变为空的了,但是内部的数组却还包含被弹出对象的引用。在Java中,我们叫它过期引用。过期引用是不能再次被引用的引用。
这个引用不能被回收,因为数组中还保留着被弹出后就不需要的元素。
更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。
为了解决这个问题,我们需要在弹出操作中把值设为null,这样这些对象才能被回收。
public int pop() { int size = pointer-- int element= stackArray[size]; stackArray[size]; return element; }
北京京南校区:北京亦庄经济开发区科创十四街6号院1号楼 赛蒂国际工业园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE+微服务+大数据 大数据+机器学习+平台架构 Python+数据分析+机器学习 人工智能+模式识别+强化学习 WEB前端+移动端+服务端渲染
地址:陕西省西安市高新区西安软件园西区创新信息大厦A座三层尚学堂
电话:029-88228155 / 18291433445
山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702
武汉学区地址:武汉市东湖高新区光谷金融港B22栋11楼
咨询电话:027-87989193
网址:http://www.cssxt.com/
咨询电话:0731-83072091
深圳校区地址:深圳市宝安区航城大道U8智造产业园U6栋3楼
咨询电话:0755-23061965 / 18898413781
上海尚学堂校区地址:上海市浦东新区城丰路650号
咨询电话:021-67690939
广州校区地址:广州市天河区车陂街道大岗路5号中侨广场2栋321室(四号线车陂站D出口,或brt车陂站)
咨询电话:18948349646
保定招生办公室
地址:河北省保定市竞秀区朝阳南大街777号鸿悦国际1101室
电话:15132423123