java虚拟机多久触发垃圾回收_java虚拟机——垃圾回收机制

问题1:什么是垃圾回收机制?

在java的虚拟机当中,在我们进行实例化的时候,堆会给我们开辟新的空间存放实例。而由于堆,方法区是线程公有,不会像栈区(线程私有)一样随着线程的销毁而销毁。因此在java虚拟机中必须要有垃圾回收的机制,定时清理内存,防止内存溢出(OutMemory)的情况。

问题2:哪些运行时数据区中的哪些内存需要被GC?

在运行时数据区中,分别存在以下的区域:

90ea88ca50d5d9b00bd5fb63268a1789.png

虚拟机栈,本地方法栈中的内存会随着线程的销毁而清空,而方法区和堆不会自动情况这是垃圾收集器所关注的部分,因此需要JVM进行GC。

问题3:如何判断哪些内存需要被GC?

1、引用计数算法。

当创建对象实例时候,就会给该变量的实例创建一个变量(计数器),初始值为1。当其他变量用这个对象进行赋值的时候,这个对象的变量就会+1。当这个对象过了生命周期或者赋了新的值后,该计数器就会减1.当计数器的值为0,该对象也就会被回收。

优点:对线程的运行影响不大,而且执行快。

缺点:无法检测循环的引用。如父对象引用子对象,子对象引用父对象。这种情况下计数器不可能为0,也不可能被回收。

/*虚拟机参数:-verbose:gc*/

public classGCDemo {private Object instance = null;public static voidmain(String[] args) {

GCDemo gcDemoParent= newGCDemo();

GCDemo gcDemoChild= newGCDemo();

gcDemoParent.instance=gcDemoChild;

gcDemoChild.instance=gcDemoParent;

gcDemoChild= null;

gcDemoParent= null;

System.gc();

}

}

以上的代码可以看出父子互相调用,且执行了System.gc()。

输出结果:

[Full GC (System.gc()) 1216K->560K(15872K), 0.0165474 secs]

1216K代表执行之前的内存,560代表执行GC后的内存,15872代表虚拟机的内存,最后的代表执行时间。通过输出结果可以得知,该虚拟机不是通过引用计数算法来判断对象是否存活。

2、可达性算法:

在我学习的过程中,只知道引用计数算法和可达性算法两种算法判定对象是否存活。

这个图很好的阐释了可达性算法的算法思路。他就像一颗树,不断的进行引用,从GC Root(根集合)开始,不断的通过引用链进行引用。当有对象没有被引用链的时候,就会出现对象不可达的情况,此时就代表对象是不可用的(ObjD Obje)。

c2100be90cff1a24383ba25412d47f86.png

什么可以作为GC Root呢。从网上的资料查找得出,GC的对象包括:

1、虚拟机栈中的引用对象(栈帧中的本地变量表)。

2、方法区中类静态引用的对象。

3、方法区中常量引用对象。

4、本地方法栈中的引用对象。

第一次标记:在对象被发现没有被引用时,会被标记第一次,并不会立刻执行GC。

第二次标记:在对象被标记后进行筛选,看该对象是否有必要执行finalize()方法(拯救自己机会只有一次,如将自己赋值给其他对象),若在该方法中也没有进行连接,则就要挂了。

public classGCDemo {static GCDemo gcDemo = null;protected voidfinalize() throws Throwable {

gcDemo= this; //给gcDemo加了强引用

System.out.println("执行了finalize");

}public static voidmain(String[] args) throws InterruptedException {

gcDemo= newGCDemo();

gcDemo= null; //去掉强引用

System.gc(); //垃圾回收//睡眠一秒,以便于垃圾回收线程清理gcDemo对象。

Thread.sleep(1000);if(null !=gcDemo) {

System.out.println("第一次gc后,alive");

}else{

System.out.println("第一次GC后,die");

}

gcDemo= null; //去掉强引用

System.gc(); //垃圾回收//睡眠一秒,以便于垃圾回收线程清理gcDemo对象。

Thread.sleep(1000);if(null !=gcDemo) {

System.out.println("第二次gc后,alive");

}else{

System.out.println("第二次GC后,die");

}

}

}

以上的代码很好的演示了对象在通过finalize()方法进行自救,以及第二次自救是否成功。

当然,输出结果为:

执行了finalize

第一次gc后,alive

第二次GC后,die

问题3:java虚拟机中存在哪几种引用?

1、强引用:如Object ob = new Object()。通过new出来的实例,就是强引用,如果该对象还在引用,就不会被回收。(可达性算法,引用计数算法都是基于强引用)

2、软引用:有用非必须的对象,JAVA中的SoftReference作为软引用对象。如果内存足够不会被回收,如果内存要溢出时候,就会被回收。如果回收后内存依然溢出,就会出现OutMemory的异常。

3、弱引用。说白了只能活一天的对象。JAVA中的WeekReference作为弱引用对象。在一次生命周期的结束时,无论内存是否足够,都被回收。

4、虚引用。最弱的引用关系,JAVA中的PhantomReferene作为虚引用对象。我不知道干嘛的。

问题4:方法区的垃圾如何回收?

方法区中要回收的主要是两类型:

1、废弃常量。

如何判断废弃常量呢?以字面量回收为例,如果一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

2、无用的类。

如何判断无用的类,满足一下三个条件。

1、该类的所有实例被回收。堆中没有该类的任何实例。

2、加载该类的ClassLoader被回收。

3、该类对于的java.lang.Class对象没有在任何地方被引用,任何地方无法通过放射调用该方法。

问题5:常用的垃圾回收算法?

1、标记-清除算法(Mark-Sweep)

9fa060c1b6e3a3643baecdc844555630.png

分为两个阶段:如图所示,当GCRoot没有引用到B时候,B被进行了标记,然后被清除。存活的对象不进行移动。

缺点:会产生内存碎片。会使得大对象无法创建。

2、复制算法(Copying)

804f895b26a65810aab69d5e3913cda9.png

他将内存分为两块(a1,a2),每次值使用其中一块(a1),当其中一块(a1)内存不足时候,就会将存活对象复制给另一块(a2),这样a1就变成了由原来的满内存变成空闲内存,a2由空闲内存变成有对象的内存。在下一次进行Copy的算法时候,就会在新的有对象的内存(a2)中进行回收。

3、标记整理算法(Mark-Compant)

364722b54055a079e11fb03b5112ffe2.png

过程和标记-清楚算法一样,唯一不同的是会进行对象的移动。解决了内存碎片的问题。

4、分代回收算法。

e0c2fbe7006f42f5835d22dee5a7727e.png

图可得:将对象的生命周期作为内存划分成若干个不同的区域。新生代(YoungGeneration),年老代(OldGeneration),永久代(PermanentGernation)

在新生代中:分为了Eden区(好像是夏娃住的地方,伊甸),survivor1区(From space),survivor2区(To space)(8:1:1的比例)。大部分对象在Eden区中生成,在进行Minor GC时候,将Eden区的存活对象转移至Survivor1区中,其他对象进行回收,当survivor1区中的内存满时候,将Eden区和Survivor1区中的存活对象传至survivor2区中,其他对象进行Minor GC。完成后将两个survivor区进行交换,以此往复。

当survivor2区中的内存不足以放下eden区和survivor1区中对象时候,会讲对象放置在年老代中,若年老代也也放不下了,就执行FullGC,将新生代,年老代的对象进行回收。

Full GC的效率低,因为对象多,但是频率低,常在年老代中进行,System.gc()会触发该GC。

Minor GC的效率高,频率高,常在新生代中进行(不一定等Eden区满才执行)。

年老代中:在年轻代中经历了N次的回收,仍然存活的对象,会被放入年老代中,因此可以认为年老代放的对象的生命周期都很长。而内存也比新生代大,约为新生代的2倍。

永久代中:永久代存放的是静态文件,如java类,方法等。回收的方法在问题4中。

5、垃圾回收器。

bf1cc4bc56273d0cfdcee19c5799456b.png

上图展示了7种不同的垃圾收集器。如果两个收集器之间有线连起来的话,就代表能搭配使用。

年轻代中的收集器包括:Serial收集器、ParNew收集器、ParallelScavenge收集器。老年代中的收集器包括:CMS收集器、MSC收集器,Parallel Old收集器。

1、Serial收集器:

一款年轻代中使用的单线程垃圾收集器。使用的是标记-复制算法。在进行GC时候,其他的用户线程会进行stop the world(STW),会线程的运行,程序会出现卡住的现象。

如果长时间、频繁的STW,会导致系统的反应迟钝。

b1858c6f56feb9a93eadbd759714165d.png

2、ParNew收集器:

由图可得,这是Serial收集器的对线程版,同样是标记-复制算法。在很多时候作为年轻代的首选收集器,且和老年代中的CMS收集器搭配使用。

但如果运行在单核的机器中,他的性能不会比Serial线程好,相反会更差。 PareNew收集器默认开启的垃圾回收线程和当前机器的CPU数量一样,为了控制GC线程的                                                                       数量,我们可以通过-XX:+ParallelGCThreads来控制垃圾收集的线程。

11cf8eeed94496fa56b1c47f1ddeb115.png

3、ParallelScavenge收集器:

同样是一款年轻代的垃圾收集器,同样是多线程操作,同样是标记-复制算法。但是却和ParNew收集器有很大的不同,ParallelScavenge收集器更注重于缩短回收的时间,关注如何控制系统的

吞吐量(CPU用于运行应用程序和CPU总时间的对比),吞吐量=应用程序运行时间/(应用程序运行时间+GC时间)。

bd1930f674976b9c5c4635ae991f3cfa.png

总结:年轻代中分为三种不同的收集器,Serial收集器,ParNew收集器, ParallelScavenge收集器,采用的算法都是标记-复制算法,但是性能会在不同数量的CPU下会有所不同。

4、Serial Old收集器:

由图可得,他是老年版本的Serial收集器,一款单线程收集器,但使用的是标记-整理算法。

858829ab42bfff5790526fdb65be4032.png

5、Parallel Old收集器:

一款老年版本的Parallel Scavenge的收集器,使用标记-整理算法。工作原理和Parallel Scavenge相似,都是关注吞吐量。如果搭配这两款收集器,将可以实现JAVA对吞吐量优先的收集策略。

627495e363ec923fc272bc1f55fe2a08.png

6、CMS收集器:

由图可得,这是一款在老年代中采用标记-清除算法的一款多线程收集器。

8591fc955d6fafe7789223e54109e606.png

CMS收集器一共分成四个阶段:

1、初始标记,在线程到达safepoint时候,会进行第一阶段,因为这里是单线程的操作,因此会发生STW,但是速度很快,基本不会影响线程的运行。

2、并发标记,这个阶段是一个并发阶段,标记过程也比较耗时,但也不影响系统的运作。

3、重新标记,由图可知这是一款多线程的标记工作,但是同时也会STW,重新标记的耗时会比初始标记场,但是远小于并发标记。

4、并发清理,和并发标记一样,会相对耗时,但不影响系统运作。

CMS收集器实现了低延迟并发收集工作,但是也会有不足。

CMS默认开启的垃圾回收线程数量是(CPU+3)/4,随着CPU的增加,垃圾回收线程占用CPU的资源会减少,但是如果CPU少于4个的时候,垃圾回收的占比就好愈来愈大。比如目前CPU有2个,回收的线程占比将会大于50%。因此在在采取该收集器,应当考虑系统是否依赖CPU。

其次,在并发标记的过程中会产生浮动垃圾,由于在标记的过程中产生了垃圾,而CMS也无法进行标记,可能会导致老年代发送MajorGC(Full GC).。在JDK5中,当老年代到达68%的内存时候,就好激发MajorGC,而我们可以通过-XX:CMSInitiatingOccupancyFraction进行控制。

同时,CMS收集器因为算法的问题,在垃圾回收时会产生内存碎片,因此提供了-XX:+UseCMSCompactAtFullCollection参数在有必要时用于压缩处理,但因为该操作是单线程,所以会引起STW。虚拟机还提供了一个"-XX:CMSFullGCsBeforeCompaction"参数,来控制进行过多少次不压缩的Full GC以后,进行一次带压缩的Full GC,默认值是0,表示每次在进行Full GC前都进行碎片整理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值