判定对象是否为垃圾的算法?
对象被判定为垃圾的标准:没有被其他对象引用
判定对象是否为垃圾的算法?
1.引用计数算法
该算法通过判断对象的引用数量来决定对象是否可以被回收。
堆中的每个对象实例都有一个引用计数器,当一个对象被创建时,若对象被分配给一个引用变量,则该对象的引用计数+1;当对象的某个引用变量超过了生命周期或者被设置成一个新值时,该对象的引用计数-1。
任何引用计数为0的对象实例可以被当作垃圾收集。
优点:
由于只需要过滤出引用计数为0的对象,将其内存回收即可,所以算法的执行效率高。
由于垃圾回收时可以做到几乎不打断程序的执行,因此对程序要求长期不被打断的环境比较有利。
缺点:
由于实现过于简单,会出现无法检测出循环引用的情况(如父对象有一个对子对象的引用,子对象反过来引用父对象,那么它们的引用计数永远不可能为0),导致内存泄露。
循环引用的例子:
2.可达性分析算法
通过判断对象的引用链是否可达来决定对象是否可以被回收。
可达性算法是从离散数学中的图论引入的,程序把所有的引用关系看做一张图。通过一系列的名为GC Root的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径就被称为引用链。当一个对象没有任何引用链相连,那从图论上来说就是从GC Root到这个对象是不可达的,就证明这个对象是不可用的,它也就被标记为垃圾。
可以作为GC Root的对象:
1.虚拟机栈中引用的对象(栈帧中的本地变量表)
比如在一个方法中新建一个对象并赋值给一个引用变量,那么这个对象在方法结束前都可以作为GC Root。
2.方法区里的常量引用的对象
比如在类里面定义了一个常量,该常量是某一个对象的地址,那么该对象也可以作为GC Root。
3.方法区中的类静态属性引用的对象
比如在类里面定义了一个类静态属性,该类静态属性是某一个对象的地址,那么该对象也可以作为GC Root。
4.本地方法栈中JNI(Native方法)的引用对象
调用native方法,即被引用的对象是非JAVA语言构建的对象,也可以成为GC Root
5.活跃线程的引用对象
只要一个线程是活跃的,该线程中引用的对象被判定为可达的,可以作为GC Root
谈谈你了解的垃圾回收算法
1.标记-清除算法(Mark and Sweep)
顾名思义,就是将回收分为两个阶段,标记阶段和清除阶段。
Mark标记阶段:从根集合进行扫描,对存活的对象进行标记。使用可达性算法,从GCRoot对象开始标记可达的对象。
Sweep清除阶段:对堆内存从头到尾进行线性遍历,如果发现有对象没有被标记为可达对象,则回收不可达对象内存。并且将原来标记为可达的对象标识清除掉,以方便下一次垃圾回收。
缺点:
碎片化。由于标记-清除算法不需要进行对象进行移动,并且仅对不可达的对象进行处理,因此标记清除后会产生大量不连续的内存碎片。空间碎片太多,可能会导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存,而不得不提前触发另一次垃圾回收工作。
2.复制算法(Copying)
复制算法将可用的内存按容量和一定比例划分为对象面和空闲面。对象在对象面上创建。当被定义为对象面的内存用完之后,就将存活的对象从对象面复制到空闲面。将对象面所有的对象内存清除。
这种算法适用于对象存活率低的场景。适用于新生代垃圾。
缺点:面对对象存活率高的场景就有点力不从心了,要复制较多的对象,效率就比较低了。不适用于老年代垃圾
3.标记-整理算法(Compacting)
标记阶段:从根集合进行扫描,对存活的对象进行标记
清除阶段:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收
优点:避免内存的不连续性;不用设置两块内存互换;适用于存活率高的场景
4.分代收集算法(Generational Collector)
垃圾回收算法的组合拳
按照对象生命周期的不同将对象存放到堆中不同的区域,不同的区域采用不同的垃圾回收算法
目的:提高JVM垃圾回收效率
堆空间被分成年轻代和老年代
年轻代中的对象存活率低,采用复制算法进行垃圾回收
老年代中的对象存活率高,采用标记-清除算法或者标记整理算法。
分代收集算法的分类
Minor GC:处理年轻代的垃圾回收
Full GC:处理老年代的垃圾回收
年轻代和老年代
年轻代:尽可能快速地收集掉那些生命周期短的对象
采用复制算法进行垃圾回收
年轻代划分成两个区,Eden区和两个Survivor区。对象刚被创建时,首先先存放在Eden区。两个Survivor区一个叫from,一个叫to。在垃圾回收机制的不同进程中,from,to相互转换。每次垃圾回收时,将Eden区和from区中存活的对象复制到to区,清理掉Eden区和from区中的对象。当to区中的空间不够用时,这时就需要依赖老年代的空间。
对象如何晋升到老年代?
- 每个对象有一个年龄的属性,在每次垃圾回收算法进行后,分别+1.当对象的年龄达到一个值,默认是15岁,则将对象移到老年代中。
- Survivor区中存放不下的对象,会直接移到老年代中
- 新生成的大对象(-XX:+PretenuerSize Threshold)
常用的调优参数
-XX:SurvivorRatio:设置Eden区和Survivor区大小的比值,默认8:1
-XX:NewRatio:设置老年代和年轻代内存大小的比例
-XX:MaxTenuringThreshold:对象从年轻代晋升到老年代经过GC次数的最大阙值
老年代:存放生命周期较长的对象
采用标记-清除算法或者标记整理算法。
当触发老年代的垃圾回收算法时,通常也会伴随着对新生代内存的回收
FullGC和MajorGC
FullGC一般指对整个堆空间的垃圾回收,包括年轻代和老年代
MajorGC一般指针对老年代的垃圾回收
Full GC比Minor GC慢,但执行效率低
触发Full GC的条件
- 老年代空间不足
- 永久代空间不足
- CMS时出现Promotion failed,Concurrent mode failure
Promotion failed:当创建对象时,年轻代放不下时想放到老年代时,发现内存不足
Concurrent mode failure:在执行GC的过程中,有对象要放入老年代中,老年代内存不足
- Minor GC晋升到老年代的平均大小大于老年代的剩余空间
- 在程序中调用System.gc()
- 使用RMI来进行RPC或管理的JDK应用,每小时执行1次Full GC
两个概念
Stop-the-World
JVM由于要执行GC而停止了应用程序的执行
任何一种GC算法中都会发生,当Stop-the-World发生时,除了GC线程外,其他线程都处于等待的状态
多数GC优化通过减少Stop-the-World发生的时间来提高程序性能
Safepoint(安全点)
该点是分析过程中对象引用关系不会变化的点,确定线程到达安全点后才会停顿下来,进行垃圾回收
产生安全点的地方:方法调用;循环跳转;异常跳转等
要进行垃圾回收时,让所有线程到达安全点后再停下来进行垃圾回收
安全点数量得适中
常见的垃圾收集器
JVM的运行模式
Server模式和Client模式。
Client模式启动速度快,Server模式启动较慢,但是启动后运行速度会比Client模式快。因为Server模式采用的是重量级的虚拟机。Client模式采用的是轻量级的虚拟机。
查看当前JVM的运行模式:java -version
垃圾收集器之间的联系
新生代收集器:Serial/ParNew/Parallel Scavenge
老年代收集器:CMS/Serial Old/Parallel Old
连线代表可以搭配使用
Serial收集器
-XX:+UseSerialGC,使用该命令设置年轻代使用该垃圾收集器对垃圾进行回收
采用复制算法进行垃圾回收
单线程收集,进行垃圾收集时,必须暂停所有工作线程
简单高效,Client模式下默认的年轻代收集器
ParNew收集器
-XX:+UseParNewGC,使用该命令设置年轻代使用该垃圾收集器对垃圾进行回收
采用复制算法进行垃圾回收
多线程收集,其余的行为、特点和Serial收集器一样
单核执行效率不如Serial,在多核下执行才有优势
Parallel Scavenge收集器
-XX:+UseParallel GC,使用该命令设置年轻代使用该垃圾收集器对垃圾进行回收
采用复制算法进行垃圾回收
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
多线程收集
比起关注用户线程停顿时间,更关注系统的吞吐量
前面的收集器关注的是系统的停顿时间,停顿时间短适合需要与用户交互的程序,良好的响应速度,能提升用户体验。高吞吐量则可以高效率的利用CPU时间,尽可能快的完成运算任务,主要适用于在后台运算,而不需要太多交互运算的情况。
在多核下执行才有优势,Server模式下默认的年轻代收集器
Serial Old收集器
XX:+UseSerialOldGC,使用该命令设置老年代使用该垃圾收集器对垃圾进行回收
采用标记-整理算法进行垃圾回收
单线程收集,进行垃圾收集时,必须暂停所有工作线程
简单高效,Client模式下默认的老年代收集器
Parallel Old收集器
XX:+UseParallelOldGC,使用该命令设置老年代使用该垃圾收集器对垃圾进行回收
采用标记-整理算法进行垃圾回收
多线程,吞吐量优先
在吞吐量优先的系统中可以考虑Parallel Scavenge+Parallel Old的组合
CMS收集器
XX:+UseConcMarkSweepGC,使用该命令设置老年代使用该垃圾收集器对垃圾进行回收
采用标记-清除算法进行垃圾回收
适用于:应用程序对停顿时间要求较高或者JVM有大量停留时间较长的对象
步骤:
初始标记:stop-the-world,虚拟机停下所有线程进行标记。这个阶段只从一个Root节点标记可达对象。很快执行完毕
并发标记:并发追溯标记,程序不会停顿
并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象
重新标记:暂停虚拟机,扫描CMS堆中的剩余对象
并发清理:清理垃圾对象,程序不会停顿
并发重置:重置CMS收集器的数据结构
此垃圾回收器的用户执行过程和垃圾回收过程是并发的。
缺点:带来内存空间碎片化问题
G1收集器(Garbage First收集器)
-XX:+UseG1GC,,使用该命令设置使用该垃圾收集器对垃圾进行回收
既用于年轻代也用于老年代的收集器
采用复制+标记-整理算法
将整个Java堆内存划分为多个大小相等的Region
年轻代和老年代不再物理隔离
特点:
并行和并发
分代收集:独立管理整个堆,采用不同的方式来管理新创建的对象和已经存活了一段时间的对象
空间整合:基于标记-整理算法,解决内存碎片化的问题
可预测的停顿:可以设置垃圾回收时间不能超过N
对象的finalize方法的作用是否与C++的析构函数作用相同?
与C++的析构函数不同,析构函数调用时间是确定的,即对象离开作用域后就会被析构函数delete掉。而java中finalize方法的调用具有不确定性
垃圾回收器要宣告一个对象死亡,至少需要经过两次标记。当对象经过可达性分析后,发现没有和GC root相连的对象,就会被第一次标记,并且判断是否执行finalize方法。如果这个对象重写了finalize方法并且未被引用过,那么就会将这个对象放置在F-Queue队列中,并由java虚拟机建立的一个finalize线程来依次执行对象的finalize方法。finalize方法执行随时可能会被终止,不保证一定执行完毕。finalize方法给予对象最后一次重生的机会,如果finalize方法执行中对象又被赋予了新的引用,那么对象就不会被回收了。
Java中的强引用,软引用,弱引用,虚引用有什么用?
强引用:
最普遍的引用- Object obj=new Object()
Java回收机制在内存不足时抛出OutOfMemoryError终止程序也不会回收具有强引用的对象
可以在程序中通过将该对象设置为null来弱化引用,使其被回收。或者在对象生命周期结束后,再由GC进行回收
软引用:
软引用表示一个对象处在有用但非必须的状态
如果一个对象是软引用,只有当内存空间不足时,GC会回收该引用的对象的内存
软引用可以用来实现高速缓存,这样我们就不用担心内存不足的问题,因为软引用的对象会在内存不足时被回收
String str=new String(“abc”);//强引用
SoftReference<String> softRef = new SoftReference<String>(str);//软引用
弱引用:
用于描述非必须的对象,比软引用更弱一些
弱引用具有更短的生命周期,GC在扫描时如果发现弱引用的对象,就会将其回收。因为GC线程优先级比较低,被回收的概率也不大,所以弱引用适用于引用偶尔被使用且不影响垃圾收集的对象
String str=new String(“abc”);//强引用
WeakReference<String> weakRef = new WeakReference<String>(str);// 弱引用
虚引用:
顾名思义就是形同虚设的意思,与其他几种引用不同,虚引用不会决定对象的生命周期
虚引用就跟没有任何引用一样,任何时候都可能被垃圾收集器回收
主要用来跟踪对象被垃圾回收器回收的活动,起哨兵的作用
与软引用、弱引用的区别在于,其必须和引用队列ReferenceQueue联合使用
GC在回收一个对象时,如果发现一个对象具有虚引用,在回收之前会首先将该对象的虚引用加入到与之关联的引用队列当中,程序可以通过判断引用队列是否已经加入虚引用来了解被引用的对象是否被GC回收,因此起到哨兵的作用
String str=new String(“abc”);//强引用
ReferenceQueue queue= new ReferenceQueue();
PhantomReference ref = new PhantomReference(str,queue);//虚引用
总结:强引用>软引用>弱引用>虚引用