java gc回收机制_Java中的GC回收机制

本文详细介绍了Java的垃圾回收机制,包括GC回收的原因、时机、算法(引用计数法、可达性分析法)以及四种回收策略(标记清除、复制、标记整理、分代回收)。还讨论了不同类型的垃圾收集器,如Serial、Parallel、CMS等,以及CMS的运行过程。此外,还解释了finalize()方法的作用和生命周期,以及与对象存活状态的关系。
摘要由CSDN通过智能技术生成

为什么要进行GC回收?

当我们新建一个对象时,系统就会为其分配一定的内存空间,而有时候新建的对象没有去使用时,不回收的话会极大浪费内存空间,造成系统效率低下。

什么时候进行GC回收?

1、当CPU空闲的时候

2、执行System.gc()方法的时候

3、堆内存满了以后

GC的算法有引用计数法和可达性分析的算法进行回收

引用计数法:当新建对象就创建一个与之对应的计数器,当对象被使用时计数器就加一,而当不执行此对象时计数器就减一,最终计数器为0的对象将会被回收。

优点:执行速度快

缺点:当存在相互引用时会造成内存泄漏

相互引用

public class test{

public static void main(String []args){

public Object instance=null;

test test1 = new test();

test test2 = new test();

test1.instance = test2;

test.instance = test1;

test1=null;

test2=null;

}

}

内存泄漏:内存泄漏是指无用对象(不再引用对象)持续占有内存,得不到及时的释放,从而造成的内存空间的浪费。

可达性分析法:

通过一系列名为"GC Roots"的对象作为起点,从这些节点往下搜寻,搜索所经过的路径称为引用链,当某个对象没有任何引用链相连时,证明此对象无用,回收(回收不是立即进行的,Java对象在回收前调用finalize()方法——finalize方法通常交给线程优先级较低的线程去处理,有些本应该被回收的对象还会“再生”原因是它复制给了可达对象去引用“借尸还魂法”)。

01fe566d79c4b628106294518ec1a0a4.png

如何回收?

回收算法有四种:标记清除法、复制算法、标记整理法、分代回收法

标记清除法:

对内存中无用的对象进行标记,然后回收,缺点是会造成内存不连续,空间的浪费。

c1075d73dedc69fc28e511b225557330.png

复制算法:

将内存分成两块,每次只使用其中的一块,当这一块使用完之后就将这一块中存活的对象复制到另一块内存中,已经使用完的内存空间清除。缺点是将内存缩成原来的一半。对于存活较多的对象,要进行复制,效率较低。

d4451cef6faaf7f0d9d81bfb81a91023.png

标记整理法:

与标记清除法有一定的类似,不过标记整理法仅对存活的对象进行处理,对不调用的对象不进行处理,将活的对象复制到另一半内存中并整理,不会产生内存碎片。

b871ea318e7a74c99e898057662ccf08.png

分代回收法:(目前大部分JVM所采用的方法)

1、年轻代:对象被创建时,内存的分配首先在年轻代(大的对象可以直接创建在老年代),大部分的对象在创建后就不在使用了,因此很快变得不可达,于是被年轻代的GC机制清理掉,这个GC机制被称为Minor GC或者叫Young GC。年轻代分为三个区,Eden和两个活区(survivor0和survivor1)分别占80%、10%、10%。

将Eden中和survivor中存活的对象拷贝到另一个survivor中(使用的是stop-and-copy的方法)并且清空Eden和survivor中的对象,当Eden满了以后执行minorGC,并将剩余存活的对象放到survivor0中,回收Eden中没有存活的对象。当survivor0满了以后,就将存活的对象复制到survivor1中,不存活的对象回收。依次去存。当两个存活区切换了15次后(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代),仍然存活的对象将被存放近老年代区。

2、老年代:(存放较大的实例化对象和在新生代中存活很久的对象)

使用的是标记整理算法,即标记存活对象,向一端移动,保证内存完整性,然后将未标记的清理掉。当老年代不够用,也会执行majorGC,FullGC.

3、永久代:永久代的回收有两种,一种是常量池中的常量、二是无用的类信息

对无用的类信息进行回收必须保证以下几点:

类的所有实例已经被回收

加载类的ClassLoader已经被回收

类对象的Class对象没有被引用

垃圾收集器

1、Serial收集器

串行收集器是最古老、最高效、最稳定的收集器。

可能产生较长的停顿。

-XX:UseSerialGC

-XX:UseSerial Old

新生代收集器,使用停止-复制算法,使用一个线程进行GC,串行,其他工作线程停止

8446e2a565bf246834b92a414b33b196.png

2、并行收集器

-XX:UseParNewGC(new表示新生代适用)

新生代并行

Serial收集器新生代并行版本

在新生代回收时使用停止复制算法

多线程,需要多核支持

ParallelGCThreads 限制线程数量

51379e2fb395794cc4cff39b1cc3b384.png

3、parallel收集器

类似ParNew

新生代复制算法

老年代标记整理算法

更关注吞吐量

UseParallelGC  :使用parallel收集器+老年代串行

UseParallelOldGC  :使用parallel收集器+老年代并行

其他GC参数

MaxGCPausemills

最大停顿时间,单位毫秒

GCTimeRatio

0-100范围

垃圾收集时间占总时间比

默认99,即最大允许1%的时间做GC

4、CMS收集器

Concurrent Mark Sweep  并发标记清除(应用程序线程与GC线程交替执行)

使用标记清除算法

并发阶段会降低吞吐量

老年代收集器

UseConcMarkSweepGC

资料来源:https://www.cnblogs.com/bluemapleman/p/9277064.html

https://blog.csdn.net/laomo_bible/article/details/83112622

https://www.cnblogs.com/xiaoxi/p/6486852.html

CMS运行的标记过程主要为

1、初始标记(会产生全局停顿)

根可以直接关联到的对象

速度快

2、并发标记(和用户线程一起)

主要的标记过程,标记全部对象

3、重新标记(会产生全局停顿)

由于并发标记时,线程正在运行,因此在正式清理前,再做修正。

4、并发清理(和用户线程一起)

基于标记结果,直接清理对象

8c55f89a7a0539c432065d4c828ccd0a.png

CMS收集器的特点:

尽可能降低停顿

会影响系统整体吞吐量和性能

清理不彻底

finalize()方法详解

1、finalize的作用

(1)finalized是Object类中protected的方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象前使用该方法。

(2)finalized()方法与c++中的析构函数不是对应的。c++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中finalized()调用有不确定性。

(3)不建议用finalized()对“非内存资源”的清理工作,建议用于:1、清理本地对象 ;2、作为某些非内存资源(如Socket、文件)释放的补充:在finalized方法中显式调用其他其他资源释放方法

2、finalized的生命周期

当对象不可达时,GC判断对象是否覆盖了finalized()方法,若未覆盖则直接将对象回收。否则对象未执行finalized()方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalized()方法。执行完毕后,GC再判断对象是否可达,若不可达则回收,否则对象“复活”。

代码例子

package com.demo;

/*

* 此代码演示了两点:

* 1.对象可以再被GC时自我拯救

* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次

* */

public class FinalizeEscapeGC {

public String name;

public static FinalizeEscapeGC SAVE_HOOK = null;

public FinalizeEscapeGC(String name) {

this.name = name;

}

public void isAlive() {

System.out.println("yes, i am still alive :)");

}

@Override

protected void finalize() throws Throwable {

super.finalize();

System.out.println("finalize method executed!");

System.out.println(this);

FinalizeEscapeGC.SAVE_HOOK = this;

}

@Override

public String toString() {

return name;

}

public static void main(String[] args) throws InterruptedException {

SAVE_HOOK = new FinalizeEscapeGC("leesf");

System.out.println(SAVE_HOOK);

// 对象第一次拯救自己

SAVE_HOOK = null;

System.out.println(SAVE_HOOK);

System.gc();

// 因为finalize方法优先级很低,所以暂停0.5秒以等待它

Thread.sleep(500);

if (SAVE_HOOK != null) {

SAVE_HOOK.isAlive();

} else {

System.out.println("no, i am dead : (");

}

// 下面这段代码与上面的完全相同,但是这一次自救却失败了

// 一个对象的finalize方法只会被调用一次

SAVE_HOOK = null;

System.gc();

// 因为finalize方法优先级很低,所以暂停0.5秒以等待它

Thread.sleep(500);

if (SAVE_HOOK != null) {

SAVE_HOOK.isAlive();

} else {

System.out.println("no, i am dead : (");

}

}

}

运行结果

leesf

null

finalize method executed!

leesf

yes, i am still alive :)

no, i am dead : (

一些疑问

1、GC是怎么判断对象是被标记的

通过枚举根节点的方式,通过jvm提供的一种oopMap的数据结构,简单来说就是不要再通过遍历内存里的东西,而是通过OOPMap的数据结构去记录该记录的信息,比如说它可以不用去遍历整个栈,而是扫描栈上面引用的信息并记录下来。

2、什么时候触发GC

minor GC(young GC):当年轻代中eden区分配满的时候触发【young GC后部分存活的对象会已到老年代(比如对象熬过15轮),所以old gen的占用量通常会变高】

full GC:

1)手动调用system.gc()方法【增加full GC的频率,不建议使用而是让jvm自己管理内存,可以设置-XX:DisableExplicitGC来禁止RMI调用system.gc】

2)发现perm gen(如果存在永久代的话)需分配空间但已经没有足够的空间

3)老年代空间不足,比如说新生代的大对象数组晋升到老年代可能导致老年代空间不足。

4)CMS GC时出现promotion Faield【pf】

5)统计的到的minor GC晋升到旧生代的平均大小大于老年代的剩余空间。

full gc导致了concurrent mode failure,而不是concurrent mode failure错误导致触发full gc,真正触发full gc 可能是promotion failure。

3、cms收集器是否会扫描年轻代

会,在初始标记的时候会扫描新生代。

虽然cms是老年代收集器,但是我们 知道年轻代对象是可以晋升老年代的,为了空间分配担保,还是有必要扫描年轻代。

4、什么是空间分配担保

在minor gc前,jvm会检查年老代最大可用空间是否大于新生代所有对象的总空间,如果是的话,则minor gc可以确保安全

如果担保失败,会检查一个配置(handlepromotionfailire),即是否允许担保失败。

如果允许:继续检查老年代最大可用的连续空间是否大于之前的晋升的平均大仙,比如说剩10m,之前每次都有9M左右的新生到老年代,那么尝试一次minor gc,这会比较冒险。如果不允许,而且还小于的情况,则会触发full gc.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值