java中的GC垃圾回收机制使用的自动回收,而C使用的是手动挥手机制。
1.引用计数法:
引用计数法是一种比较古老的一种垃圾回收算法了,他是对于每一个对象都有一个计数器,如果该对象被引用了,那么计数器count就会增加1,引用失效时就会减一,当计数器为0的时候就是该对象没有被引用,这种算法很简单就是为每个独享额外添加个计数器,但是也有缺点:
(1)他的加减操作会影响系统的性能,
(2)无法处理循环引用问题,所以java垃圾回收机制中没有这种
下面举一个简单的例子:A对象和B对象,对象A中有B的引用,对象B中有A的引用。此时A和B两个对象的计数器都不是0,但在使用中没有其他对象引用A和B,说白了,A和B应该是被回收的对象,但是他们之间相互引用。计数器不为0,系统没有这种探针检测到循环引用,从而无法垃圾回收,引发内存泄露。互相引用,而没有其他引用,陷入了死循环,
解释一个可达对象,不可达对象。
可达对象:通过根节点进行引用搜索,最终可以到达的对象。
不可达对象:通过根节点进行引用搜索,最终没有被引用的对象。
2.标记清除法:
标记清除法是分为两个阶段进行的:标记阶段和清除阶段
在标记阶段,从根节点开始标记可达对象,没有被标记的就是没有被引用的对象也就是垃圾对象,之后就是清除这些没有标记的对象,但是标记清除法会造成空间碎片,因为使用标记清除法清楚地是一段连续的内存空间,回收完对象之后,内存空间是不连续的,不连续的内存空间效率是低于连续的内存空间。
3.复制算法:
复制算法的原理:将分配的内存空间分为2块,每次只是用1块,在垃圾回收呢时,讲正在使用的内存中的存活对象复制到没有被使用的内存的一块,完成之后,清除正在使用的内存块中的所有对象,然后两个内存块交换,以此完成垃圾回收。
如果系统中的垃圾对象有很多,存活对象比较少,复制算法的优势就体现出来了。由于对象是在垃圾回收过程中统一被复制到新的内存空间,所以复制算法可以确保新的内存空间是没有碎片的。但是缺点就是,把内存分为了2块,不能充分的利用内存。
如下图所示:A、B两块相同的内存,A在进行垃圾回收的时候,讲存货的对象复制到B的空间,B的空间在复制后是连续的。复制完成之后,清除A。把B设置为当前使用的空间。
在java的新生代串行垃圾回收中,使用了复制算法,新生代之前的章节也讲过分为eden区域,from、to三个部分,之前也说过from、to是相等的可以替换的两个空间、地位相等、角色相等。from、to空间也被称之为survivor空间,英文解释为幸存者,用来存放没有被回收的对象,
在垃圾回收的过程中,eden空间存活的对象被复制到survivor空间,假如我们正在使用from空间,因此den空间存活的对象被复制到to空间。如果to空间内存满了,则对象直接进入老年代,可参考虚拟机参数设置的章节参考实例。此时eden空间和from空间剩余的对象就是垃圾对象,直接清空,to空间存放的就是存活的对象,这种改进之后的算法,保证了空间的连续性,这点很重要,又避免了大量的空间的浪费,因为这些空间是可以手动分配的。参考之前的章节。上图显示了复制算法的时机回收过程。
复制算法在新生代比较适合,因为新生代大部分是垃圾对象,存活对象较少,如果在老年代的话不太合适,老年代都是存活对象,复制的话就会消耗挺多资源,复制成本较高
4.标记压缩法:所以在老年代基本上使用的是标记压缩法,标记压缩算法是在标记清除算法基础之上优化了,标记压缩算法也是从根节点开始,对对象的可达性做一次扫描标记,之后不是直接清除未标记的对象,而是将所有的存活对象压缩到内存的一端,之后,清除边界外的所有空间,这样做的好处是避免了内存的不连续性,不需要内存设置两块相同的内存进行复制交换。所以压缩标记算法的性价比高。
GC真正的垃圾:强,软,弱虚对象
垃圾回收的思想就是判断一个对象是否有可触及性,其中的含义就是该对象是否可以访问,如果对象被引用了,说明该对象正在被使用,如果发现该对象没有被引用,就说明该对象不再使用,不再使用的对象可以被回收,但是不是立即回收,取决于GC垃圾回收算法。
判断对象的可触及性:
1.可触及:从根节点开始,可以到达这个对象,说明这个对象还在使用
2.可复活的:对象所有的引用都被释放了,但是对象可能在finalize()方法中复活了
3.不可触及的:对象的finalize()被调用后,但是没有复活,那么这个对象就彻底挂了, 进 入不可及状态
finalize:
1.finalize不推荐使用,
2.finalize方法可以复活对象,但是可能会引起发生引用外泄
3.finalize是系统级的调用,你并不知道是什么时候调用,所以释放资源最好在try catch finally中
强引用:
String str = new stringbuffer();
新建的stringbuffer对象是存放在堆空间的,str是存放在栈列表上的,它的value是保存着指向stringbuffer对象的内存地址。
强引用特点:1.强引用可以直接访问对象
2.强引用对象在任何时候都不会被系统回收,虚拟机跑出oom异常也不会回收强引用对象~
3.强引用对象可能可能会造成内存泄漏
软引用:可以被回收的引用
特点:GC不一定会回收软引用,但是当内存严重不足的时候,人引用一定会被回收,所以软引用一定不会导致OOM异常,
软引用的时候可以构造一个队列,当软引用对象被回收的时候,就会加入引用队列,通过对了可以追踪对象的回收情况
弱引用:发现立即回收
在系统进行垃圾回收GC得时候,只要发现了弱引用,不管系统堆内存是否够用,都会将对象回收,但是垃圾回收期的线程的优先级比较低, 所以不一定会很快发现弱引用的对象,所以这个弱引用的对象可能会存在较长的时间,一旦一个弱引用对象被回收,
弱引用的时候可以构造一个队列,当弱引用对象被回收的时候,就会加入引用队列,通过对了可以追踪对象的回收情况。
虚引用:对象回收追踪
虚引用是所有引用类型中最弱的一个,一个持有虚引用对象。和没有引用基本一样,随时都有可能被回收,当试图使用虚引用的get()获取的时候,总会失败报错,不能获取到。虚引用必须和引用队列一起使用,它的作用就是跟踪垃圾回收的过程。
当垃圾回收器回收一个对象的时候,如果发现他是虚引用,会立马讲这个虚引用的对象加入引用队列,通知应用程序对象的回收情况。所以我们在这里看一下队列如何使用。(除了强引用不能使用队列,其他的三个都可以使用,虚引用是必须使用队列)
软、弱、和虚 引用都可以放置到队列中,强引用不需要,虚引用必须要使用队列。
软引用可以放一些缓存的数据,当内存不足的时候被回收
软引用在设备内存比较少的时候特别有用,比如android系统。
一个android应用如果设计到通过网络获取图片,为了让系统更快的运行和更节省流量我们可以将已经下载下来的图片缓存起来,当第二次浏览到该图片时就可以从缓存中拿。
缓存的方式有:一是放在系统内存中这样效率最高,二是把文件写到外部存储器上。但是就目前而言android系统的内存是非常的有限的不可能像PC机那样配置那么高的内存,而且外部存储器的容量也是有限的。
如何我们用SoftReference的方式存储在内存中是一中很好的解决方法(当然不止这一种)。
虚引用对象可以跟踪对象的回收时间,所以,可以将一些资源释放操作放在虚引用中执行和记录。