1、哪些内存需要回收?
已经不存活的象需要被回收,即:不可能再被任何途径使用的对象需要被回收
1.1、如何找到需要被回收对象
- 1) 引用计算器法
对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。但这个方法有个明显的问题就是:如果2个对象相互引用,但又不被其它对象引用那个2个对象实际上也是应该被回收的,但这个方法回收不了。 -
- 可达性分析法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。在Java语言中可以作为GC Roots的对象包括:
· 虚拟机栈中引用的对象
· 方法区中静态属性引用的对象
· 方法区中常量引用的对象
· 本地方法栈中JNI(即Native方法)引用的对象
- 可达性分析法
2、垃圾回收算法
2.1、标记-清除(Mark-Sweep)
算法分为“标记”和“清除”两个阶段:
**标记:**遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
**清除:**遍历堆中所有的对象,将没有标记的对象全部清除掉。
2.1.1、缺点:
不足主要体现在效率和空间,
从效率的角度讲:标记和清除两个过程的效率都不高(递归与全堆对象遍历);
从空间的角度讲:标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。
2.2、复制(Copying)算法
复制算法是为了解决效率问题而出现的,它将可用的内存分为两块。
当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。
GC线程就将还存活着的对象复制到另外一块上面,并且严格按内存地址依次排列,同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。然后再把已经使用过的内存空间一次性清理掉。这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。
2.2.1、优点
每次只需对半区进行内存回收,没有内存碎片,只需移动指针,按顺序分配。
2.2.2、缺点
对象存活率较高的场景下需要进行大量复制,
内存缩小为了原来的一半。
##2.2.3 、新生代内存划分作了优化
新生代的内存被划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。每次回收时,将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden区和Survivor区的比例为8:1,意思是每次新生代中可用内存空间为整个新生代容量的90%。当然,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)。
2.3、标记-整理(Mark-Compact)算法
复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。根据老年代的特点,有人提出了另外一种标记-整理算法,过程与标记-清除算法一样,不过不是直接对可回收对象进行清理。
标记整理算法分为2个阶段。
标记: 与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
**整理:**移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
2.4、分代收集
代商用虚拟机基本都采用分代收集算法来进行垃圾回收。这种算法没什么特别的,无非是上面内容的结合罢了,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。大批对象死去、少量对象存活的,使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担保的,采用标记-清理算法或者标记-整理算法。
2.5触发GC的时机
1、当年轻代或者老年代满了,Java虚拟机无法再为新的对象分配内存空间了,那么Java虚拟机就会触发一次GC去回收掉那些已经不会再被使用到的对象
2、手动调用System.gc()方法,通常这样会触发一次的Full GC以及至少一次的Minor GC
3、程序运行的时候有一条低优先级的GC线程,它是一条守护线程,当这条线程处于运行状态的时候,自然就触发了一次GC了。这点也很好证明,不过要用到WeakReference的知识,后面写WeakReference的时候会专门讲到这个。
内存泄露
一、什么是内存泄露
指一个不再被程序使用的对象或变量一直被占据在内存中,无法回收的情况叫内存泄露。
二、什么情况下会出现内存泄露
1 、长生命周期的对象持有短生命周期对
象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需
要,但是因为长生命周期对象持有它的引用而导致不能被回收
1)比如:缓存系统,加载一个对象放在缓存中(例如一个全局的map中),然后一直不使用它,但这个对象一直被缓存引用,便却不再被使用
2)定一个栈,存放10个元素,即使将10个无素全部弹出,虽然栈是空的,但这个对象是无法回收的,需要我们交栈元素弹出时并将这个位置设置Null,那么这个对象的不被引用就可以被回收