本文作者:包子也沉默

JVM垃圾回收(上)

包子也沉默 3年前 (2019-10-23) ( 10-23 ) 859 0条评论
摘要: 。一旦某个对象的引用计数器为0,则说明该对象已经死亡,便可以被回收了。其具体实现为:如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器+1。如果一个指向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器-1。也就是说,我们需要截获所有的引用更新操作,并且相应地增减目标对象的引用计数器。看似很简单的实现,其实里面有不少缺陷:需要额外的空间来存储计数器。计数器的更新操作十分繁琐。最重要的

Java 中的垃圾回收,常常是由 JVM 帮我们做好的。虽然这节省了大家很多的学习的成本,提高了项目的执行效率,但是当项目变得越来越复杂,用户量越来越大时,还是需要我们懂得垃圾回收机制,这样也能进行更深一步的优化。

的Java线程之前我们说引用计数法会有循环引用的问题,可达性分析就不会了。举例来说,即便对象a和b相互引用,只要从GCRoots出发无法到达a或者b,那么可达性分析便会认为它们已经死亡。那可达性分析有

辨别对象存亡

垃圾回收( Garbage Collection,以下简称 GC ),从字面上理解,就是将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配。

年龄增加1,当年龄达到某个值时(默认15,通过设置参数-XX:MaxTenuringThreshold来设置),这些对象就会进入老年代。当然,对于一些较大的对象可以直接进入老年代,可以根据-XX:+P

在 JVM 中,垃圾就是指的死亡对象所占据的堆空间( GC 是发生在堆空间中),那么我们如果辨别一个对象是否死亡呢?JVM 使用的是引用计数法可达性分析

eThreshold设置大对象进入老年代的阈值。新生代采用复制算法:老年代使用标记-整理算法:垃圾收集器垃圾收集算法只是内存回收的理论方法,垃圾收集器才是内存回收的具体实现。Java虚拟机规范中对垃圾

引用计数法

引用计数法( Reference Counting),是为每个对象添加一个引用计数器,用来统计引用该对象的个数。一旦某个对象的引用计数器为0,则说明该对象已经死亡,便可以被回收了。

标记和重写标记任然需要”StopTheWorld”,但是速度很快,整个过程中耗时的操作在并发标记和并发清除阶段,在这个过程中,收集器线程都可以和用户线程一起工作。G1收集器G1

其具体实现为:

t),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。那么什么是GCRoots呢?我们可

如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器 +1。

数器的值为0时,对象就是不可能被使用的,可以对其进行垃圾回收。引用计数法的实现简单,效率也比较高,但是它很难解决对象之间循环引用的问题。可达性分析算法可达性分析算法,也称为根搜索算法,这个算法的基本思

如果一个指向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器 -1。

区域中,然后清理掉eden和之前使用的survivor区域,并将这些存活的对象年龄+1,以后对象在survivor中每熬过一次minorgc则年龄增加1,当年龄达到某个值时(默认15,通过设置参数-X

也就是说,我们需要截获所有的引用更新操作,并且相应地增减目标对象的引用计数器。

这类应用尤其重视服务的响应速度,希望系统停顿时间最短,CMS收集器非常符合这类应用的要求。当然,CMS收集器也有自己的缺点,它会占用更多的CPU资源,并和用户线程争抢,同时由于采用标记-清除算法,存在

看似很简单的实现,其实里面有不少缺陷:

下,a和b实际上已经死了。但由于它们的引用计数器皆不为0(因为相互引用,两者均为1),在引用计数法的计算中,这两个对象还活着。因此,这些循环引用对象所占据的空间将不可回收,从而造成了内存泄露。可达性分

  1. 需要额外的空间来存储计数器。
  2. 计数器的更新操作十分繁琐。
  3. 最重要的:无法处理循环引用对象。

针对第3点,举个例子特别说明一下:

言中,内存的分配和释放是手动的过程,在Java语言中内存的分配和回收是由垃圾收集器自动处理的。而在自动垃圾收集中如何确定那些内存需要被回收,通常来说第一步就是标记,利用引用计数,可达性分析等来标记那些

假设对象 a 与 b 相互引用,除此之外没有其他引用指向他们。在这种情况下,a 和 b 实际上已经死了。

算法。新生代几乎是所有JAVA对象出生的地方,JAVA对象申请的内存和存放都是在这个地方,JVM每次只会使用新生代中的eden和其中一块survivor来为对象服务,所以无论什么时候,都会有一块sur

但由于它们的引用计数器皆不为0(因为相互引用,两者均为1),在引用计数法的计算中,这两个对象还活着。因此,这些循环引用对象所占据的空间将不可回收,从而造成了内存泄露

收集器是一个并发收集器,CMS即ConcurrentMarkSwap,主要用于老年代,使用标记-清除算法,可以通过设置参数-XX:+UseConcMarkSweepGC来使用。CMS收集器可以并行的执

可达性分析

可达性分析( Reachability Analysis ),是目前 JVM 主要采取的判定对象死亡的方法。实质在于将一系列GC Roots作为初始的存活对象合集(live set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。

向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器-1。也就是说,我们需要截获所有的引用更新操作,并且相应地增减目标对象的引用计数器。看似很简单的实现,其实里面有不少缺陷:需要额外的空间来存储

那么什么是GC Roots呢?我们可以暂时理解为由堆外指向堆内的引用,一般而言,GC Roots 包括(但不限于)如下几种:

程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。自动垃圾收集自动垃圾收集是查看堆内存,识别正在使用那些对象,那些对象未被删除,删除未被使用对

  1. Java 方法栈桢中的局部变量
  2. 已加载类的静态变量
  3. JNI handles
  4. 已启动且未停止的 Java 线程

之前我们说引用计数法会有循环引用的问题,可达性分析就不会了。举例来说,即便对象 a 和 b 相互引用,只要从 GC Roots 出发无法到达 a 或者 b,那么可达性分析便会认为它们已经死亡。

圾收集算法。目前主流的JVM一般将堆内存分为新生代和老年代(大小比列为1:2),而新生代又被分为了eden、fromsurvivor、tosurvivor(大小比列为8:1:1)。在新生代中,每次垃圾

可达性分析有没有什么缺点呢?有的,在多线程环境下,其他线程可能会更新已经分析过的对象中的引用,从而造成误报(将引用设置为 null)或者漏报(将引用设置为未被访问过的对象)。

去,只有少量存活,那么就可以选用复制算法,只需付出少量对象的复制成本就可以完成收集。在老年代中,对象存活率高,就可以使用标记-清除,标记-整理算法。新生代几乎是所有JAVA对象出生的地方,JAVA对象

误报并没有什么伤害,JVM 至多损失了部分垃圾回收的机会。漏报则比较麻烦,因为垃圾回收器可能回收事实上仍被引用的对象内存。一旦从原引用访问已经被回收了的对象,则很有可能会直接导致 JVM 崩溃。

venge收集器的老年代版本,使用多线程和标记-整理算法,可以通过设置参数-XX:+UseParallelOldGC来使用。CMS收集器CMS收集器是一个并发收集器,CMS即ConcurrentMar

STW

既然可达性分析在多线程下有缺点,那 JVM 是如何解决的呢?答案便是 Stop-the-world(以下简称JWT),停止了其他非垃圾回收线程的工作直到完成垃圾回收。这也就造成了垃圾回收所谓的暂停时间(GC pause)。

引用计数器为0,则说明该对象已经死亡,便可以被回收了。其具体实现为:如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器+1。如果一个指向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器

那 SWT 是如何实现的呢?当 JVM 收到 SWT 请求后,它会等待所有的线程都到达安全点(Safe Point),才允许请求 SWT 的线程进行独占的工作。

内存自然就跟随着回收了。而Java堆不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配

那什么又叫安全点呢?安全点是 JVM 能找到一个稳定的执行状态,在这个执行状态下,JVM 的堆栈不会发生变化。

存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存分代收集根据对象的存活周期,将内存划分为几个区域,不同区域采用合适的垃圾收集算法。目前主流的JVM一般将堆内存分为新生代和老年代(

这么一来,垃圾回收器便能够“安全”地执行可达性分析,所有存活的对象也都可以成功被标记,那么之后就可以将死亡的对象进行垃圾回收了。

GCPauseMillis来控制最大垃圾收集停顿时间和-XX:GCTimeRatio来直接设置吞吐量的大小。SerialOld收集器SerialOld收集器是Serial收集器的在老年代使用的版本,它

总结

以上便是发现死亡对象的过程,这也为之后的垃圾回收进行铺垫,具体的垃圾回收过程,我会在下一篇文章中讲述,敬请期待。

),从字面上理解,就是将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配。在JVM中,垃圾就是指的死亡对象所占据的堆空间(GC是发生在堆空间中),那么我们如果辨别一个对象是否死亡呢?JVM

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

00.github.io/Java内存运行时区域中的程序计数器、虚拟机栈、本地方法栈随线程而生灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结

https://death00.github.io/

文章版权声明:除非注明,否则均为本站原创文章,转载或复制请以超链接形式并注明出处。
分享到:
赞 (0

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

发表评论

快捷回复:

评论列表 (有 0条评论, 859人围观) 参与讨论