程序员 了解初始化的重要性,但是往往会忘记同样重要的清理工作。当你创建一个对象的时候,开辟了一块内存区域,很明显,如果你对它“置之不理”,总有一天它就是压死你的最后一根稻草。这里我不想讨论C++,我在乎的是我的最爱java.
毫无疑问,java的GC为开发人员省下了大部分时间用与处理内存回收问题。java中没有提供像C++里面的析构函数用于释放资源,回收内存空间,但是它提供了方法finalize,千万不要把它就当成了析构函数,在C++中其实对象一定会被销毁的,当然是你的程序不存在bug,然而在java中你需要记住:
1. java对象可能不被垃圾回收 2. 垃圾回收不等于析构。
我们可以简单的这样来定义java的垃圾回收:一旦垃圾回收器准备释放对象的存储空间,将首先调用 finalize方法做一些重要的清理工作,记住这时候是没有回收存储空间,等到下一次回收动作发生的时候,才会真正的回收对象占用的内存空间。
在我看来,java的GC实现得益于java的单根继承 和统一内存堆分配,java是杂合性语言,如果说java里面一切皆为对象,我想没有那本教科书是这么写的,我想读者你应该明白了我为什么这么说。但如果你说 java对象都在堆上 或者 堆上面存储的都是对象 ,这是可以肯定的。下面就来说说GC的神奇吧。
一. java中内存的对象类型:
1. 活动对象:即当前正在其他对象引用的对象'
2. 非活动对象:这类对象不再被其他对象,堆栈或静态存储区所引用,是孤立的对象。这类对象可以被回收,回收的堆空间用于分配给其它新创建的对象。
二. GC何时会被触发
a. 系统空闲 ,GC线程的优先级低于系统应用线程,当系统中没有应用线程执行时,GC会被触发。
b. 堆空间内存不足,当堆空间的内存不足以创建新对象时,GC会被触发。如果第一GC仍不能获得足够的空间,第二次GC将被触发,如果这一次仍无法获取足够的空间,“Out of Memory” 将被抛出。
三. 影响GC执行时间、频度的因素
JVM 堆(heap)空间的大小, 堆空间设置偏大,完全GC执行比较耗时,但执行频率会降低。堆空间设置恰好符合应用内存需求,完全GC执行很快,但执行会变得更频繁。
四. 堆(heap)空间
堆是java程序中对象存活的地方,其中包括:活动对象; 非活动对象,这类对象不再为应用程序中的任何指针能够到达; 剩余内存.
堆空间中包含三种区域:
新生代(younggeneration)
新生代被分为两块: Eden, survivor spaces i. Eden是为新对象分配的地方,很多对象分配后就变成非活动对象,即垃圾对象。这类对象具有“infant mortality”(幼儿死亡率)。如:方法体中的临时对象。 ii. Survivor spaces也被称为两片生存空间,其中一片要保证任何时刻是空的,并作为下一个空间的目的地。当GC发生的时候,Eden中的存活对象被移入下一片空间。对象在生存空间之间移动,直到它们老化(达到存活时间阀值),然后被移入旧生代。 iii. Eden中对象满的时候,发生一次小收集(minor collection),小收集执行时间取决于Eden中对象的infant mortality。infant mortality高,执行就会很快。
|
旧生代(tenuredgeneration)
a. 该区域用于存放那些生命周期比较长的对象,Eden中的活动对象经过minor collection后,被复制到两片生存空间,当两片生存空间中的对象老化时,这些对象被移入旧生代。 b.旧生代中对象满的时候,发生一次大收集(major collection),因为收集时要涉及所有存活对象,所以大收集的速度相对于小收集要慢很多。
|
永生代(permanentgeneration)
这个代比较特别,它负责保存反射对象。这些数据是虚拟机所需的数据,用来描述在Java语言中没有等同物的对象。例如,描述类与方法的对象存储在永生代中。
|
五. 回收机制
遍历堆栈或静态存储区的引用找对象, java中采用“自适应技术”,当垃圾回收器第一次启动时,它执行的是“停止-复制”,因为这个时刻内存有太多的垃圾。然后Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记-清扫”方式;同样,Java虚拟机会跟踪“标记-清扫”效果,要是堆空间出现很多碎片,就会切换到“停止-复制”方式。这就是所谓的“自适应”技术。
i.“停止-复制”:
理论上是先暂停程序的运行(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全是垃圾。当对象被复制到新堆上时,它们是一个挨着一个的,所以新堆保持紧凑排列(这也是为什么分配对象的时候“堆指针”只管依次往前移动)。然后就可以按前述方法简单、直接地分配内存了。这将导致大量内存复制行为,内存分配是以较大的“块”为单位的。有了块之后,垃圾回收器就可以不往堆里拷贝对象了,直接就可以往废弃的块里拷贝对象了。
ii. 另一种是“标记-清扫”:它的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象一个标记。这个过程中不会回收任何对象。只有全部标记完成时,没有标记的对象将被释放,不会发生任何复制工作,所以剩下的堆空间是不连续的,然后垃圾回收器重新整理剩余的对象,使它们是连续排列的。