一.判断对象是否存活
在堆里面存放着几乎所有的对象实例,垃圾回收器在对堆进行回收前,要做的事情就是确定这些对象中哪些还 是“存活”着,哪些已经“死去”(死去代表着不可能再被任何途径使用得对象了)
1.引用计数算法
在对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1,当引用失效时,计数器减1。Python在用,但主流虚拟机没有使用,因为存在对象相互引用的情况,这个时候需要引入额外的机制来处理,这样做影响效率。
2.可达性分析
来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这 些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何 引用链相连时,则证明此对象是不可用的。 作为GC Roots的对象包括下面几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象。
2. 方法区中类静态属性引用的对象。
3. 方法区中常量引用的对象。
4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。
5.JVM的内部引用(class对象、异常对象NullPointException、OutofMemoryError,系统类加载器)。
6. 所有被同步锁(synchronized关键)持有的对象。
7.JVM内部的JMXBean、JVMTI中注册的回调、本地代码缓存等
8.JVM实现中的“临时性”对象,跨代引用的对象(在使用分代模型回收只回收部分代时)
3.Finalize方法
即使通过可达性分析判断不可达的对象,也不是“非死不可”,它还会处于“缓刑”阶段,真正要宣告一个对 象死亡,需要经过两次标记过程,一次是没有找到与GCRoots的引用链,它将被第一次标记。随后进行一次筛 选(如果对象覆盖了finalize),我们可以在finalize中去拯救。
二.四种引用
1.强引用
一般的Object obj = new Object() ,就属于强引用。在任何情况下,只有有强引用关联(与根可达)还在, 垃圾回收器就永远不会回收掉被引用的对象。
2.软引用 SoftReference
一些有用但是并非必需,用软引用关联的对象,系统将要发生内存溢出(OuyOfMemory)之前,这些对 象就会被回收(如果这次回收后还是没有足够的空间,才会抛出内存溢出)。参见代码: VM参数 - Xms10m -Xmx10m -XX:+PrintGC
例如,一个程序用来处理用户提供的图片。如果将所有图片读入内存,这样虽然可以很快的打开图片,但 内存空间使用巨大,一些使用较少的图片浪费内存空间,需要手动从内存中移除。如果每次打开图片都从 磁盘文件中读取到内存再显示出来,虽然内存占用较少,但一些经常使用的图片每次打开都要访问磁盘, 代价巨大。这个时候就可以用软引用构建缓存。
3.弱引用 WeakReference
一些有用(程度比软引用更低)但是并非必需,用弱引用关联的对象,只能生存到下一次垃圾回收之前, GC发生时,不管内存够不够,都会被回收。
注意:软引用 SoftReference和弱引用 WeakReference,可以用在内存资源紧张的情况下以及创建不是 很重要的数据缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。
实际运用(WeakHashMap、ThreadLocal)
4.虚引用 PhantomReference
幽灵引用,最弱(随时会被回收掉)
垃圾回收的时候收到一个通知,就是为了监控垃圾回收器是否正常工作。
三.垃圾回收主要发生在那
栈:栈中的生命周期是跟随线程,所以一般不需要关注
堆:堆中的对象是垃圾回收的重点
方法区/元空间:这一块也会发生垃圾回收,不过这块的效率比较低,一般不是我们关注的重点
四.对象的分配策略
1.对象的分配原则
栈中分配对象
没有逃逸
即方法中的对象没有发生逃逸。
逃逸分析的原理:分析对象动态作用域,当一个对象在方法中定义后,它可能被外部方法所引用,比如: 调用参数传递到其他方法中,这种称之为方法逃逸,甚至还有可能被外部线程访问到,例如:赋值给其他 线程中访问的变量,这个称之为线程逃逸。
从不逃逸到方法逃逸到线程逃逸,称之为对象由低到高的不同逃逸程度。
如果确定一个对象不会逃逸出线程之外,那么让对象在栈上分配内存可以提高JVM的效率。
逃逸分析
逃逸分析 如果是逃逸分析出来的对象可以在栈上分配的话,那么该对象的生命周期就跟随线程了,就不需要垃圾回 收,如果是频繁的调用此方法则可以得到很大的性能提高。 采用了逃逸分析后,满足逃逸的对象在栈上分配
没有开启逃逸分析,对象都在堆上分配,会频繁触发垃圾回收(垃圾回收会影响系统性能),导致代码运行
2.在堆中分配对象
对象优先在Eden分配
虚拟机参数:
-Xms20m
-Xmx20m
-Xmn10m
-XX:+PrintGCDetails
-XX:+PrintGCDetails 打印垃圾回收日志,程序退出时输出当前内存的分配情况
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间分配时,虚拟机将发起一次Minor GC。
注意:新生代初始时就有大小
大对象直接进入老年代
虚拟机参数:
-Xms20m
-Xmx20m
-Xmn10m
-XX:+PrintGCDetails
-XX:PretenureSizeThreshold=4m
-XX:+UseSerialGC
PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效。
最典型的大对象是那种很长的字符串以及数组。
这样做的目的:
1.避免大量内存复制,
2.避免提前进行垃圾回收,明明内存有空间进行分配。
长期存活的对象进入老年代
如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor 空间中,并将对象年龄设为1,对象在Survivor区中每熬过一次 Minor GC,年龄就增加1,当它的年龄增加到 一定程度(并发的垃圾回收器默认为15),CMS是6时,就会被晋升到老年代中。
注:Survivor空间为图中的From与To区域
对象年龄动态判定
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了 MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄
空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这 个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置 值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象 的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果担保失败则会进 行一次Full GC;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
五.复制算法
1.特点
1. 实现简单、运行高效
2.内存复制、没有内存碎片
3. 利用率只有一半
2.具体操作
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活 着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区 进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可,实现简单,运 行高效。只是这种算法的代价是将内存缩小为了原来的一半。 注意:内存移动是必须实打实的移动(复制),不能使用指针玩。
新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间, 而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor[1]。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上, 最后清理掉Eden和刚才用过的Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代 容量的90%(80%+10%),只有10%的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的 数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保
注:Survivor空间为图中的From与To区域
六.标记-清除算法
1.特点
1.执行效率不稳定
2.内存碎片导致提前GC
2.具体操作
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被 标记的对象。
它的主要不足空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程 序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
七.标记-整理算法
1.特点
1.对象移动
2.引用更新
3.用户线程暂停
4.没有内存碎片
2.具体操作
首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有 存活的对象都向一端移动,然后直接清理掉端边界以外的内存。标记整理算法虽然没有内存碎片,但是效 率偏低。
八.分代收集理论
1.绝大部分的对象都是朝生夕死
2.对象熬过多次垃圾回收,越难回收
3.在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少 量存活对象的复制成本就可以完成收集。
4.而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记 —整理”算法来进行回收。