垃圾回收
可达性分析算法
可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)
如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。
GC Roots
虚拟机栈中引用的对象、本地方法引用的对象、方法区中类静态属性引用对象、所有被同步锁synchronized持有的对象、Java虚拟机内部的引用、反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。
GC进行时必须"stop The world"的一个重要原因。即使是号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。
finalization
当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用这个对象的finalize()方法。
finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等。
不要主动调用
在finalize()时可能会导致对象复活。
finalize()方法的执行时间是没有保障的,它完全由GC线程决定,极端情况下,若不发生GC,则finalize ()方法将没有执行机会。
一个糟糕的finalize()会严重影响GC的性能。
对象的状态
可触及的:从根节点开始,可以到达这个对象。
可复活的:对象的所有引用都被释放,但是对象有可能在finalize ()中复活。
不可触及的:对象的finalize ()被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为finalize ()只会被调用一次。
垃圾清除
分代收集算法
年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与标记-整理的混合实现。
①Mark阶段的开销与存活对象的数量成正比。
②sweep阶段的开销与所管理区域的大小成正相关。
③compact阶段的开销与存活对象的数据成正比。
垃圾回收算法
标记-清除
标记:collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
清除:collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。
复制算法
将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
优点
没有标记和清除过程,实现简单,运行高效
复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点
此算法的缺点也是很明显的,就是需要两倍的内存空间。
对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。
适合
如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,或者说非常低才行。
标记-压缩
标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-sweep-Compact)算法。
可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。
优点
消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
消除了复制算法当中,内存减半的高额代价。
缺点
从效率上来说,标记-整理算法要低于复制算法。
移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址。
移动过程中,需要全程暂停用户应用程序。即:STW
三种算法的比较
复制算法清除垃圾效果最好、但是不适合内存太小的jvm。标记压缩就就不如人意了,首先效率低于复制算法,步骤也比标记清除和复制算法多一步
分区算法
一般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就越长,有关GC产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。
分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。
每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
垃圾回收概念
System.gc
在默认情况下,通过system.gc()或者Runtime. getRuntime().gc()的调用,会显式触发Full Gc,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
然而system.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用。
JVM实现者可以通过system.gc()调用来决定JVM的GC行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用system.gc ()。
内存溢出
javadoc中对outOfMemoryError的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存。
1、Java虚拟机的堆内存设置不够。
比如:可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定JVM堆大小或者指定数值偏小。我们可以通过参数-Xms.-Xmx来调整。
(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)对于老版本的oracle JDK,因为永久代的大小是有限的,并且JVM对永久代垃圾回收(如,常量池回收、卸载不再需要的类型)非常不积极,所以当我们不断添加新类型的时候,永久代出现outofMemoryError也非常多见,尤其是在运行时存在大量动态类型生成的场合;类似intern字符串缓存占用太多空间,也会导致OOM问题。对应的异常信息,会标记出来和永久代相关:“java.lang, outOfMemoryError: PermGen space"。
随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的OOM有所改观,出现OOM,异常信息则变成了:“java.lang.outOfMemoryError: Metaspace”。直接内存不足,也会导致OOM。
内存泄漏
严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。
尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现outOfMemory异常,导致程序崩溃。
单例模式
单例的生命周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生。
资源未关闭
数据库连接(dataSourse.getConnection( ) ),网络连接(socket)和io连接必须手动close,否则是不能被回收的。
STW
可达性分析算法中枚举根节点(GC Roots)会导致所有Java执行线程停顿。
①分析工作必须在一个能确保一致性的快照中进行
②—致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
③如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
STW事件和采用哪款GC无关,所有的GC都有这个事件。
并发
并发,指的是多个事情,在同一时间段内同时发生了。任务之间是互相抢占资源的。
在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理器止运行。
并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。
并行
并行,指的是多个事情,在同一时间点上同时发生了。任务之间是不互相抢占资源的。
当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,我们称之为并行(Parallel).
其实决定并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核也可以并行。
安全点
程序执行时并非在所有地方都能停顿下来开始Gc,只有在特定的位置才能停顿下来开始Gc,这些位置称为“安全点(Safepoint) ”。
Safe Point的选择很重要,如果太少可能导致GC等待的时间太长,如果太频繁可能导致运行时的性能问题。大部分指令的执行时间都非常短暂,通常会根据“是否具有让程序长时间执行的特征”为标准。比如:选择一些执行时间较长的指令作为Safe Point,如方法调用、循环跳转和异常跳转等。
抢断式中断
首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
主动式中断
设置一个中断标志,各个线程运行到safe Point的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起。
安全区域
安全区域是指在一段代码片段中对象的引用关系不会发生变化,在这个区域中的任何位置开始cc都是安全的。我们也可以把safe Region看做是被扩展了的safepoint。
1、当线程运行到safe Region的代码时,首先标识已经进入了Safe Region,如果这段时间内发生GC,JVM会忽略标识为Safe Region状态的线程;
2、当线程即将离开Safe Region时,会检查JVM是否已经完成GC,如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开Safe Region的信号为止;
引用
强引用
强引用(StrongReference) :最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“object obj=new object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
强引用是造成Java内存泄漏的主要原因之一。
软引用
软引用(SoftReference):在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
弱引用
弱引用(WeakReference):被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。
虚引用
虚引用(PhantomReference) :一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
它不能单独使用,也无法通过虚引用来获取被引用的对象。当试图通过虚引用的get()方法取得对象时,总是null。
为一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知。
垃圾回收器
碎片处理方式
压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片。
再分配对象空间使用:指针碰撞
非压缩式的垃圾回收器不进行这步操作。再分配对象空间使用:空闲列表
工作内存区间
年轻代垃圾回收器、老年代垃圾回收器
工作模式
并发式、独占式
GC性能评估
现在标准:在最大吞吐量优先的情况下,降低停顿时间。
吞吐量
因为如果选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致GC需要更长的暂停时间来执行内存回收。
(程序运行时间-总暂停时间)/程序运行时间
暂停时间
相反的,如果选择以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,也只能频繁地执行内存回收,但这又引起了年轻代内存的缩减和导致程序吞吐量的下降。

浙公网安备 33010602011771号