什么是GC?
GC是Java的垃圾回收机制,程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存资源,最终将导致内存溢出,GC垃圾回收机制就是对这些无效资源的处理,是对内存资源的管理。
在那些场景使用GC?
排查内存溢出,内存泄露,程序性能调优,解决并发场景下垃圾回收造成的性能优化
什么时候进行垃圾回收?
当cpu处在空闲的时候自动进行回收在堆内存存储满了之后
主动调用System.gc()后尝试进行回收,调用时,垃圾收集器将运行以回收未使用的内存空间。它将尝试释放被丢弃对象占用的内存。然而System.gc()附带一个免责声明,无法保证对垃圾收集器的调用。
GC问题发生在JVM那个部分?
GC主要发生在堆中,堆区由所有线程共享,在虚拟机启动时创建。堆区主要用于存放对象实例及数组,所有new出来的对象都存储在该区域。
JVM 虚拟栈,本地方法栈,程序计数器不需要进行垃圾回收,因为他们的生命周期是和线程同步的,随着线程的销毁,他们占用的内存会自动释放。所以,只有方法区和堆区需要进行垃圾回收,回收的对象就是那些不存在任何引用的对象。
如何判断对象已死(或能够被回收)
引用记数法
既然没有引用就可以回收,引用计数法应运而生。简单的来说就是判断对象的引用数量。
实现方式:给对象共添加一个引用计数器,每当有引用对他进行引用时,计数器的值就加1,当引用失效,也就是不在执行此对象,它的计数器的值随之减1,若某一个对象的计数器的值为0,那么表示这个对象没有被其他对象引用,也就是意味着是一个失效的垃圾对象,就会被GC进行回收。
缺点:无法解决对象减互相循环引用的问题。即当两个对象循环引用时,引用计数器都为1,当对象周期结束后应该被回收却无法回收,造成内存泄漏。
可达性分析算法
目前主流使用的都是可达性分析算法来判断对象是否存活。算法基本思路:以“GC Roots”作为对象的起点,从此节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
哪些对象可作为GC Roots?
虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(Native方法)引用的对象;
活跃线程的引用对象。
在可达性分析法中,判定一个对象 objA 是否可回收,至少要经历两次标记过程:如果对象 objA 到 GC Roots没有引用链,则进行第一次标记。
如果对象 objA 重写了 finalize() 方法,且还未执行过,那么 objA 会被插入到 F-Queue 队列中,由一个虚拟机自动创建的、低优先级的 Finalizer 线程触发其 finalize() 方法。finalize() 方法是对象逃脱死亡的最后机会,GC 会对队列中的对象进行第二次标记,如果 objA 在 finalize() 方法中与引用链上的任何一个对象建立联系,那么在第二次标记时,objA 会被移出“即将回收”集合。
Java中四种引用
强引用(Strong Reference)
强引用就是值在程序代码中普遍存在的,用 new 关键字创建的对象都是强引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用(Soft Reference)
软引用是用来描述一些还有用但并非必需的对象,在系统将要发生内存溢出之前,将会吧这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。可用来实现高速缓存。软引用对象在回收时会被放入引用队列(ReferenceQueue)。
弱引用(Weak Reference)
弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次 GC 发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉该类对象。弱引用对象在回收时会被放入引用队列(ReferenceQueue)。
虚引用(Phantom Reference)
虚引用被称为幽灵引用或幻象引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得对象实例。任何时候都可能被回收,一般用来跟踪对象被垃圾收集器回收的活动,起哨兵作用。必须和引用队列(ReferenceQueue)联合使用。
垃圾收集算法
标记-清除(Mark-Sweep)算法
这是最基础的算法,标记-清除算法就如同它的名字样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。这种算法的不足主要体现在效率和空间,从效率的角度讲,标记和清除两个过程的效率都不高;从空间的角度讲,标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。
复制(Copying)算法
复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可
不过这种算法有个缺点,内存缩小为了原来的一半,这样代价太高了。现在的商用虚拟机都采用这种算法来回收新生代,不过研究表明 1:1 的比例非常不科学,因此新生代的内存被划分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。每次回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另外一块 Survivor 空间上,最后清理掉 Eden 和刚才用过的 Survivor 空间
标记-整理(Mark-Compact)算法
复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。根据老年代的特点,有人提出了另外一种标记-整理算法,过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。
jdk1.8之前分:三代:年轻代、老年代、永久代
新生代(复制算法为主)
新生代的目标就是尽可能快速的收集掉那些生命周期较短的对象,一般情况下新生成的或者朝生夕亡的对象一般都是首先存放在新生代里面。
新生代的迭代更新很快,大多数对象的存活时间都比较短,所以对 GC 的效率和性能要求较高,因此使用复制算法,保证了每次 GC 仅浪费 10% 的内存,内存利用率也有所提高。
老年代(标记-整理算法为主)
在新生代经过很多次垃圾回收之后仍然存活的对象(默认15岁),就会被放入老年代中,因为老年代中的对象大多数是存活的,所以使用算法是 标记-整理 算法。老年代执行的 GC 是Full GC。
永久代
永久代用于存放静态文件,如 Java 类、方法等。该区域回收与上述“方法区内存回收”一致。但是永久代是使用的堆内存,如果创建对象太多容易造成内存溢出OOM(OutOfMemory)。
jdk8 以后便取消了永久代的说法,而是用元空间代替,所存内容没有变化,只是存储的地址有所改变,元空间使用的是主机内存,而不是堆内存,元空间的大小限制受主机内存限制,这样有效的避免了创建大量对象时发生内存溢出的情况。
减少GC开销的措施
(1)尽量少显示地调用 System.gc();
(2)减少临时对象的引用;临时对象退出函数后 ,
(3)对象使用完后,设置为指向null;这样会方便系统查找到空对象,更快回收掉内存;
(4)能用int等基本数据类型,就尽量不要使用Integer等引用类型,基本类型占用的资源比引用类型要小得多;
(5)尽量少地使用static变量。static 变量是全局性的,系统在堆中为其分配内存,GC无法回收该内存;
(6)对于需要使用变长的字符串变量,尽量使用StringBuffer而不是String。String每赋值一次,就会重新分配一次内存,String str = str1+str2+str3+str4+str5,每多一个“+”,就会多
创建一个对象。
(7)分散创建和删除对象的时间。一次性创建或者删除太多的对象,会导致内存突然变得紧张或者一次性释放太对,不利用内存的合理使用。
GC原理:
对象被创建时,首先分配在年轻代的Eden区(大对象可以直接被创建分配到年老代),大多数对象在被创建不久后就不在使用,因此很快变得不可达,当Eden区满时,执行Minor GC将不可达对象清除,并将剩余的对象复制到一个存活区S0中,此时,另一个存活区S1是空白。
到下一次Eden区满,在执行一次Minor GC,将Eden区不可达对象清除,将存活对象复制到S1区,并将刚才S0区中不可达对象清除,将此时Eden区存活对象和S0存活对象一并拷贝到S1区。
当两个幸存区切换几次,达到设定阈值,将任然存活的对象复制到年老区中,此时其实只有很小的一部分。
年轻代采用算法是“停止-复制算法”。
在年轻到经过多次停止-复制法清除不可达对象后,存活的对象都被复制到年老代中,需要更多的内存空间,所以年老代的空间一般比年轻代大,但在老年代发生GC的次数比较少,当年老带内存不足时,执行Major GC(Full GC)清除不可达对象,采用“标记-整理算法”,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。
永久代,主要回收两种:常量池中的常量,无用的类信息。常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证三点:
1)类的所有实例都已经被回收
2)加载类的ClassLoader已经被回收
3)类对象的Class对象没有被引用(即没有通过反射引用该类的地方)
版权声明:本文为CSDN博主「liubotian1995」的原创文章,遵循CC 4.0 BY-SA版权协议,