1.为什么要有垃圾回收?
例如在C语言中,通过malloc在堆(和JVM的堆不太一样)里申请内存之后,就得再通过free手动释放内存空间,或者等到程序结束才会释放,如果忘记释放就可能会导致"内存泄漏"
内存泄漏:内存越用越多,可以用的空间越来越少,,空间逐渐用完,程序就会出现问题.
所以申请空间之后就得靠人为记得手动释放空间,但是只要涉及到人为,就会有不靠谱的时候,所以JVM就实现了垃圾回收这样的机制(garbage collection)简称GC.
2.垃圾回收主要回收哪个内存区域?
栈:栈里面是放的是局部变量,它们出了自己的作用域之后就自动回收了,所以不用刻意去回收.
程序计数器:每个线程有一个,所需空间是固定的,在线程销毁的时候跟着销毁就行了.
方法区:放的是类对象,主要的工作是"类加载",而很少涉及"类卸载",所以需要GC不必太迫切.
堆:这里放的是new出来的对象,这里是GC最主要的工作地点.new出来的对象用完之后就需要回收.
3.JVM怎么知道谁是垃圾呢?
在垃圾回收中有两个典型的判定方法:1.引用计数 2.可达性分析
在JVM中使用的是可达性分析
什么是可达性分析呢?
就是我们设置一些起点作为GCRoot,从这些GCRoot开始去寻找可以访问到的对象,将他们标记为"可达",当这些标记完之后,剩下的那些就是"不可达"的也就是需要回收的垃圾了.
哪些可以作为GCRoot呢?
1.局部变量表中的局部变量 (每个线程中有一个栈,每个栈中有很多栈帧,每个战帧里有一个自己的局部变量表)
2.常量池中的对象
3.方法区中静态引用类型的成员
什么是引用计数呢?
就是使用额外的计数器记录一个对象被多少个引用指向.如果有新的引用指向这个对象,那引用计数就+1,如果有指向这个对象的引用有减少,则计数也会跟着-1.直到计数为0的时候就意味着没人引用它,也就成了垃圾了.此时就会被回收.
但是引用计数有两个比较大的问题:
1.对象如果不大,空间成本高
假如这个对象比较大的话就没什么问题,假设这个对象是1000Byte,程序计数器是4Byte
那加起来也就1004Byte
但是如果这个对象很小,只有2Byte,那加上计数器(4Byte1)就是6Byte,是原来的3倍大,空间成本太高辣!
2.有循环引用的问题
在下面的操作之后就会有循环引用的问题.
两个对象现在相互引用了,这样计数器既不是0,也拿不到对象了,就会一直占着内存.我们就称为循环引用.
4.垃圾回收的过程
但是这种清理方法会形成"内存碎片",内存并不是连续的,但是以后new对象的时候要的是连续的空间,所以这样清理出来的空间也不太能用.
解决方法使用"复制算法"也就是将标记的对象复制到另外一片内存上,再将原来的整片内存清空
这种方法有点类似顺序表的删除操作,优点是可以有效解决"内存碎片问题",缺点是有点耗时.
典型的垃圾回收器:Serial, ParNew, Parallel, Serial Old, Parallel Old, CMS, G1