垃圾收集器在对堆进行回收前,要判断堆中的对象哪些还存活,哪些可以回收,使用的方法有:
原理:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器加1,当引用失效时,计数器减1,当计数器值为0时表示该对象不再被使用。需要注意的是:主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。
基本思路:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的。
在Java语言中,可作为GC Roots的对象包括下面几种:
判断对象是否被引用,java将引用分为:
强引用(Strong Reference):类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用(Soft Reference):软引用是用来描述一些还有用但并非必需的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
弱引用(Weak Reference):弱引用也是用来描述非必需对象,被弱引用关联的对象只能生存到下一次垃圾收集发生之前
虚引用(Phantom Reference):为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。算法分为两个阶段:标记阶段和清除阶段。
标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效。回收过程如下:
它的主要不足有两个:
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。垃圾回收时,将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单。但是这种算法将内存缩小为了原来的一半,代价太高。
新生代中的对象98%是“朝生夕死”的,所以并不需要按照1 1的比例来划分内存空间,而是将新生代分为一块较大的Eden空间和两块较小的Survivor空间。每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8 1:1,也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会被“浪费”的。
很显然,复制算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么复制算法的效率将会大大降低。
(1)为什么需要2个Survivor,不能只有1个吗?
因为每次GC时的操作对象都是Eden和其中一块Survivor,把这两块的存活对象copy到s1区,清空Eden和S0,再将s1区copy到s0,保证每次GC都是针对Eden和一个survivor区。
(2)为对象分配内存时,对象也会分配到Survivor区吗?
在为对象分配内存时,对象除了会分配到Eden区,同时也会分配到Survivor中的一个区,因为Eden区满了的话,可以在其中一个Survivor区中分配空间。
为了解决复制算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。由于进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。
1 )新生代(Young Generation)的回收算法 (标记-复制算法)
2)年老代(Old Generation)的回收算法(标记-整理算法)
3) 持久代(Permanent Generation)(也就是方法区)的回收算法
用于存放静态文件,如Java类、方法等。垃圾回收对持久代没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代也称方法区。
方法区存储内容是否需要回收的判断与堆的判断方式不一样。方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
在JDK1.8中堆内存已经移除了方法区,转而使用元空间替代方法区,元空间在直接内存中。
GC类型 | GC算法 | 触发条件 |
Young GC (Minor GC) | 标记复制算法 对新生代(Eden+1块Survivor区)进行回收,Minor GC 非常频繁 |
|
Old GC (Major GC) | 标记-整理算法 对老年代进行回收 |
|
Full GC (Minor GC+ Major GC) | 对整个堆进行回收 |
|
Mixed GC | 收集整个新生代,和部分老年代 | 只有G1有这个模式 |
当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC。当进行垃圾回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间上,最后处理掉Eden和刚才的Survivor空间。当Survivor空间不够用时,需要依赖老年代进行分配担保。
所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。
当对象在新生代中经历过一定次数(默认为15)的Minor GC后,就会被晋升到老年代中。
为了更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。(-XX:MaxTenuringThreshold=15)
Minor GC之前,需要老年代进行分配担保,虚拟机会先检查以下两项确保Minor GC可以可以执行。
如果条件不满足,会进行一次Full GC。
《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》
页面更新:2024-05-13
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号