1 强引用
强引用回收机制
强引用需要切断引用关系,才可以让对象被垃圾回收器正常回收
示例:
@Test
fun testMemoryLeak(){
var m:M? = M()
System.gc()
println("打印m对象 == $m")
m = null
System.gc()
println("置空之后打印m == $m")
}
结果:
打印m对象 == com.example.testdemo2.M@6f53b8a
置空之后打印m == null
该对象被回收了finalize
2 弱引用
弱引用的回收机制
弱引用只要java触发gc垃圾回收,被弱引用持有的对象就会被回收掉。
示例:
@Test
fun testWeakReference(){
val ref = WeakReference<Person>(Person())
println("打印弱引用持有的对象 == ${ref.get()}")
Thread.sleep(5000)
println("打印弱引用持有的对象 == ${ref.get()}")
Thread.sleep(5000)
System.gc()
println("GC之后打印弱引用持有的对象 == ${ref.get()}")
}
class Person{
var name = "zhangsan"
}
结果:
打印弱引用持有的对象 == com.example.testdemo2.ExampleUnitTest$Person@6b695b06
打印弱引用持有的对象 == com.example.testdemo2.ExampleUnitTest$Person@6b695b06
GC之后打印弱引用持有的对象 == null
3 软引用
软引用回收机制
软引用会在jvm分配内存不足的时候,释放回收其引用的对象,在触发gc时,只要内存分配足够,是不会被回收的,只有到使用内存大于分配内存的时候才会回收该对象的引用。
示例
@Test
fun testSoftReference() {
val softRef = SoftReference<ByteArray>(ByteArray(1024 * 1024 * 10))
println("打印软引用 == ${softRef.get()}")
System.gc()
Thread.sleep(500)
println("打印软引用 == ${softRef.get()}")
val softRef2 = SoftReference<ByteArray>(ByteArray(1024 * 1024 * 12))
println("打印软引用 == ${softRef.get()}")
}
结果:
jvm分配空间为20M的情况下,就会造成软引用中的对象被回收掉
打印软引用 == [B@58a9760d
打印软引用 == [B@58a9760d
打印软引用 == null
4 ThreadLocal的使用
ThreadLocal 自带线程隔离机制,因为ThreadLocal在设置对象的时候,会用当前的线程作为key存储当前设置的引用对象,所以每个线程中只能获取到自己存放的引用对象,无法获取其他线程中存储的值,从而起到的线程隔离的作用。
示例
@Test
fun testThreadLocal(){
val threadLocal = ThreadLocal<Person>()
Thread{
threadLocal.set(Person())
println("线程1 == 打印threadLocal中的存储的信息 == ${threadLocal.get()}")
threadLocal.remove()
println("线程11 == 打印threadLocal中的存储的信息 == ${threadLocal.get()}")
}.start()
Thread{
println("线程2 == 打印threadLocal中的存储的信息 == ${threadLocal.get()}")
}.start()
}
class Person{
var name = "zhangsan"
}
结果
线程2 == 打印threadLocal中的存储的信息 == null
线程1 == 打印threadLocal中的存储的信息 == com.benben.TestDataDemo.TestDataDemoApplicationTests$Person@6388e817
线程11 == 打印threadLocal中的存储的信息 == null
从结果上看 只有线程1的日志中正常拿到了设置进去的Person对象,线程11之所以没有拿到 是因为当前线程中的threadLocal对象已经将存储的信息进行了remove,线程2拿不到的原因是因为不在同一个线程中,即使使用同一个threadLocal对象也无法获取相同的map容器,所以获取存储对象的时候拿到了null
5 集合容器内存泄漏示例
@Test
fun testListLeak() {
var list: MutableList<M?>? = mutableListOf<M?>()
for (i in 0 until 10) {
var m: M? = M(i)
list?.add(m)
m = null
}
System.gc()
println("当前容器中的数量 = ${list?.size}")
}
运行结果:
当前容器中的数量 = 10
结论:
主动调用了System.gc() 启动垃圾回收,手动给集合中的每个m引用都置为空,这是观察集合中m对象的释放情况,并没有看到m对象被回收的日志,原因是我们虽然置空了每个m对象的引用,但是对象的示例还是存在集合当中,所以垃圾回收期回收不了当前的10个m对象。
解决方案
@Test
fun testListLeakSuccess() {
var list: MutableList<M?>? = mutableListOf<M?>()
for (i in 0 until 10) {
var m: M? = M(i)
list?.add(m)
m = null
}
list?.clear()
list = null
System.gc()
println("当前容器中的数量 = ${list?.size}")
}
运行结果
当前容器中的数量 = null
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
该对象被回收了finalize
在集合容器List不用的时候,可以清理掉内部的内容,并且切断list对象和示例的直接引用关系(设置list = null),
这样在垃圾回收器启动回收的时候,发现该集合属于垃圾,就可以正常的进行回收了,并且可以正常把内部的M也同时清理掉。