java中如何确认垃圾
java中采用引用计数法和可达性分析来确定对象是否应该被回收。其中,引用计数法容易产生循环引用的问题,可达性分析通过根搜索算法来实现。根搜索算法以一系列GC Roots的点作为起点向下搜索,在一个对象到任何GC Roots都没有引用链相连时,说明其已经死亡。
引用计数法
在java中如果要操作对象,就必须先获取该对象的引用,因此可以通过引用计数法来判断一个对象是否可以被回收。在为一个对象添加一个引用时,引用技术加1,在为对象删除一个引用时,引用计数减1。如果一个对象的引用计数为0,则表示此刻该对象没有引用,可以被回收。
可达性分析
为了解决引用计数法的循环引用问题,java还采用了可达性分析来判断是否可以被回收。具体做法是首先定义一些GC Roots对象,然后以这些GC Roots对象作为起点向下搜索,如果在GC Roots和一个对象之间没有可达路径,则称该对象是不可达的,不可达对象要经过至少两次标记才能判断其是否可以被回收。如果在两次标记后该对象仍然是不可达的,则将被垃圾收集器回收。
java中常用的垃圾回收算法
java中常用的垃圾回收算法有标记清除,复制,标记整理和分代收集这四种垃圾回收算法。
标记清除算法
标记清楚算法是基础的垃圾回收算法,其过程分为标记和清除两个阶段。在标记阶段标记所有需要回收的对象,在清除阶段清除可回收的对象并释放其所占用的内存空间。由于标记清除算法在清除对象所占用的内存空间后并没有重新整理可用的内存空间,因此如果内存中可被回收的小内存居多,则会引起内存碎片化的问题,继而引起大对象无法获得连续可用空间的问题。
复制算法
复制算法是为了解决标记清除算法内存碎片化的问题设计的。复制算法首先将内存划分为两块大小相等的内存区域,即区域1和区域2.新生成的对象都被存放在区域1,在区域1大的对象存储满之后,会对区域1进行一次标记,并将标记后仍然存活的对象全部复制到区域2中,直接清理整个区域1内存即可。但是同一时刻智能有一个内存区域可用,存在大量的内存浪费。
标记整理算法
结合了标记清除算法和复制算法的优点,其标记阶段和标记清除的标记阶段相同,在标记完成之后将存活的对象移到内存的另一端,然后清除该段对象,并是否内存。
分代收集算法
分代收集算法根据对象的不同类型,将内存分为不同的区域,jvm将堆内存分为新生代和老年代。新生代主要存放新生成的对象,其特点是对象数量多,但是生命周期短,每次进行垃圾回收时都有大量的对象被回收;老年代主要存放大对象和生命周期长的对象,因此可回收的对象相对较少。因此JVM根据不同的区域对象的特点选择了不同的算法。
目前,大多数JVM在新生代都选择了复制算法,因为在新生代中每次进行垃圾回收时都有大量的对象被回收,需要复制的对象(存活的对象)较少,不存在大量的对象在内存中被来回复制的问题。
JVM将新生代进一步分为一块较大的Eden区,和两块较小的Servivor区,Servivor区又分为ServivorFrom区和ServivorTo区,JVM在运行过程中主要使用Eden区和ServivorFrom区,进行垃圾回收时会将在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区,然后清理Eden区和ServivorFrom区的内存空间。老年代主要存放生命周期较长的对象和大对象,因此每次只有少量非存活对象被回收,因而在老年代采用标记清除算法。
JVM还有一个区域,即方法区的永久代,永久代用来存储Class类,常量,方法描述等。在永久代主要回收废弃的常量和无用的类。
java中的4种引用类型
1 强引用:把一个对象赋给一个引用变量时,这个引用变量就是一个强引用。有强引用的对象一定为可达性状态,所以不会被垃圾回收机制回收。强引用是造成java内存泄漏的主要原因。
2 软引用:通过SoftReference类实现。如果一个对象只有软引用,则在系统内存空间不足时该对象将被回收。
3弱引用:弱引用通过WeakReference实现,如果一个对象只有弱引用,那么在垃圾回收过程中一定会被回收。
4虚引用:虚引用通过PhantomReference类实现,虚引用和引用队列联合使用,主要用于跟踪对象的垃圾回收状态。
垃圾收集器
新生代:Serial(单线程复制算法),ParNew(多线程复制算法),Paraller Scavenge(多线程复制算法)
老年代:CMS(多线程标记清除算法)Serial Old(单线程标记整理算法) Paraller Old(多线程标记整理算法) G1(多线程标记整理算法)
CMS(多线程标记清除算法) 专门为老年代设计的垃圾收集器,其主要目的是达到最短的垃圾回收停顿时间,基于线程的标记算法实现,以便在多线程并发环境下以最短的垃圾收集停顿时间来提高系统的稳定性。
Stop-the-world
在新生代进行的垃圾回收叫做Minor GC.在老年代进行的垃圾回收叫Major GC.Full GC对新生代和老年代都进行GC。在进行垃圾回收时,经常要设计对对象进行移动,因此必须要对对象引用进行更新,而为了保证引用更新的正确性,JVM将暂停所有其他线程,这一过程称为Stop-the-word.导致系统全局停顿。
CMS的回收过程
1 初始标记:在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。
2并发标记:这个阶段紧随初始标记阶段,在初始阶段的基础上继续向下追溯标记。并发标记阶段,程序线程和标记线程并发执行,用户并不会感到停顿。
3 标记预处理:这一阶段仍然是并发的,在这一阶段,会标记在并发标记阶段新进入老年代的对象(有可能是从新生代进入到老年代的对象,也有可能是直接被分配到老年代的对象),这一阶段主要是减少下一阶段重新标记的工作量。因为下一阶段还会进入Stop-the-world.
4重新标记:虚拟机会进入暂停阶段,在这一阶段,会扫描CMS堆中剩余的对象,从跟对象向下扫描。
5并发清理:这一阶段是并发阶段,清理垃圾对象
6并发重置:这一阶段,重置CMS收集器的数据结构,等待下一次的垃圾回收
哪些对象可以作为GCRoots
栈,方法区,本地方法区的对象不受GC管理,可以选择这些区域的对象作为GC Roots
1 虚拟机栈中引用的对象
2 方法区中引用的对象
3 方法区中类静态属性引用的对象
4 本地方法栈中本地方法引用的对象
5 活跃线程的引用对象
GC什么时候会发生
GC分为minorGC和Full GC(也叫MajorGC),对于Minor GC触发的条件:大多数情况下直接在Eden区进行分配,如果Eden区没有足够区域,那么就会发起一次MinorGC。对于Full GC的触发条件:如果老年代没有足够的空间,那么就执行一次Full GC.
Full GC次数太多,如何优化
Full GC是针对整个堆来说的,在最近版本的jdk中默认包括了对永生代即方法区的回收,Full GC的速度一般会比Minor GC慢10倍以上。
1System.gc()方法的调用:
此方法的调用是建议JVM进行FullGC,虽然只是建议而非一定,但很多情况下它会触发Full GC,从而增加Full GC的概率,也增加了间歇性停顿的次数。强烈建议系统能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过-xx:+DisableExplicitGC来禁止RMI调用System.gc();
2老年代空间不足:老年代的对象为新生代对象转入,或者创建的大对象大数组。当老年代空间不足时,会执行FullGC,当执行FullGC后空间仍不足时,则抛出异常;
java.lang.OutOfMemoryError:java heap space
解决方法:为避免上述情况引起的GC,调优时应尽量做到让对象在Minor GC阶段被回收,让对象在新生代多存活一段时间以及不要创建过大的对象与数组;
3方法区空间不足
方法区存放的为一些class类的信息,常类,静态变量等数据,当系统要加载的类,反射的类,调用的方法过多时,方法区可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC,如果经过Full GC仍然回收不了,那么JVM就会抛出如下错误信息:
java.lang.OutOfMemoryError:PermGen space
解决办法:增大方法去的空间或转为使用CMS GC;
4CMS GC出现promotion failed和concurrent mode failure
对于采用CMS进行老年代GC的程序而言,需要注意是否有promotion failed和concurrent mode failure两种状态,当这两种状态出现时可能会触发GC
promotion failed是在进行Minor GC时,survior space放不下,对象只能放入老年代,而此时老年代也放不下造成的。
concurrend mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而老年代空间不足造成的。
5统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间
6堆中分配很大的对象,虽然老年代有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前的对象,这种情况下也会进行Full GC.
可以在上次的Full GC服务后,进行一次碎片整理。
Java面试之GC
最新推荐文章于 2025-10-17 07:55:37 发布
319

被折叠的 条评论
为什么被折叠?



