JVM理解-GC算法-基本参数

一、概念

1、JVM内存模型

首先老规矩,祭上一张自己画的内存模型图
内存模型
画的比较简陋,简单介绍一下,整个JVM占用的内存可分为两个大区,分别是线程共享区和线程私有区,线程共享区和JVM同生共死,所有线程均可访问此区域;而线程私有区顾名思义每个线程各自占有,与各自线程同生共死。这两个大区内部根据JVM规范定义又分为以下几个区:

方法区(Method Area)

方法区主要是放一下类似类定义、常量、编译后的代码、静态变量等,在JDK1.7中,HotSpot VM的实现就是将其放在永久代中,这样的好处就是可以直接使用堆中的GC算法来进行管理,但坏处就是经常会出现内存溢出,即PermGen Space异常,所以在JDK1.8中,HotSpot VM取消了永久代,用元空间取而代之,元空间直接使用本地内存,理论上电脑有多少内存它就可以使用多少内存,所以不会再出现PermGen Space异常。

堆(Heap)

几乎所有对象、数组等都是在此分配内存的,在JVM内存中占的比例也是极大的,也是GC垃圾回收的主要阵地,平时我们说的什么新生代、老年代、永久代也是指的这片区域,至于为什么要进行分代后面会解释。

虚拟机栈(Java Stack)

当JVM在执行方法时,会在此区域中创建一个栈帧来存放方法的各种信息,比如返回值,局部变量表和各种对象引用等,方法开始执行前就先创建栈帧入栈,执行完后就出栈。

本地方法栈(Native Method Stack)

和虚拟机栈类似,不过区别是专门提供给Native方法用的。

程序计数器(Program Counter Register)

占用很小的一片区域,我们知道JVM执行代码是一行一行执行字节码,所以需要一个计数器来记录当前执行的行数。

2、堆内存

堆内存是JVM内存中占用较大的一块区域,对象都在此地分配内存。在堆中,又分为新生代及老年代,新生代中又分三个区域,分别是Eden,Survivor To,Survivor From。堆内存是JVM调优的重点区域,也是这篇博客重点讨论的内容。

3、提出问题

可能看到这里我们都会产生这样一个经典问题

为何堆内存要进行分代?

最简单的回收方式(标记-回收算法)

假设堆内存不进行分代,那么垃圾回收应该如何进行呢?我们可以大胆想象一下,在一大片内存空间中我们分配了若干个对象(假设图中一个黑色方块代表一个字节,分配的对象均占用两个字节)
堆满
此时堆内存满了,是时候来一波垃圾回收操作了,通过某种分析算法,我们分析到某几个对象是需要进行回收(下述此类对象称之为回收对象),我们让无用对象就地清除,回收后的结果如下:
堆一
我们可以看到,回收后的内存支离破碎的,虽然现在还有八个字节的内存空间,但只要有三个字节或以上的对象需要申请内存,那么这片支离破碎依旧无法为其分配内存,因为没有连续的空间。

第一次演化(复制算法)

既然我们没法回收出连续的空间,那我们可以从一开始就把内存分两个大区,平时只用其中一个区,如下图
回收二
当左边内存区满时,就开始一波回收操作,找到那些无需回收的对象(下述称此类对象为存活对象),将它们工工整整地复制到右边的区域中,接着将左边的区域来次大清理,清理后的结果如下:
回收三
这样当需要再分配内存给对象时,就使用右边的区域,而右边的区域此时也有8个字节的连续空间供分配,当右边满了,再如法炮制,将存活对象复制到左边再将右边回收。貌似这样就解决了问题了,但是总感觉有什么地方不对,是的没错,每次只使用一半的内存,未免也太浪费了!

第二次演化(标记-压缩算法)

我们依旧不分区,将整个内存用满, 开始回收垃圾时,我们将存活对象全部移动到左边,然后对边界外的内存进行清理,如下图
回收四
这算是一种折中的做法,起码比第一次演化中的做法更加充分使用内存,也比最开始的做法的空闲内存更加连续。
在这里我们可以再往下思考,在第一次演化中,如果每次回收对象特别多,而存活对象特别少,那么只需要通过少数的复制操作和一次清除就可以实现回收,此时效率会特别高。而在第二次演化中,如果每次回收对象较少,而存活对象较多,则可以采取此策略进行回收确保最终剩余的空间是连续的空间。
到这里其实并不足够完善,毕竟上述几种演化都有缺点和优点,有没有办法可以取长补短呢?
在开发中,其实我们可以发现,大多数对象都是在方法体中new出来,new完使用后就不再使用了,此时该对象即可进行回收。所以这一类的对象有个特点就是朝生夕死。假如在方法执行完,该对象的引用还被持有着,证明该对象是比较重要的对象,越到后面要回收则越来越困难。这个情况不就刚好符合上述两种演化的情况,当对象刚出生时,我们可以将其使用演化一的方式进行回收,当使用演化一的方式回收不了的对象,则证明该对象为比较重要的对象,我们就可以采用演化二的方式进行回收。这样我们可以对我们的内存进行分区

第一次分区

分区一
当new对象时,内存均从上图右上方的区域申请,当右上方的内存区域满时,则进行一次复制算法进行垃圾回收。从上面的思考我们知道,绝大多数新对象都有朝生夕死的特点,所以在这次的垃圾回收中,存活的对象寥寥无几,然后存活的对象全部塞到右下方区域。在下一次垃圾回收到来时,根据上述分析,之前存活的对象绝大多数还会继续存活,我们将经历过一次垃圾回收的对象年龄+1,可见大多数的对象都熬不过两岁,一般在一岁时就被回收了。而当对象经历了多次垃圾回收仍然存活,此时它很难被回收了,我们可以将其移到左边的区域,另外右边上下俩区域都满了时,则通过垃圾回收将存活对象的那一边区域也移动到左边区域中。当左边区域满时,可通过标记-压缩算法进行垃圾回收。在这种分区方式中,左侧区域称之为老年代,而右侧区域则为新生代。新生代使用复制算法进行一次垃圾回收,称之为Minor GC,而复制完后如果老年代区域不够,也会触发老年代使用标记-压缩算法进行垃圾回收,称之为Major GC,一般Major GC会伴随着Minor GC,所以也称为Full GC。
在上述分区中,新生代仍然只有一半的区域可以用,之前使用一半区域的原因是考虑到有可能所有对象都是存活对象,这样才足够完全复制,但现在有老年代的存在,再考虑到此区域每一次回收时仅有少数对象需要复制,分区方式是否还有优化的空间呢?

第二次分区

现代分区
这个分区是在第一次分区的基础上,将新生代分为三部分,分别是伊甸园、幸存区S0,幸存区S1,伊甸园内存占比为8:1:1,S0与S1大小相同。对象的一生如下:
①所有对象都在伊甸园出生,当伊甸园占满时,开始进行一次Minor GC,此次GC会将已存活的对象复制到S0区中
②伊甸园区又被占满,此时又进行一次Minor GC,伊甸园存活的对象又复制到S0区。
③在若干次GC后,幸存区S0也满了,此时Minor GC会对伊甸园和幸存区S0的
做一次垃圾回收,将两个区存活的对象复制到幸存区S1中,再把伊甸园和S0清空,最后把S1的内存与S0交换,此时S1又腾空了,S0剩下一些老对象。
④又经历若干次GC,幸存区S0已经放满了经历过N次GC都回收不了的老对象,此时会将老对象复制到老年代中,腾空幸存区。
⑤并非当幸存区被老对象占满才复制到老年代中,当老对象年龄达到15岁,即经历过15次GC都还活着的,也会复制到老年代中,另外伊甸园中如果诞生了一个比幸存区还大的对象,那么该对象回收不了时,也会直接送入到老年代中。
⑥又经历过若干次GC后,老年代也满了,那么此时它会进行一次Major GC。

动图演示

上述过程使用文字描述可能比较抽象,下面用动图简单来演示一次Minor GC。
动图

二、垃圾回收涉及的算法

在垃圾回收中,涉及的算法主要有以下五个

  • 引用计数算法
  • 可达性分析
  • 标记-回收算法
  • 标记-压缩算法
  • 复制算法
    前两个算法用于判断对象是否需要回收,其原理简单讲,引用计数算法就是计算对象被谁引用,一旦有其它对象引用此对象,引用次数加一,而GC时引用次数大于零的对象则判断为存活对象,但此算法无法解决循环引用问题,如A引用B,B引用A,此时A与B均无法回收,所以现在JVM不采用此算法;而可达性算法则从GCRoot出发,若A引用B,B引用C,则通过A可以到达C,此时ABC三个对象均不进行回收。后面的三个算法为回收策略,其思路在第一章有提及,在这里就不加赘述,下面总结一下三个算法优缺点:
算法优点缺点
标记-回收算法暂无标记和清除效率低、可用空间不连续
标记-压缩算法实现简单,运行高效内存空间利用不充分
复制算法内存空间利用率高性能较低

三、JVM常用参数

  • Xss:每个线程的栈大小
  • Xms:堆空间的初始值
  • Xmx:堆空间最大值、默认为物理内存的1/4,一般Xms与Xmx最好一样
  • Xmn:年轻代的大小
  • XX:NewRatio :新生代和年老代的比例
  • XX:SurvivorRatio :伊甸园区和幸存区的占用比例
  • XX:PermSize:设定内存的永久保存区域(1.8已废除)
  • XX:MetaspaceSize:1.8使用此参数替代上述参数
  • XX:MaxPermSize:设定最大内存的永久保存区域(1.8已废除)
  • XX:MaxMetaspaceSize:1.8使用此参数替代上述参数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值