JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统Java · 成长之路

JVM垃圾回收机制小结

2018-05-14  本文已影响3人  南山羊

本文主要浅谈JAVA回收机制,让初学者对这一块大概有个简单的认识,同时也记录下自己学习的成果,温故而知新。

疑问

1、什么是垃圾回收

对于垃圾JVM的垃圾回收机制这里我们称为 GC ,众所周知,Java 语言不需要像 C++ 那样需要自己申请内存,自己释放内存,这些都是JVM帮我们做好了的,那么JVM对于内存的回收过程就成为垃圾回收(GC)。

2、为什么要进行垃圾回收

程序是运行在物理机之上,而一台物理机的内存数是有上限的,那么程序对于内存的使用也是有上限的,例如Java中大量的 new Object() 新建对象,会占用内存,往往我们在程序中只管新建,没有去管它的回收,那么这些对象在新建之后就会保存在内存当中,随着时间增加大量的垃圾对象就会撑爆内存,导致内存溢出。

那么我们日常开发中为什么很少出现这种情况,这就是JVM的垃圾回收机制,自动帮我们进行了回收,由此可见,为什么要进行垃圾回收,是为了更合理使用内存,避免大量垃圾对象占用内存。

3、如何进行垃圾回收

3.1、判断机制

上面谈到垃圾回收机制,实质是将垃圾对象进行回收,那么问题来了,如何判断一个对象是垃圾对象。

  public class A(){
      public A a;
  }

  public class B(){
    public B b;
  }

  public class demo(){
    public static void main(String[] args){
      A a = new A();
      B b = new B();
      a.b = b;
      b.a = a;
      // 这种情况 这两个对象永远被引用数为1,无法被垃圾回收机制回收。
    }
  }
3.2、回收机制

上文谈到引用计数法存在致命问题,所以这里讲到的回收机制主要讲根搜索算法,当JVM根据根搜索算法找到垃圾对象之后,会如何去回收掉它,在此过程中会出现什么问题,以及各个算法的优势劣势,根据优势劣势如果去优化等等...

i.先来看看简单化后的堆的内存结构:

Java堆 = 年老代 + 年轻代
(空间大小比例一般是3:1)

年轻代 = Eden区 + Survivor区 + Survivor区
(空间大小比例一般是8:1:1)

ii.按照对象存活时间长短,我们可以把对象简单分为三类:

短命对象:存活时间较短的对象,如中间变量对象、临时对象、循环体创建的对象等。这也是产生最多数量的对象,GC回收的关注重点。

长命对象:存活时间较长的对象,如单例模式产生的单例对象、数据库连接对象、缓存对象等。

长生对象:一旦创建则一直存活。

iii.对象分配区域

短命对象存在于年轻代,长命对象存在于年老代,而长生对象则存在于方法区中。
由于GC的主要内存区域是堆,所以GC的对象主要就是短命对象和长命对象这类寿命“有限”的对象。
年轻代回收

当需要在堆中创建一个新的对象,而年轻代内存不足时触发一次GC,在年轻代触发的GC称为普通GC,Minor GC。注意到年轻代中的对象都是存活时间较短的对象,所以适合使用复制算法。这里肯定不会使用两倍的内存来实现复制算法了,牛人们是这样解决的,把年轻代内存组成是80%的Eden、10%的Survivor和10%的Survivor,然后在这些内存区域直接进行复制。

刚开始创建的对象是在Eden中,此时Eden中有对象,而两个Survivor区没有对象,都是空闲区间。第一次Minor GC后,存活的对象被放到其中一个Survivor,Eden中的内存空间直接被回收。在下一次GC到来时,Eden和一个survivor中又创建满了对象,这个时候GC清除的就是Eden和这个放满对象的survivor组成的大区域(占90%),Minor GC使用复制算法把活的对象复制到另一个空闲的survivor区间,然后直接回收之前90%的内存。周而复始。始终会有一个10%空闲的Survivor区间,作为下一次Minor GC存放对象的准备空间。

要完成上面的算法,每次Minor GC过程都要满足:
存活的对象大小都不能超过Survivor那10%的内存空间,不然就没有空间复制剩下的对象了。但是,万一超过了呢?前面我们提到过年老代,对,就是把这些大对象放到年老代。

例如下图中,Eden中有4个对象块,其中AC对象如果被标记为垃圾对象,在GC的时候就会将BD对象复制到Survivor中,然后直接清除Eden跟另外一个Survivor。当对象被Copy的次数超过一个配置值的时候,就会从年轻代移动到年老代。


年轻代.png
年老代回收
a.在年轻代中,如果一个对象的年龄(GC一次后还存活的对象年岁加1)达到一个阈值(可以配置),就会被移动到年老代。
b.Survivor中相同年龄的对象大小总和超过survivor空间的一半,则不小于这个年龄的对象都会直接进入年老代。
c.创建的对象的大小超过设定阈值,这个对象会被直接存进年老代。
d. 年轻代中大于survivor空间的对象,Minor GC时会被移进年老代。

年老代中的对象特点就是存活时间较长,而且没有备用的空闲空间,所以显然不适合使用复制算法了,这个时候使用标记-清除算法或者标记-整理算法来实现GC。负责年老代中GC操作的是全局GC,Major GC,Full GC。
什么时候触发Major GC呢?在Minor GC时,先检测JVM的统计数据,查看历史上进入老年代的对象平均大小是否大于目前年老代中的剩余空间,如果大于则触发Full GC。

3.2、执行机制
串行GC

在搜索扫描和复制过程都是采用单线程实现,适用于单CPU、新生代空间较小或者要求GC暂停时间要求不高的地方。是client级别的默认方式。

并行GC

在搜索扫描和复制过程都是采用多线程实现,适用于多CPU、或者要求GC暂停时间要求高的地方。是server级别的默认方式。

同步GC

同时允许多个GC任务,减少GC暂停时间。主要应用在实时性要求重于总体吞吐量要求的中大型应用,即使如此,降低中断时间的技术还是会导致应用程序性能的降低。

日常优化

年老代空间不足
1)分配足够大空间给old gen。
2)避免直接创建过大对象或者数组,否则会绕过年轻代直接进入年老代。
3)应该使对象尽量在年轻代就被回收,或待得时间尽量久,避免过早的把对象移进年老代。

方法区的永久代空间不足
1)分配足够大空间给。
2)避免创建过多的静态对象。

被显示调用System.gc()
通常情况下不要显示地触发GC,让JVM根据自己的机制实现。

1.年轻代过小(年老代过大)
导致频繁发生GC,增大系统消耗
容易让普通大文件直接进入年老代,从而更容易诱发Full GC。

2.年轻代过大(年老大过小)
导致年老代过小,从而更容易诱发Full GC。
GC耗时增加,降低GC的效率。

3.Eden过大(survivor过小)
Minor GC时容易让普通大文件直接绕过survivor进入年老代,从而更容易诱发Full GC。

4Eden过小(survivor过大)
导致GC频率升高,影响系统性能。

5调优策略
保证系统吞吐量优先
减少GC暂停时间优先
堆设置

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:设置年轻代大小

-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

-XX:MaxPermSize=n:设置持久代大小

收集器设置

-XX:+UseSerialGC:设置串行收集器

-XX:+UseParallelGC:设置并行收集器

-XX:+UseParalledlOldGC:设置并行年老代收集器

-XX:+UseConcMarkSweepGC:设置并发收集器

垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
上一篇下一篇

猜你喜欢

热点阅读