从Heap Usage看,老年代(concurrent mark-sweep generation)的内存使用了99%以上,相较而言,新生代还有较多剩余;
JVM参数配置中的这一条,-XX:MaxTenuringThreshold=0,使新生代Eden区域的Java对象不经过Survivor区域,而直接晋升到老年代。这增加了老年代的垃圾回收负担,而且老年代开启了碎片整理,更加耗时;
请尝试将-XX:MaxTenuringThreshold参数调大一些,让对象晚一些进入老年代;
另外,请试一下增大Java堆内存的分配量,看是否能解决问题。
以上只是个人猜测,不知能否帮上忙。从JVM的GC日志中,也许能进一步发现问题。
Java中的垃圾回收器几乎是面试中的必考点,无论是面试初级,中级还是高级,总免不了要问一问垃圾回收器的一些知识点。不管在实际开发中你使用程度怎么样,为了面试不被压价,还是非常有必要对它做一个较深入的理解。
本篇对JVM中常用的几种垃圾回收器的主要特点,使用场景及优化建议做一个简单介绍,希望起到抛砖引玉的效果,对你入门有所帮助。
新生代回收器
老年代回收器
新生代和老年代回收器
Serial收集器是最基本、发展 历史 最悠久的收集器。JDK131前是HotSpot新生代收集的唯一选择。
运行示意图
有如下特点:
简单高效,由于采用的是单线程的方法,因此与其他类型的收集器相比,对单个cpu来说没有了上下文之间的的切换,效率比较高。
会在用户不知道的情况下停止所有工作线程。
在用户的桌面应用场景中,可用内存一般不大,可以在较短时间内完成垃圾收集,只要不频繁发生,这是可以接受的
对于限定单个CPU的环境来说,Serial收集器没有线程切换开销,可以获得最高的单线程收集效率
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余均和Serial 收集器一致。
运行示意图
多线程版本的Serial,可以更加有效地利用系统资源
同Serial,会在用户不知道的情况下停止所有工作线程
Server模式下使用,亮点是除Serial外,目前只有它能与CMS收集器配合工作,是一个非常重要的垃圾回收器。
运行示意图
有如下特点:
追求高吞吐量,高效利用CPU,使吞吐量优先,且能进行精确控制。
根据相关特性,我们很容易想到它的使用场景,即:当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,程序主要在后台进行计算,而不需要与用户进行太多交互等就特别适合ParNew收集器。
Serial Old是Serial收集器的老年代版本,同样是一个单线程收集器,使用标记-整理算法。
有如下特点:
优劣势基本和Serial无异,它是和Serial收集器配合使用的老年代收集器。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。采用的算法是“标记-清除”,运作过程分为四个步骤:
运行示意图
有如下特点:
如常见WEB、B/S系统的服务器上的应用。
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法,可以充分利用多核CPU的计算能力。
有如下特点:
优劣势参考Parallel Scavenge收集器。
这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge(新生代)加Parallel Old(老年代)收集器的"给力"应用组合;
G1(Garbage-First)是JDK7-u4才推出商用的收集器
有如下特点:
G1收集器是当今收集器技术发展的最前沿成果。
G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。
按照《深入理解Java虚拟机》作者的说法,CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。
个人以为G1已经基本全面压制cms、parallel等回收器,缺点见上面的劣势。但如果不是追求极致的性能,基本可以无脑G1
基本就介绍这些了,垃圾回收器基本不变的知识点多,学会(理解)可以应付N年的相关知识的面试,又是高频面试考点,各位同学还是值得在这块下点功夫的。文中有任何不足,错误欢迎指出,共同进步!
V8的垃圾回收策略基于分代回收机制,该机制又基于 世代假说 。该假说有两个特点:基于这个理论,现代垃圾回收算法根据对象的存活时间将内存进行了分代,并对不同分代的内存采用不同的高效算法进行垃圾回收。
在V8中,将内存分为了新生代(new space)和老生代(old space)。它们特点如下:
V8堆的空间等于新生代空间加上老生代空间。我们可以通过 --max-old-space-size 命令设置老生代空间的最大值, --max-new-space-size 命令设置新生代空间的最大值。老生代与新生代的空间大小在程序初始化时设置,一旦生效则不能动态改变。
默认设置下,64位系统的老生代大小为1400M,32位系统为700M。
对于新生代,它由两个 reserved_semispace_size 组成。每个 reserved_semispace_size 的大小在不同位数的机器上大小不同。默认设置下,在64位与32位的系统下分别为16MB和8MB。我们将新生代、老生代、 reserved_semispace_size 空间大小总结如下表。
在介绍垃圾回收算法之前,我们先了解一下「全停顿」。垃圾回收算法在执行前,需要将应用逻辑暂停,执行完垃圾回收后再执行应用逻辑,这种行为称为 「全停顿」(Stop The World)。例如,如果一次GC需要50ms,应用逻辑就会暂停50ms。
全停顿的目的,是为了解决应用逻辑与垃圾回收器看到的情况不一致的问题。举个例子,在自助餐厅吃饭,高高兴兴地取完食物回来时,结果发现自己餐具被服务员收走了。这里,服务员好比垃圾回收器,餐具就像是分配的对象,我们就是应用逻辑。在我们看来,只是将餐具临时放在桌上,但是服务员看来觉得你已经不需要使用了,因此就收走了。你与服务员对于同一个事物看到的情况是不一致,导致服务员做了与我们不期望的事情。因此,为避免应用逻辑与垃圾回收器看到的情况不一致,垃圾回收算法在执行时,需要停止应用逻辑。
新生代中的对象主要通过 Scavenge 算法进行垃圾回收。Scavenge 的具体实现,主要采用了Cheney算法。
Cheney算法采用复制的方式进行垃圾回收。它将堆内存一分为二,每一部分空间称为 semispace。这两个空间,只有一个空间处于使用中,另一个则处于闲置。使用中的 semispace 称为 「From 空间」,闲置的 semispace 称为 「To 空间」。
过程如下:
对象晋升的条件有两个:
1)对象是否经历过Scavenge回收。对象从 From 空间复制 To 空间时,会检查对象的内存地址来判断对象是否已经经过一次Scavenge回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。
2)To 空间的内存使用占比是否超过限制。当对象从From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置为25%的比例的原因是,当完成 Scavenge 回收后,To 空间将翻转成From 空间,继续进行对象内存的分配。若占比过大,将影响后续内存分配。
对象晋升到老生代后,将接受新的垃圾回收算法处理。下图为Scavenge算法中,对象晋升流程图。
Scavenge 算法的缺点是,它的算法机制决定了只能利用一半的内存空间。但是新生代中的对象生存周期短、存活对象少,进行对象复制的成本不是很高,因而非常适合这种场景。
老生代中的对象有两个特点,第一是存活对象多,第二个存活时间长。若在老生代中使用 Scavenge 算法进行垃圾回收,将会导致复制存活对象的效率不高,且还会浪费一半的空间。因而,V8在老生代采用Mark-Sweep 和 Mark-Compact 算法进行垃圾回收。
Mark-Sweep,是标记清除的意思。它主要分为标记和清除两个阶段。
与 Scavenge 算法不同,Mark-Sweep 不会对内存一分为二,因此不会浪费空间。但是,经历过一次 Mark-Sweep 之后,内存的空间将会变得不连续,这样会对后续内存分配造成问题。比如,当需要分配一个比较大的对象时,没有任何一个碎片内支持分配,这将提前触发一次垃圾回收,尽管这次垃圾回收是没有必要的。
为了解决内存碎片的问题,提高对内存的利用,引入了 Mark-Compact (标记整理)算法。Mark-Compact 是在 Mark-Sweep 算法上进行了改进,标记阶段与Mark-Sweep相同,但是对未标记的对象处理方式不同。与Mark-Sweep是对未标记的对象立即进行回收,Mark-Compact则是将存活的对象移动到一边,然后再清理端边界外的内存。
由于Mark-Compact需要移动对象,所以执行速度上,比Mark-Sweep要慢。所以,V8主要使用Mark-Sweep算法,然后在当空间内存分配不足时,采用Mark-Compact算法。
在新生代中,由于存活对象少,垃圾回收效率高,全停顿时间短,造成的影响小。但是老生代中,存活对象多,垃圾回收时间长,全停顿造成的影响大。为了减少全停顿的时间,V8对标记进行了优化,将一次停顿进行的标记过程,分成了很多小步。每执行完一小步就让应用逻辑执行一会儿,这样交替多次后完成标记。如下图所示:
长时间的GC,会导致应用暂停和无响应,将会导致糟糕的用户体验。从2011年起,v8就将「全暂停」标记换成了增量标记。改进后的标记方式,最大停顿时间减少到原来的1/6。
垃圾回收的原理较为复杂,在理解上需要花费一些功夫。了解GC原理,有助于我们对NodeJS项目进行性能瓶颈定位与调优。文章所描述的算法为V8中使用的基础算法,现代V8引擎对垃圾回收进行了很多改进,比如,在Chrome 64和Nodejs v10中V8启用了「并行标记」技术,将标记时间缩短了60%~70%。还有「Parallel Scavenger」技术,它将新生代的垃圾回收时间缩短了20%~50%。
垃圾回收是影响服务性能的因素之一,为了提高服务性能,应尽量减少垃圾回收的次数。
V8堆内存最大值在64位系统上为1464MB,在32位系统上为732MB。计算公式如下:作者:朱克锋1:垃圾回收机制由JVM完全负责,编写者在抛弃对象时不必关系空间回收问题2:JVM的垃圾回收机制对堆空间做实时监测,当发现某对象的引用计数为0时,就将该对象列入待回收系类中并不是马上予以销毁3:某个对象被认定为没有必要存在了,那么它所占用的内存就可以被释放,被回收的内存可以用于后续的再分配,并不是对象被抛弃后就立即被回收,垃圾回收器通常只在有对象要回收且系统需要回收时才运行,因此用户无法知道垃圾回收发生的精确时间4:system,gc()也仅仅是一个回收请求,JVM接受到这个消息后并不是立即做垃圾回收,而只是对几个垃圾回收算法做加权使垃圾回收 *** 作容易发生或提前发生5:当对象即将被销毁时,有时需要做一些善后工作,可以把这些 *** 作写在finalize()方法里(终止器)注:到程序接受时,并非所有收尾模块都会得到调用当指向某个对象的最后一个引用被删除,那么该对象就可以被删除:在对象的无用时可以回收Java的垃圾回收并不能保证内存的耗尽,其只是一个低优先级的后台线程且跟踪可达或者不可达的对象A:当JVM的拦截器调用一个合适对象的finalize()方法时,它会忽略任何由finalize()方法抛出的异常,其它情况下finalize()方法中的异常处理同普遍方法的出来是一样的B:Object对象有一个finalize()方法,由于所有的参数都是从Object类继承而来,因此所有对象有一个finalize()方法C:类可以覆盖finalize()方法,而且和普通的方法覆盖一样,不能降低finalize()方法的访问权限,调用finalize()方法本身不会破坏对象1垃圾回收目的:Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。
ps:内存泄露是指该内存空间使用完毕之后未回收,在不涉及复杂数据结构的一般情况下,Java 的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有时也将其称为“对象游离”。
2
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。
Scavenge GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
1年老代(Tenured)被写满
2持久代(Perm)被写满
3Systemgc()被显示调用
4上一次GC之后Heap的各域分配策略动态变化
内存释放:
垃圾收集机制的原理:
1 当变量进入环境时,将其标记为“进入环境”,当变量离开环境时,将其标记为“离开环境”(常用)
2 某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量
3 到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同
那么什么情况会引起内存泄漏呢?
文件服务器上的文件数据可以无限增大,但也可以删除。在文件服务器上存储文件数据可以节省计算机存储空间,同时,由于文件服务器可以提供多用户访问,因此可以很容易地共享和管理资源。不同用户之间可以安全地共享文件,并且可以实现文件备份。对于文件服务器上的文件数据,可以通过垃圾回收机制来限制其无限增长,以保持服务器的正常运行。此外,用户可以通过文件服务器提供的控制台管理工具来检查并删除不需要的文件。通过定期清理文件服务器上的文件数据,可以提高文件服务器的效率和性能,同时避免文件服务器上数据无限增长所带来的问题。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)