首页 > 代码库 > TIJ读书笔记06-终结清理和垃圾回收
TIJ读书笔记06-终结清理和垃圾回收
- TIJ读书笔记06-终结清理和垃圾回收
- finalize()方法
- 垃圾回收器如何工作
java的垃圾回收是由jvm来控制的.所以需要java程序员参与的部分不是很多. 但是在这里需要明白一点,java的垃圾回收回收的是什么? 垃圾回收器只释放那些由new分配的内存. 注意这个限制,jvm只负责回收内存,而且这部分内存是通过new关键字来创建的. 所以像什么数据库连接,IO等等是需要程序员去释放的.
关于java的垃圾回收,简单来说有三点:
- 对象可能不会被垃圾回收
- 垃圾回收不等于析构
- 垃圾回收只和内存相关.
垃圾回收是需要消耗资源的. 所以java的策略是,在不必要的时候,不会进行垃圾回收. 也就是说,只要在程序没有濒临内存告急的时候,对象占用的空间可能一直得不到释放.
finalize()方法
finalize方法不是析构函数. 这是Object类提供的一个很特殊的方法. 这个方法会抛出异常. 但是这个方法的抛出的异常,不会导致程序停止.
这个方法是干嘛用的呢?在垃圾回收器准备释放对象占用的空间时,会先调用finalize方法. 并且在下一次垃圾回收动作发生时,才回真正的回收对象占用的内存. 也就是说,这个函数是用来在垃圾回收器执行回收之前来做一些重要的清理工作.
finalize函数最重要的一个功能,用来释放某些创建对象之外的方式所分配的内存. 但是,java中一切都是对象,那创建对象之外的方式所分配的内存是哪里来的呢? 就不是java弄出来的呗. java有所谓的本地方法,也就是在java中调用其他语言的代码(目前好像只有C和C++). 那有能在调用的C语言代码中使用malloc()方法分配了存储空间,而这些内存空间是垃圾回收器所不能处理的. 所以需要在finalize方法中去释放它.
垃圾回收器如何工作
在堆上分配内存代价很高,但是由于垃圾回收器的存在,在java中,在堆中分配内存的速度甚至可以与其他语言在栈上的速度向媲美. 为什么?
因为java的垃圾回收器一方面会释放空间,一方面会进行内存碎片整理. 所以java创建对象的时候,在堆上分配内存只需要将堆指针移动一下,就像在栈上那样…
引用计数器和java的对象存活判断机制
很多语言的垃圾回收机制靠的是引用计数器. 这是一种简单的方法来判断对象是否存活. 每个对象都有一个引用计数器,如果有一个引用变量连接到该对象时,则,该对象的引用计数器加1. 当引用离开作用域或者被置为null的时候,引用计时器减1. 如果引用计数器为0,则判定该对象失活.(有可能会被立即清理). 但是如果出现循环引用的时候,单纯靠引用计数器就不行了.
所谓循环引用,就像下面这个样子
public void buidDog(){
Dog newDog = new Dog();
Tail newTail = new Tail();
newDog.tail = newTail;
newTail.dog = newDog;
}
各种jvm的实现中好像都没有采用引用计时器的方式. 而是采用了一种更快的方式. 思路是:所有活的对象不管是被引用了多少层,一定可以追溯到堆栈或者静态存储区之中的引用. 那如果从堆栈和静态存储区反向遍历的话,就可以找到所有活着的对象. 那这样就解决了循环引用的问题.
jvm的垃圾回收机制
jvm采用了一种自适应的垃圾回收技术. 有一种做法叫做stop and copy,简单说就是,先暂停程序,但后将所有存活的对象复制到另外一个堆中.当然,那没有被复制的就全是垃圾,当对象被复制到新的堆中的时候肯定是紧凑排列的,就不会存在内存碎片的问题. 当对象从一个堆被复制到另外一个堆之后,那指向它的引用就应该被修正. 静态存储区和栈上的引用可以直接被修正. 但是还有一些其他的引用会在之后的遍历中被修正.
但是这种方式有两个很大的问题:
- 开销很大. 因为需要两个堆.内存空间平白无故的多了一倍.
- 程序在稳定之后,很少,甚至没有垃圾. 那这种做法就是大炮打蚊子了.
针对第一点,通常的做法是,将堆内存划分为几个大的区块,然后在这几个大的区块之间倒腾. 而关于第二点,会引入一个新的机制,叫mark and sweep. 这种方式很慢,但是如果已经知道只有很少的垃圾的时候,使用它还是合理的. 首先,从栈和静态存储区开始遍历,找到所有存活的对象,每找到一个就给一个标记,表示这个对象是活着的. 这时候不进行清理,等遍历完,所有活着的对象都有标记了.然后释放所有没有标记的对象. 这会使内存不连续. 如果垃圾回收器想要得到连续空间的,会进行内存整理. 同样这个也需要在程序暂停的时候进行.
jvm中内存分配是以较大的块为单位的. 如果对象比较大,那么它会占用单独的块. 对于stop and copy这种方式,严格意义上说,必须把所有活着的对象从旧的堆中复制到新的堆中. 这会导致大量的内存复制行为. 有了块之后,垃圾回收器就可以往废弃的块中拷贝对象. 每个块都会有一个generation count来记录它是否是存活的. 如果块在某处被引用,其代数会增加. 垃圾回收器针对上次清理之后的块进行整理.大型对象不会被复制.但是其代数会增加.内含小型对象的块会被复制和整理. 这对处理处理大量的短命的临时对象很有帮助.
这一段不是很理解,意思是不是,如果一个对象占满了一个块,就认为它是个大的对象. 那么这种单独占一个块的或者的大对象在垃圾回收的时候就没有必要再被复制到另外一个新块中了. 所以不去处理它. 而对于没有占满一个块的小对象,依旧会进行复制和整理.
如果对象比较稳定,那么垃圾回收器效率降低的话,会切换到mark and sweep方式.
TIJ读书笔记06-终结清理和垃圾回收