Java与C++之间有一堵由内存动态分配和垃圾收集技术围成的高墙。
哪些内存需要回收,什么时候回收,怎么回收。
本节主要关注Java堆和方法区中的内存,程序计数器,虚拟机栈,本地方法栈中的内存随方法结束或线程结束时,会随着回收。
引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1;计数器为0的对象不再被引用。
引用计数有个重要的缺陷,就是它无法解决对象之间相互循环引用的问题。
对象A持有对象B的引用,对象B持有对象A的引用,引用A和引用B置空,并不能释放对象持有的引用,导致对象A和对象B永远无法释放。
引用技术算法没有在Java虚拟机中用来管理内存,但日常开发中有些场景可以考虑使用引用计数算法。
可达性分析算法:通过一系列的称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
在可达性分析中宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选是否执行finalize方法,在第二次标记时会被真的回收。任何一个对象的finalize方法都只会被系统自动调用一次。强烈建议忘掉finalize方法。
引用分为:强引用,软引用,弱引用和虚引用。
强引用:类似 Object o = new Object(),只要强引用还在,对象就不会被回收。
软引用(SoftReference):描述一些还有用但非必需的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
弱引用(WeakReference):也是用来描述非必需对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收对象。
虚引用(PhantomReference):为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
垃圾收集算法
标记-清除算法
算法分为”标记“和”清除“两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题:标记清除之后会产生大量不连续的内存碎片,碎片太多可能会导致提前触发另一次垃圾收集动作。
复制算法
将内存分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用的那块内存一次清理掉。这样实现简单,运行高效,只是代价太大。
标记-整理算法
标记过程和标记-清除算法中的标记一样,只是后续不再进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
分代收集算法
根据对象存活周期的不同将内存划分为几块。一般把Java堆分为新生代和老年代。新生代适合复制算法,老年代适合标记-清理或标记-整理算法。
垃圾收集器
垃圾收集器是内存回收的具体实现。
Serial收集器是新生代收集器,是一个单线程的收集器,在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
ParNew收集器是新生代收集器,其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,两者基本一致。
Parallel Scavenge收集器是一个新生代收集器,使用复制算法的多线程收集器。
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。
CMS收集器是以获取最短回收停顿时间为目标的收集器,基于标记-清楚算法,CMS收集器的内存回收过程是与用户线程一起并发执行的。
G1收集器跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
内存分配与回收策略
对象优先在Eden分配,大对象(需要大量连续内存空间的Java对象)直接进入老年代。
长期存活的对象将进入老年代,虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当它的年龄增加到一定程度,就将会被晋升到老年代中。如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象都可以直接进入老年代,无须等到要求的年龄。
网友评论