关于JVM内存回收(垃圾回收)

几个问题要搞清楚
问题一:什么叫垃圾回收机制?
垃圾回收是一种动态存储管理机制,它自动的释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能.当一个对象不再被引用时,内存回收它占领的空间,以便空间被后来的新对象使用,以免造成内存泄漏

问题二:Javel的垃圾回收有什么特点?
Java语言不允许程序员直接控制内存空间的使用.内存空间的分配和回收都是由JRE负责在后台自动进行的,尤其是无用内存空间的回收操作,只能由运行环境提供的一个超级线程进行监测和控制

问题三:垃圾回收器什么时候会运行
一般是在CPU空闲或空间不足时自动进行垃圾回收,而程序员无法控制垃圾回收的时机和顺序等

问题四:什么样的对象符合垃圾回收条件
当没有任何获得线程能访问一个对象时,该对象就符合垃圾回收条件.

问题五:垃圾回收器时怎样工作的?
垃圾回收器如发现一个对象不能被任何活线程访问时,他将认为该对象符合删除条件,就将其加入回收队列不是立即销毁对象,任何销毁并释放内存是无法预知的.垃圾回收不能强制执行,然而Java提供了一些方法(如:System.gc()方法).允许你请求JVM执行垃圾回收,而不是要求,虚拟机会尽其所能满足请求,但是不能保证JVM从内存中删除所有不用的对象.

问题六:一个Java程序能够耗尽内存吗?
可以.垃圾收集系统尝试在对象不被使用时把它们从内从中删除.然而,如果保持太多活对象,系统则可能会耗尽内存.垃圾回收器不能保证有足够的内存,只能保证可用内存尽可能得到高效的管理.

问题七:
程序中的数据类型不一样存储地方也不一样,原生数据类型存储在Java栈中,方法执行结束后就会消失;对象类型存储在Java堆中,可以被共享,不一定随着方法执行结束而消失.

问题八:如何检测垃圾(垃圾检测机制)
垃圾收集器的两个任务:正确检测出垃圾对象和释放垃圾对象占用的内存空间,而前者时关键所在.
垃圾收集器有一个根对象集合,包含的元素有1)方法中局部变量的引用 ;2)Java操作栈中对象的引用;
3)常量池中对象引用;4)本地方法持有的对象引用;5)类的class对象
JVM在垃圾回收的时候会检查堆中的所有对象是否会被根对象直接引用或间接引用,能够被根对象到达的叫做活动对象,否则叫做非活动对象可以被回收.

内存回收-gc原理

jvm内存回收采用的时基于分代的垃圾回收算法
Sun的JVM Generational Collecting(垃圾回收)原理是这样的:把对象分为 年青带(yong)、年老代(tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法
[设计思路]:把对象按照寿命长短来分组,分为年青代和老年代,新创建的对象被分在年青代,如果对象经过几次回收后仍然存活,那么再把这个对象划分到老年代.年老代的收集频度没有那么频繁,这样就减少了每次垃圾收集时所需要的扫描的对象和数量,提高垃圾回收效率

1.Yong

年青代分三个区.一个Eden区,两个Survivor区. 大部分对象再Eden区中生成,当Eden区满了时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满了,此区存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到老年区,需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden和Survivor区复制过来的对象,而复制到年老区的只有从第一个Survivor过来的对象,而且,Survivor区总有一个是空的。

2.Tenured(年老代)

年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象;如果Tenured区(old区)也满了,就会触发Full GC回收整个堆内存。

3.Perm(持久代)

用于存放类的Class文件或静态文件,如Java类、方法等,垃圾回收是由FullGC触发的。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

在这里插入图片描述
举个例子

当程序中生成对象时,正常对象会在年青代中分配空间,如果是过大的对象也可能直接在年老代生成.
年青代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝到Survivor的from区,经过多次回收后如果from区也分配完毕,就会再发生内存回收然后将剩余的对象拷贝到to区,等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝到年老区

通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的所以以上对象的年青代和年老代都是指JVM的Heap空间,而持久代则是之前提到的MethodArea,不属于Heap。

关于JVM内存管理我们需要注意的几个地方
1.程序中的无用对象、中间对象置为null,可以加快内存回收
2.对象池技术如果生成的对象是可重用的对象,只是其中的属性不同,可以考虑采用对象池减少对象的生成
如果对象池中有空闲的对象就取出使用,没有则生成新的对象,提高对象复用率.
3.JVM性能调优通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄漏且上面两种方法都不能保证JVM内存回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果.

JVM的垃圾回收算法

Java中,垃圾回收的对象是Java堆和方法区(即永久区或持久区)

垃圾指的是在系统运行过程中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不释放,可能导致OOM. 后台专门有一个专门用于垃圾回收的线程 来进行监控,扫描,自动将一些无用的内存进行释放,这就是垃圾收集的一个基本思想,目的在于防止由程序员引入的人为的内容泄漏.

现代Java虚拟机常用的垃圾回收算法有三种:分别是标记-清除算法复制算法标记-整理算法


1.标记-清除算法

1.概念:
标记清除算法是现代垃圾回收算法的思想基础. 它将垃圾回收分为两个阶段:标记阶段和清除阶段

标记阶段:**首先,通过根节点,标记所有从根节点开始的可达对象.**未被标记的对象就是未被引用的垃圾对象

清除阶段:然后,清除所有未被标记的对象
在这里插入图片描述2.算法详解
原理:当中的可用有效内存空间被耗尽的时候,就暂停整个程序(也被称为 stop the world ),然后进行标记和清除两项工作,然后让程序恢复运行.

  • 标记:标记的过程其实就是,遍历所有的GC Roots ,然后将所有的GC Roots可达的对象标记为存活的对象
  • 清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除

疑问:为什么非要停止程序的运行呢?
答:不难理解,假设程序与GC线程一起运行,当对象A处于标记阶段,被标记为垃圾对象后,试想此时新new了一个对象B,且对象A可达B。但是由于此时A对象已经标记结束,B对象错过了标记阶段。因此当接下来清除阶段会被,新对象B会随着A被标记被清除掉,变为null,这样就乱套了。如此一来,要想正常清除垃圾资源,GC线程必须要暂停程序。

3.标记清除算法的缺点
首先,**它的缺点就是效率比较低(递归和全堆遍历)**暂停程序的时间比较长.

第二则是这种方式清理出来的空间内存不连续,这点不难理解,我们的死亡对象都是随机的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然就是乱七八糟,为了应付这一点,JVM不得不维持一个内存的空闲列表,这又是一种开销.而且在分配数组对象的时候,寻找连续的空间会不太好找.


复制算法(适用于年青代GC)

1.概念:内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收

  • 与标记清除算法相比,复制算法是一种相对高效的回收方法,且内存连续
  • 不适于用于存活对象较多的场合,如年老代(复制算法适合做新生代的GC)
    在这里插入图片描述

2.优点:实现简单,运行高效,内存连续.每次只要一动指针,就可以联系分配内存存放复制过来的对象

缺点:空间浪费,只用了一半的内存,所有,想要用复制算法,最起码对象的存活率要非常低才行,而且最重要的是要客服50%内存的浪费

针对这种缺点,这种算法比较合适,且已经用于年青代垃圾回收,新生代中的对象98%都是"朝生夕死"的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块比较大的Eden空间和两块比较小的Survivor空间,每次使用Eden和其中一块Survivor.当回收时,将Eden和Survivor中还存活着的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间.
HotSpot虚拟机默认Eden和Survivor的大小比例为8:1,也就是说,每次新生代中可用内存空间为整个新生代容量的(80+10%),只有10%被浪费.
当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代。整个过程如下图所示:
在这里插入图片描述

3 标记-整理算法(适用于年老代的GC)

1.引入
如果在对象存活率比较高时就要进行较多的复制操作,效率将会变低.更关键是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所有在年老代一般不能直接选这种算法.
2.概念
适用于存活对象较多的场合,如年老代.它在标记-清除算法的基础上做了一些优化.和标记清楚算法一样,
标记-整理算法也是需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端之后,清理边界外所有的空间
在这里插入图片描述

  • 标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
  • 整理:移动所有存活的对象,按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收因此第二个阶段才称为整理阶段(JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销)

优缺点
优点:标记/整理算法不仅可以弥补标记清除算法中,内存区域分散的缺点,也消除了复制算法中,内存减半的高额代价
缺点:就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址,从效率上来看,低于复制算法.

总结

三个算法都是基于根搜索算法去判断一个对象是否应该被回收,而支撑搜索算法可以正常工作的理论依据,就是语法中变量作用域的相关内容.因此,要想防止内存泄漏,最根本的方法就是掌握好变量作用域
在GC线程开启时,或者说GC过程开始时,它们都要暂停应用程序
它们的区别如下:

  1. 效率:复制算法>标记整理算法>标记清除算法
  2. 内存整齐度:复制算法=标记整理算法 > 标记清除算法
  3. 内存利用率:标记整理算法 = 标记清除算法 > 复制算法

可以看到标记/清除算法是比较落后的算法了,但是后两种算法却是在此基础上建立的。
时间与空间不可兼得。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值