java并不是第一门使用内存动态分配和垃圾收集的语言,最早的是1960年的Lisp语言
垃圾回收的三个问题:
java的垃圾收集机制,极大的提高了开发的效率,至今仍然在发展迭代
垃圾的定义:
如果不能及时的对内存中的垃圾进行清理,这些垃圾对象就会占用内存空间一直保留到应用程序结束,被保留的空间无法被其他对象使用,有可能会造成内存溢出
为什么需要GC:
在c/c++的时代,垃圾回收是手动进行的,使用new关键字申请,使用delete关键字进行内存的释放,这样会很灵活,但是管理内存会很繁琐,如果忘记释放,会导致内存泄漏,随着时间的增加,内存的使用会越来越高,有可能造成内存溢出,进而导致程序的崩溃
但是有了垃圾回收以后,就不需要手动的释放了。自动的内存分配和垃圾回收是现代开发语言的
java会对堆和方法区进行垃圾回收,重点是堆区(栈没有GC,pc寄存器不仅没有GC也没有oom)
从次数上来说:
对每个对象都保存一个整型的引用计数器属性,用于记录对象被引用的情况,只要对象被引用了计数器就加1,引用失效就减1,当计数器为0,就表示不在被引用
实现简单,便于标识,判定效率高,没有延迟
所以java没有采用这种算法,但是有的语言是使用了的,比如python
可达性分析的思路:
如果想要使用可达性分析算法来判断内存是否可回收,分析工作必须在能保证一致性的快照中进行,如果不能满足的话,分析的结果也会无法保证,所以这就是GC必须要 " Stop The World" (sew)将用户线程停下来的重要原因,即使是号称不会停顿的CMS 收集器,枚举根节点时,也是必须停顿的
Gc Roots 包括下面的中元素:
java提供了对象终止(finalization)机制,允许开发人员提供对象被销毁之前的自定义处理逻辑
当垃圾回收器发现没有引用指向一个对象,也就是垃圾回收此对象之前,总会先调用这个对象的finalize()方法
finalize()允许被子类重写,用于对象被回收时进行资源的释放,例如关闭文件、数据库连接等
object的finalize() 是一个空方法,这个方法不建议主动调用,应该交给垃圾回收机制调用,具体原因有:
finalize()方法和c++的析构函数比较相似,但是java采用的是基于垃圾回收器的自动内存管理机制,有本质区别
由于finalize 的存在,虚拟机中的对象一般处于三种可能的状态(没有被任何对象引用,可以认为对象已经死亡,但是并非一定要清除,在特定的条件下,对象有可能会复活):
只有在不可触及时,才会被回收
一个对象是否要被回收,至少要经过两次标记过程
当标记完垃圾后,就需要进行垃圾回收了,释放掉无用对象所占用的空间,以便由足够的空间为新对象分配内存,目前jvm中比较常见的三种垃圾收集算法为:
是一种非常基础、常见的垃圾收集算法,1960年就应用于Lisp语言
当堆中的有效空间被耗尽的时候,就会停止整个程序,进行标记和清除两项工作
这种算法比较基础,但是效率不高(需要进行两次遍历);在GC的时候需要停止整个程序,用户体验很差;这中方式清理出来的内存不是连续的,容易产生内存碎片,还需要维护一个空闲列表
清除,并不是真的赋空,而是把需要清除的对象的地址保存在空闲列表中,当有新的对象需要分配内存时,如果垃圾的空间足够,就会被新对象覆盖
为了解决标记-清除算法效率不高的问题,与1963年提出
将活着的内存空间分为两块,每次只使用一块,在垃圾回收时将正在使用的内存中的存活对象复制到违背使用的内存块中,之后清除正在使用的内存块,交换两个内存的角色,就完成了垃圾回收
复制算法的优点:
缺点是:
又称标记-整理算法,复制算法适用于存活对象较少的场景,例如新生代,但是对于老年代来说,存活对象会很多,复制的成本会很高,而且老年代空间很大,有一半空间不使用,代价很大
标记-清除算法确实可以用于老年代,但是效率并不理想,而且会有内存碎片问题(老年代有可能会有很多大对象,内存碎片问题更加严重),所以在标记-清除算法的基础上产生了标记-压缩算法(1970年)
标记-压缩算法的最终效果等同于标记-清除算法执行完成后,在进行一次内存碎片整理,因此也可以称为标记-清除-压缩算法,两者的区别就是是否移动对象,是否移动存活对象是优缺点并存的
优点:
缺点是:
三种算法各有优劣,没有最好的算法,只能是具体问题具体分析
分代收集算法基于:不同的对象的生命周期不同,因此不同生命周期的对象采取不同的收集方式,以便提高回收效率,一般是把对分为新生代、老年代,不同的年代采用不同的算法,提高垃圾回收的效率
目前所有的垃圾回收器都是采用分代收集算法来实现的
新生代采用复制算法(不光是幸存者区,从伊甸园区到幸存者区也是复制算法)
老年代一般采用标记-清除,标记-压缩算法两种算法结合使用:
上面的几个算法,不可避免的都会暂停用户线程,等待垃圾回收完成,如果垃圾回收时间很长,会很影响用户体验,所以就有了实时垃圾收集算法:增量收集算法的诞生
核心思想是:如果一次性处理所有垃圾,可能会造成系统的长时间停顿,可以让垃圾收集线程和用户线程交替执行,每次只收集一小片区域的内存,接着切换到用户线程继续执行,依次反复,直到垃圾收集执行完成
增量收集算法的基础仍然是标记-清除和复制算法,着重处理了线程间冲突的问题,允许垃圾收集线程分段完成任务
虽然减少了系统卡顿的时间,但是这种算法会造成线程间的频繁切换,使得垃圾回收的总成本上升,降低了吞吐量
一般来说,堆空间越大,一次垃圾回收的时间就越长,为了控制回收时间,可以把大块的内存区域分割成多个小区间,每个小的区间都独立使用,独立回收
分代:分为年轻代、老年代,
分区:把堆划分成连续的小区间region