【Java编程思想读书笔记】第五章:垃圾回收机制
参考书目:《Java编程思想》(第四版)
阅读《Java编程思想》(第四版)一书收获颇多,之所以想通过用博客记笔记的方式来读书,是因为这样可以倒逼自己仔细、反复地阅读书中的知识,找相对意义上的重点,并且由于人脑更适合输出型的学习,通过将内容边看、边记、边理解和边打字写文,相比直接反复阅读更有效益(当然这本书后续仍需反复阅读几十遍甚至百遍以上都不为过),而另一个原因就是这样也能够在阅读中通过博客来记录自己的学习历程,博客记录的不只是图文,它们记录的正是自己的成长,等以后毕业或者工作后,回首大学四年,多少有点可以回念的东西,还可以说一句:一路走来,我读了很多前辈们的好书,对技术充满了热情,永远在不断学习的路上。
start.作者讲授思路:
1.finalize()方法使用场景:当你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收机制只释放那些经由new分配的内存,所有它不知道该如何释放该对象的这块“特殊”内存,因此为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。
2.finalize()的工作原理:一旦垃圾回收器准备释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的空间。
3.也许你会发现,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。
4.无论是“垃圾回收”还是“终结”,都不保证一定会发生,如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。
5.Java的垃圾回收器在工作时,一边回收空间,一边使堆中的对象紧凑排列,这样“堆指针”就可以很容易地移动到相对于已使用内存区域更紧凑的空闲内存区域,也就尽量避免了页面错误(如果Java中的堆像传送带一样工作并且“堆指针”只是简单地移动到尚未分配的区域,那么势必会导致频繁的内存页面调度——将其移进移出硬盘,因此会显得需要拥有比实际需要更多的内存。页面调度会显著影响性能,最终,在创建了足够多的对象后,内存资源将耗尽),通过垃圾回收器对对象的重新排列,实现了一种高速的、有无限空间可供分配的堆模型。
6.关于其他系统的垃圾回收机制:
引用记数:是一种简单但速度很慢的垃圾回收技术,每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1,当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间。这种方法也有缺陷,如果对象之间存在循环引用,可能会出现“对象该被回收,但引用计数不为0”的情况,对于垃圾回收器而言,定位这样的交互自引用的对象组所需的工作量极大。
一些更快的模式:对于任何“活”的对象,一定能追溯到其存活在堆栈活静态存储区之中的引用,因此,遍历堆栈和静态存储区的所有引用,找到每个引用对应的对象,然后是此对象的所有引用,如此反复,知道“根源于堆栈或静态存储区的引用”所形成的网络全部被访问为止。而“交互式引用的对象组”根本不会干扰此模式,因此也就被解决。
7.Java虚拟机的垃圾回收机制:Java虚拟机采用一种自适应的垃圾回收技术,至于如何找到存活的对象,取决于不同的Java虚拟机实现。
8.停止-复制模式的垃圾回收器:
概述:是Java虚拟机采用的一种自适应的垃圾回收技术,它不属于后台回收模式,工作原理是先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所有新堆保持紧凑排列,然后就可以简单、直接地分配空间了。
缺陷:
(1)“复制式回收器”效率会降低:大量复制行为需要时间开销,而且需要两个堆,然后在这两个堆直接来回倒腾,从而又需要维护比实际需要多一倍的空间。
(2)其次在于复制,程序进入稳定状态后,可能只会产生少量垃圾,甚至没有垃圾,而不断的复制会造成浪费。
改进:
(1)从堆中分配几块较大的内存,复制动作发生在这些大内存块之间。内存分配以较大的“块”为单位,如果对象较大,它就会占用单独的块,垃圾回收器在回收的时候可以往废弃的块里拷贝对象,每个块都有相应的代数来记录它是否还存活,通常,如果块在某处被引用,其代数就会增加,垃圾回收器将对上次回收之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理工作——大型对象仍不会被复制(只是其代数会增加),内含小型对象的那些块则被复制并整理。
(2)一些Java虚拟机会进行监视,如果没有新垃圾产生,所有对象都很稳定,垃圾回收器效率很低的话,就会转换到另一种工作模式(即”自适应“),这种模式称为“标记-清扫”(此模式的思路仍是从堆栈或静态存储区遍历所有引用,进而找出存活的对象,每当它找到一个存活对象,就会给对象一个标记,这个过程不会回收任何对象,只要全部标记工作完成后,清理动作才会开始,在清理过程中,没有标记的对象占用的空间将被释放不发生复制操作,所以剩下的堆空间是不连续的,如希望得到连续的空间,则需重新整理剩下的对象)。同样,Java虚拟机会监视”标记-清扫“模式,如果堆空间中出现很多”碎片“,就会切换回”停止-复制“模式,这就是”自适应“技术。
9.Java虚拟机中有许多附加技术用以提升速度,尤其是与加载器有关的操作。
10.即使(Just-In-Time, JIT)编译器技术:
概述:把程序全部或部分翻译成本地机器码(这本来是JVM的工作),程序运行速度会得以提升,当需要装载某个类(通常是为该类创建第一个对象)时,编译器就会找到其.class文件,然后将该类的此字节码文件装入内存。
方案:
(1)方案一:(简单直接),让即使编译器编译所有代码。
(2)方案二:惰性评估,即使编译器只在必要时才编译代码,这样从不被执行的代码也许就不会被JIT所编译。JDK中的Java HotSpot技术就采用了类似方法,代码每次被执行时都会做一些优化,所以执行次数越多,它的速度就越快。
方案一的缺陷:
(1)加载动作散落在整个程序生命周期内,累加起来要花更多时间。
(2)会增加可执行代码的长度,因为字节码要比即使编译器展开后的本地机器码小很多,这将导致页面调度,从而降低程序速度。