2019-2020年人教统编JVM内存模型和垃圾收集课件
- 格式:ppt
- 大小:784.73 KB
- 文档页数:35
Java虚拟机(JVM)的内存垃圾回收机制主要涉及自动内存管理和垃圾回收两个核心功能。
自动内存管理主要是针对对象内存的回收和对象内存的分配。
JVM的堆是垃圾收集器管理的主要区域,也被称作GC堆(Garbage Collected Heap)。
大部分情况下,对象都会首先在Eden区域分配。
在一次新生代垃圾回收后,如果对象还存活,则会进入s0或者s1,并且对象的年龄还会加1(Eden区->Survivor区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。
垃圾回收是JVM自动内存管理的另一个重要方面。
主要有两种通用的垃圾回收方法:引用计数法和可达性分析算法。
引用计数法为每个对象增加一个计数器,当该对象被引用时,计数器加一,当引用失效时,计数器减一。
当计数器为零时,该对象就可以被回收了。
这种方法无法解决循环引用的问题。
可达性分析算法是通过GC Root的对象作为起始节点,通过引用向下搜索,所走过的路径称为引用链。
当对象没有任何一条引用链链接的时候,就会被认定为垃圾。
可作为GC Root的对象包括:类加载器,Thread,虚拟机栈的本地变量表,static成员,常量引用,本地方法栈的变量等等。
大部分情况下,对象都会首先在Eden区域分配,在一次新生代
垃圾回收后,如果对象还存活,则会进入s0或者s1,并且对象的年龄还会加1(Eden区->Survivor区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。
以上信息仅供参考,如果还想了解更多信息或遇到相关问题,建议咨询专业人士。
图解JVM垃圾内存回收算法图解JVM垃圾内存回收算法这篇⽂章主要介绍了图解JVM垃圾内存回收算法,由于年轻代堆空间的垃圾回收会很频繁,因此其垃圾回收算法会更加重视回收效率,下⾯博主和⼤家来⼀起学习⼀下吧前⾔⾸先,我们要讲的是JVM的垃圾回收机制,我默认准备阅读本篇的⼈都知道以下两点:JVM是做什么的Java堆是什么因为我们即将要讲的就是发⽣在JVM的Java堆上的垃圾回收,为了突出核⼼,其他的⼀些与本篇不太相关的东西我就⼀笔略过了众所周知,Java堆上保存着对象的实例,⽽Java堆的⼤⼩是有限的,所以我们只能把⼀些已经⽤完的,⽆法再使⽤的垃圾对象从内存中释放掉,就像JVM帮助我们⼿动在代码中添加⼀条类似于C++的free语句的⾏为然⽽这些垃圾对象是怎么回收的,现在不知道没关系,我们马上就会讲到怎么判断对象为垃圾对象在了解具体的GC(垃圾回收)算法之前,我们先来了解⼀下JVM是怎么判断⼀个对象是垃圾对象的顾名思义,垃圾对象,就是没有价值的对象,⽤更严谨的语句来说,就是没有被访问的对象,也就是说没有被其他对象引⽤,这就牵引出我们的第⼀个判断⽅案:引⽤计数法引⽤计数法这种算法的原理是,每有⼀个其他对象产⽣对A对象的引⽤,则A对象的引⽤计数值就+1,反之,每有⼀个对象对A对象的引⽤失效的时候,A对象的引⽤计数值就-1,当A对象的引⽤计数值为0的时候,其就被标明为垃圾对象这种算法看起来很美好,了解C++的应该知道,C++的智能指针也有类似的引⽤计数,但是在这种看起来“简单”的⽅法,并不能⽤来判断⼀个对象为垃圾对象,我们来看以下场景:在这个场景中,A对象有B对象的引⽤,B对象也有A对象的引⽤,所以这两个对象的引⽤计数值均不为0,但是,A、B两个对象明明就没有任何外部的对象引⽤,就像⼤海上两个紧挨着的孤岛,即使他们彼此依靠着,但仍然是孤岛,其他⼈过不去,⽽且由于引⽤计数不为0,也⽆法判断为垃圾对象,如果JVM中存在着⼤量的这样的垃圾对象,最终就会频繁抛出OOM异常,导致系统频繁崩溃总⽽⾔之,如果有⼈问你为什么JVM不采⽤引⽤计数法来判断垃圾对象,只需要记住这⼀句话:引⽤计数法⽆法解决对象循环依赖的问题可达性分析法引⽤计数法已经很接近结果了,但是其问题是,为什么每有⼀个对象来引⽤就要给引⽤计数值+1,就好像有⼈来敲门就开⼀样,我们应该只给那些我们认识的、重要的⼈开门,也就是说,只有重要的对象来引⽤时,才给引⽤计数值+1但是这样还不⾏,因为重要的对象来引⽤只要有⼀个就够了,并不需要每有⼀个引⽤就+1,所以我们可以将引⽤计数法优化为以下形式:给对象设置⼀个标记,每有⼀个“重要的对象”来引⽤时,就将这个标记设为true,当没有任何“重要的对象”引⽤时,就将标记设为false,标记为false的对象为垃圾对象这就是可达性分析法的雏形,我们可以继续进⾏修正,我们并不需要主动标记对象,⽽只需要等待垃圾回收时找到这些“重要的对象”,然后从它们出发,把我们能找到的对象都标记为⾮垃圾对象,其余的⾃然就是垃圾对象我们将上⽂提到的“重要的对象”命名为GC Roots,这样就得到了最终的可达性分析算法的概念:创建垃圾回收时的根节点,称为GC Roots,从GC Roots出发,不能到达的对象就被标记为垃圾对象其中,可以作为GC Roots的区域有:虚拟机栈的栈帧中的局部变量表⽅法区的类属性和常量所引⽤的对象本地⽅法栈中引⽤的对象换句话说,GC Roots就是⽅法中的局部变量、类属性,以及常量垃圾回收算法终于到本⽂的重点了,我们刚刚分析了如何判断⼀个对象属于垃圾对象,接下来我们就要重点分析如何将这些垃圾对象回收掉标记-清除算法标记-清除很容易理解,该算法有两个过程,标记过程和清除过程,标记过程中通过上⽂提到的可达性分析法来标记出所有的⾮垃圾对象,然后再通过清除过程进⾏清理⽐⽅说,我们现在有下⾯的这样的⼀个Java堆,已经通过可达性分析法来标记出所有的垃圾对象(⽤橙⾊表明,蓝⾊的是普通对象):然后我们通过清除阶段进⾏清理,结果是下图:发现什么问题了吗,没错,清理完后的空间是不连续的,也就是说,整个算法最⼤的缺点就是:会出现⼤量的空间碎⽚,当需要分配⼤对象时,会触发FGC,⾮常消耗性能这⾥引出⼀个FGC的概念,为了避免主题跑偏,本⽂中暂时不进⾏深⼊,只需要知道垃圾回收分为YGC(年轻代垃圾回收)和FGC(完全垃圾回收),可以把YGC理解为扫扫地,倒倒垃圾,把FGC理解为给家⾥来个⼤扫除复制算法复制算法将Java堆划分为两块区域,每次只使⽤其中的⼀块区域,当垃圾回收发⽣时,将所有被标记的对象(GC Roots可达,为⾮垃圾对象)复制到另⼀块区域,然后进⾏清理,清理完成后交换两块区域的可⽤性这种⽅式因为每次只需要⼀整块⼀起删除即可,就不⽤⼀个个地删除了,同时还能保证另⼀块区域是连续的,也解决了空间碎⽚的问题整个流程我们再来看⼀遍1.⾸先我们有两块区域S1和S2,标记为灰⾊的区域为当前激活可⽤的区域:2.对Java堆上的对象进⾏标记,其中蓝⾊的为GC Roots可达的对象,其余的均为垃圾对象:3.接下来将所有可⽤的对象复制到另⼀块区域中:4.将原区域中所有内容删除,并将另⼀块区域激活这种⽅法的优缺点也很明显:优点:解决了空间不连续的问题缺点:空间利⽤率低(每次只使⽤⼀半)为了解决这⼀缺点,就引出了下⾯这个算法优化的复制算法⾄于为什么不另起⼀个名字,其实是因为这个算法也叫做复制算法,更确切的说,刚才介绍的只是优化算法的雏形,没有虚拟机会使⽤上⾯的那种复制算法,所以接下来要讲的,就是真正的复制算法这个算法的思路和刚才讲的⼀样,不过这个算法将内存分为3块区域:1块Eden区,和2块Survivor区,其中,Eden区要占到80%这两块Survivor区就可以理解为我们刚才提到的S1和S2两块区域,我们每次只使⽤整个Eden区和其中⼀块Survivor区,整个算法的流程可以简要概括为:1.当发⽣垃圾回收时,将Eden区+Survivor区中仍然存活的对象⼀次性复制到另⼀块Survivor区上2.清理掉Eden区和使⽤的Survivor区中的所有对象3.交换两块Survivor的激活状态光看⽂字描述⽐较抽象,我们来看图像的形式:1.我们有以下这样的⼀块Java堆,其中灰⾊的Survivor区为激活状态2.标记所有的GC Roots可达对象(蓝⾊标记)3.将标记对象全部复制到另⼀块Survivor区域中4.清理掉Eden区和激活的Survivor区中的所有对象,然后交换两块区域的激活状态以上就是整个复制算法的全过程了,有⼈可能会问了,为什么Survivor区这么⼩,就不怕放不下吗?其实平均来说,每次垃圾回收的时候基本都会回收98%左右的对象,也就是说,我们完全可以保证⼤部分情况下剩余的对象都⼩于10%,放在⼀块Survivor区中是没问题的。
JVM内存模型以及垃圾回收JA V A堆的描述如下:内存由Perm 和Heap 组成. 其中Heap = {Old + NEW = { Eden , from, to } }JVM内存模型中分两大块,一块是NEW Generation, 另一块是Old Generation. 在New Generation中,有一个叫Eden的空间,主要是用来存放新生的对象,还有两个Survivor Spaces (from,to), 它们用来存放每次垃圾回收后存活下来的对象。
在Old Generation中,主要存放应用程序中生命周期长的内存对象,还有个Permanent Generation,主要用来放JVM自己的反射对象,比如类对象和方法对象等。
垃圾回收描述:在New Generation块中,垃圾回收一般用Copying的算法,速度快。
每次GC的时候,存活下来的对象首先由Eden拷贝到某个Survivor Space, 当Survivor Space空间满了后, 剩下的live对象就被直接拷贝到Old Generation中去。
因此,每次GC后,Eden内存块会被清空。
在Old Generation块中,垃圾回收一般用mark-compact的算法,速度慢些,但减少内存要求. 垃圾回收分多级,0级为全部(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收NEW中的垃圾,内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。
当一个URL被访问时,内存申请过程如下:A. JVM会试图为相关Java对象在Eden中初始化一块内存区域B. 当Eden空间足够时,内存申请结束。
否则到下一步C. JVM试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收), 释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区D. Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor 区的对象会被移到Old区,否则会被保留在Survivor区E. 当OLD区空间不够时,JVM会在OLD区进行完全的垃圾收集(0级)F. 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”JVM调优建议:ms/mx:定义YOUNG+OLD段的总尺寸,ms为JVM启动时YOUNG+OLD的内存大小;mx为最大可占用的YOUNG+OLD内存大小。
一JVM内存模型1.1Java栈Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。
它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文。
栈空间随着线程的终止而释放。
StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的。
1.2堆Java中堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等。
1.2.1GenerationJVM堆一般又可以分为以下三部分:➢ PermPerm代主要保存class,method,filed对象,这部门的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到ng.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。
➢∙TenuredTenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。
➢∙YoungYoung区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Young区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。
1.2.2Sizing the GenerationsJVM提供了相应的参数来对内存大小进行配置。
JVM内存模型引⾔JVM是运⾏在操作系统之上,⽽JVM是要想了解JVM虚拟机运⾏的内幕,必须要先知道其内存模型根据JVM规范,JVM内存共分为五块区域本⽂围绕这个⼏个区域,剖析JVM运⾏时数据区JVM运⾏时数据区1.程序计数器程序计数器是线程私有的,也就是意味着,每⼀个线程都有⾃⼰的⼀个独⽴程序计数器,⾥⾯记载着当前线程执⾏下⼀条字节码指令的⾏号,它的⽣命周期跟其对应的线程⽣命周期⼀样。
这⾥的程序计数器指的是JVM运⾏时指的下⼀条字节码的⾏号,跟CPU当中的程序计数器不⼀样,可以这么理解,这⾥的程序计数器是JVM指令级别的,⽽CPU内部的程序计数器是位于CPU的⼀个物理空间,通常我们把它称作——寄存器,它指向的是下⼀条CPU指令的内存地址,它是CPU指令级别的。
特点线程私有占⽤内存很⼩在内存当中不可能出现OOM(OutOfMemory),所有对⼀这块区域也不存在垃圾回收这⼀说法如果是在执⾏native⽅法,则是未指定值(undefined),因为程序计数器不负责本地⽅法栈。
2.JVM虚拟机栈虚拟机栈也是线程私有的,⼀个线程对应着虚拟机栈⾥⾯⼀块区域。
线程运⾏过程中,每个⽅法在执⾏的时候会创建⼀个栈帧,存储了局部变量,操作数,动态链接,⽅法返回地址。
每个⽅法从调⽤到执⾏完毕,对应⼀个栈帧在虚拟机栈中的⼊栈和出栈。
如果线程请求的栈深度⼤于虚拟机所允许的深度,则StackOverflowError(递归调⽤)。
如果虚拟机栈可以动态扩展,扩展到⽆法申请⾜够的内存,则OutOfMemoryError。
3.堆(Heap)堆是 JVM 所管理的最⼤的⼀块内存空间,主要⽤于存放各种类的实例对象。
在 Java 中,堆被划分成两个不同的区域:新⽣代 ( Young )、⽼年代 ( Old )。
新⽣代 ( Young ) ⼜被划分为三个区域:Eden、From Survivor、To Survivor。
这样划分的⽬的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。