深入理解java虚拟机-垃圾回收
当需要排查各种内存溢出,内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些自动化的技术实施必要的监控和调节。
在前面一章了解到java内存的运行时区域,对于程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而灭,且栈中的每一个栈帧分配多少内存基本上是在类结构确定下来时就已知的,因此,这几个区域的内存分配和回收都具备确定性,因此不需要加多考虑。
而java堆和方法区不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分的内存分配和回收都是动态的,因此,垃圾收集器所关注的就是这部分的内存
垃圾收集也叫GC,GC需要做以下三件事情:
1)如何判定对象为垃圾对象?
答:策略有引用计数法和可达性分析法
2)如何进行垃圾回收?
答:策略有:标记-清除算法,复制算法,标记-整理算法,分代收集算法
垃圾收集器有:Serial ,parnew,CMS,G1
3)何时进行垃圾回收?
如何判定对象为垃圾对象?
1,引用计数法: 在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值+1,当引用失效的时候,计数器的值-1。
当值被判为0的时候就进行回收
存在问题:当对象进行循环引用时,导致他们的引用计数都不为0,就不能分析对象为垃圾对象
优点是比较灵活
2,可达性分析法:通过一系列的名为“GC Roots” 的对象作为起点,从这些节点开始往下搜索,搜索所走过的路径成为引用链。当一个GC Roots到这个对象不可达时,则证明此对象是不可用的,此时会将该对象判定为垃圾对象。
在java语言中,可作为GC Roots的对象包括下面几种:
1)虚拟机栈(栈帧中的局部变量表)
2)方法区的类属性所引用的对象
3)方法区中的常量所引用的对象
4)本地方法栈中的引用的对象
目前判定是否为垃圾的算法均是可达性分析算法
如何进行垃圾回收?
1,标记-清除算法:算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象
它的缺点:一个是效率问题,标记和清除的过程都不高,另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不触发另一次垃圾收集的动作。
2,复制算法:针对新生代内存进行回收
解决标记-清除算法的效率问题,它将可用内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块的内存用完了,就将还存活的的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉
上述算法会引来一个新的问题:造成内存资源极大的浪费,我们来按以下方法分配内存空间:
将内存分为一块较大的Eden空间和两块较小的Servivor空间,每次使用Eden和其中的一块Servivor。当回收时,将Eden和Servivor中还存活着的对象一次性的拷贝到另外一块Servivor空间上,最后清理掉Eden和刚才使用过的Servivor的空间
3,标记整理算法:针对老年代内存进行回收,也叫标记,整理,清除,整理阶段需要将存活对象排序整理好
4,分代收集算法:
对于新生代选择复制算法,对于老年代,选择标记整理算法
垃圾回收器:垃圾回收器就是垃圾回收的具体体现
介绍HotSpot JVM 1.6的垃圾收集器:两个收集器之间存在连线,就说明它们可以搭配使用
Serial收集器:它是一个单线程的收集器,在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束
它是虚拟机运行在client模式下的默认新生代收集器,有着优于其他收集器的地方:简单而高效,对于限定单个CPU的环境来说,Serial收集器优 于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。
ParNew收集器:是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数,收集算法,对象分配规 则,回收策略等都与Serial收集器完全一样。