GC即垃圾收集器,虚拟机的必要组成部分。
不过这里说当然是,hotspot虚拟机(jvm的主要版本)的GC机制,前面说过了jvm的组成部分,那么想当然GC只需要负责方法区和堆就好了,虚拟机栈、本地方法栈、程序计数器随线程而生,随线程而死,为毛要回收内存?
先说堆,它分为新生代和老生代,这两个东西貌似没说过,这里说一下。为了便于管理和维护,写出更优化的内存管理,jvm将堆分为新生代和老生代,新生代存储的大多都是朝生夕死的对象实例,通过众多的实验证明,一个程序的运行中这种实例往往很多,他们存在不了多少时间,所以放在新生代就够了。这就说明了一个对象被实例化之后往往都是放在新生代,如果长时间回收不掉,它依然坚挺,那么这个对象实例就会进入老生代长久保存。堆分代的重要性和实用性特别明显,减去了大量的不必要的GC时间。
标记对象:可达性分析算法
当新生代到达了某种条件,jvm将自动启动GC,首先,GC将创建若干个“GC Roots"对象作为回收起点,通过这些起点遍历所有通过引用能到达的实例,就像遍历一张图一样,那些没有被遍历到的点,就是孤点没有用处了,那么什么东西可以作为”GC Roots"呢? 嘿嘿,想一想就马上出答案了,就是常量池和虚拟机栈变量表里的那些对象引用啊!这个思想很简单,用来标记对象是否可清除。
这里就说明了一个编程的良好习惯,尽量把不用的对象引用赋空,以便GC回收。
新生代算法:复制算法
这是目前新生代GC主流算法,大意是将新生代分为两块,一块使用,另一块被保留,当内存不够时,便触发GC将有用的实例复制到被保留的那块内存去,然后便整块擦除。由于只有少量的对象得到保存,所以这种算法运行起来快了许多。
老生代算法:标记-整理算法
无论什么老年代GC收集器,貌似用的都是这个算法,将标记的内存块向一端移动,清理另一端,这种方法的效率目前没有具体的感受。
SUN公司(现在是谷歌了)开发了多种GC,已适用多变的内存状况。嗯,说了这么多,有一个重点落下了,就是GC如何启动? 这里需要考虑一个重要的问题,GC启动之后所有事物必须静止!这个很容易想明白,如果GC正在清扫内存,其他的线程照样该咋办咋办,往堆里填对象,GC一边扫,他们一边添,那内存能干净吗?数据能稳定吗? 所以GC启动之后所有的线程全部停止了!那么问题又来了,那么多线程怎么让他们说停就停呢?原来JVM在程序运行期间设置多个安全点,如果内存需要清理,那么众多的线程就会在某个安全点停下,待所有线程全部停止后,GC便开始打扫了。
好吧,这是JVM的一大硬伤,虽然GC的优化一直在继续,但运行GC永远需要时间,所以程序永远需要停顿,这对使用者来说是无法忍受的,幸好如今的GC清理几百兆的内存仅需几百毫秒,几乎感不到程序的停顿,但这只是几百兆。。。为什么JAVA写不了游戏呢?排除其他因素,GC时间对于游戏来说就是致命的了,这相当于自带延迟。