System.gc的理解
在默认情况下,通过system.gc()或者runtime.getRuntime().gc()的调用,会显示触发full gc,同时,对老年代和新生代进行回收,尝试释放被丢弃对象所占用的内存。
system.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用。
jvm实现者可以通过system.gc()调用来决定jvm的gc行为,而一般情况下,垃圾回收应该是自动进行的,无需手动触发,否则就太过于麻烦。在一些特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用system.gc。
1内存溢出相对于内存泄露来说,尽管,尽管更容易被理解,但是同样的内存溢出也是引发程序崩溃的罪魁祸首之一。2由于gc一直在发展,所有一般情况下,除非应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现oom的情况。3在大多数情况下,gc会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的full gc操作,这时候会回收大量的内存供应用程序继续使用。4JAVA doc中对out of memory error的解释是没有空闲内存,并且垃圾回收器也无法提供更多内存。
首先说没有空闲内存的情况,说明JAVA虚拟机的堆内存不够。原因有二,1 JAVA虚拟机的堆内存设置不够,比如可能存在内存泄露问题,也有可能是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显示指定jvm堆大小或者指定数值偏小,我们可以通过参数-Xms,-Xmx来调整。二 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集,存在被引用。对于老版本的oracle gdk,因为永久代的大小是有限的,并且jvm对永久代垃圾收集,如常量池回收、卸载不再需要的类型,非常不积极,所以当我们不断添加新类型的时候,永久代出现out of memory error也非常多见,尤其在运行时存在大量动态类型生成的场合。类似intern字符串缓存占用大量空间,也会导致oom问题,对应的异常信息会标记出来和永久代相关。
这里边隐含着一层意思,是在抛出out of memory error之前,通常垃圾收集器会被触发,尽其可能去清理出空间,1例如在引用机制分析中,涉及到gvm会去尝试回收软引用指向的对象等。2在java.nio.bits.reservememory()方法中,可以清楚看到,system.gc()会被调用,清理空间
当然也不是在任何情况下,垃圾收集器都会被触发的。比如我们去分配一个超大对象,类似一个超大数组,超过堆的最大值jvm可以判断出垃圾收集并不能解决这个问题,所以直接抛出out of memory error。
也称作存储泄露。严格来说,只有对象不会再被程序用到了,但是gc又不能回收他们的情况,才叫内存泄露。
但实际情况很多时候一些不太好的实践,会导致对象的生命周期变得很长,甚至导致oom也可以叫做宽泛意义上的内存泄露。
尽管内存泄露并不会立刻引起程序崩溃,,但是一旦发生内存泄露程序中的可用内存就会逐步蚕食,直至耗尽所有内存,最终出现out of memory异常,导致程序崩溃。
注意这里的存储空间,并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。
1单例模式,单例的生命周期和应用程序是一样长的,所以单例程序中如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄露的产生。2一些提供close的资源未关闭导致内存泄露,数据库连接datasources.getConnection,网络连接socket和IO连接必须手动close,否则不能被回收。
Stop the world简称stw,指的是gc事件发生过程中会产生应用程序的停顿,停顿产生使整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉。这个停顿称为stw。
可达性分析算法中枚举根节点gc roots,会导致所有JAVA执行线程停顿。1分析工作必须在一个确保一致性的快照中进行;2一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上。3如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证。
被stw中断的应用程序会在应用程序完成gc之后恢复,频繁中断,会让用户感觉像是网速不快,造成电影卡带一样,所以我们需要减少stw的发生。
1Stw事件和采用哪款gc无关所有,所有的gc都有这个事情。2哪怕是g1也不能完全避免stop the world情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能的缩短了暂停时间。3Stw是jvm在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉。4开发中不要用system.gc会导致stop the word发生。
并发:1在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理器上运行。2并发不是真正意义上的同时进行,只是CPU把一个时间段划分成几个时间片段,然后在这几个时间区间之间来回切换,由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时进行。
并行:当系统由一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,我们称之为并行。2其实并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核心也可以并行;3适合科学计算、后天处理等弱交互场景。
并发与并行
并发,指的是多个事情在同一时间段内同时发生了。并行,指的是多个事情在同一个时间点上发生了。
并发的多个任务之间是互相抢占资源的,并行的多个任务之间是不互相抢占资源的。
只有在多CPU或者一个CPU多核的情况下才会发生病情,否则看似同时发生的事情其实都是并发执行的。
并发与并行在讨论垃圾收集器的上下文语境中,他们可以解释如下:
并行指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。如parnew、parallel scanvenge, parallel old;串行,相较于并行的概念,单线程执行,如果内存不够,则程序暂停,启动jvm垃圾回收器进行垃圾回收,回收完再启动程序的线程。
并发指用户线程与垃圾收集线程同时执行,但不一定是并行的,可能会交替进行,垃圾回收线程在执行时不会停顿用户程序的运行,用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上如cms,g1。
安全点
程序执行时并非在所有地方都能停顿下来开始gc。只有在特定的位置才能停顿下来开始gc,这些位置称为安全点safe point。
Safe point的选择很重要,如果太少,可能导致gc等待的时间太长,如果太频繁,可能导致运行时的性能问题,大部分指令的执行时间都非常短暂,通常会根据是否具有让程序长时间执行的特征为标准。比如选择一些执行时间较长的指令作为save point,如方法调用,循环跳转和异常跳转等。
通常设置在方法调用、循环跳转和异常跳转等指令之前
如何在gc发生时检查所有线程都跑到最近的安全点停顿下来呢?
一抢先式中段,目前没有虚拟机采用。首先,中断所有线程,如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
主动式中断,设置一个中断标志,各个线程运行到safe point的时候,主动轮询这个标志,如果中断标志为真,则将自己进行中断挂。
Safe point机制保证了程序执行时,在不太长的时间内就会遇到可进入gc的safe point。但是程序不执行的时候呢,例如线程处于sleep状态或block的状态,这时候,线程无法响应jvm的中断请求,走到安全点去中断挂起,jvm也不太可能等待线程被唤醒。对于这种情况,就需要安全区域来解决。
安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始gc都是安全的,我们也可以把safe region看作是被扩展了的safe point。
实际执行时,1当线程运行到safe region代码时,首先标识已经进入了safe region,如果这段时间内发生gc, jvm会忽略标识为safe region状态的线程。2当线程即将离开safe region时,会检查jvm是否已经完成gc,如果完成了,则继续进行,否则线程必须等待,直到收到可以安全离开safe region的信号为止。
我们希望能描述这样一类对象,当内存空间还足够,始终能保留在内存中。如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。
既偏门又非常高频的面试题:强引用、软引用、弱引用、虚引用,有什么区别?具体使用场景是什么? jdk1.2版本之后,对引用概念进行扩充,这4种引用强度逐渐减弱。除强引用外,其他3种引用均可以在java.lang.ref包找到它们的身影。
在JAVA程序中,最常见的引用类型是强引用,普通系统99%以上都是强引用,也就是我们最常见的普通对象引用,也是默认的引用类型。
当在JAVA语言中使用new操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用。
强引用的对象是可触及的垃圾收集器,就永远不会回收掉被引用的对象。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式的将相应强引用赋值为null,则可以当做垃圾被收集了。当然,具体回收时机还是要看垃圾收集策略。
相对的,软引用、弱引用和虚引用的对象都是软可触及、弱可触及和虚可触及的,在一定条件下都是可以被回收的,所以强引用是造成JAVA内存泄露的主要原因之一。
通过new创建的变量,强引用,再初始化变量指向刚才创建的变量,也是强引用。
本例中的两个引用都是强引用,强引用具备以下特点,一强引用可以直接访问目标对象,二强引用所指向的对象在任何时候都不会被系统回收。虚拟机宁愿抛出oom异常,也不会回收强引用所指向对象。三强引用可能导致内存泄露。
软引用是用来描述一些还有用,但非必需的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
软引用通常用来实现内存敏感的缓存,比如高速缓存就有用到软引用,如果还有空闲内存就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时不会耗尽内存。
垃圾回收器在某个时刻决定回收软可达的对象的时候,会清理软引用,并可选的把引用存放到一个引用队列reference queue。
类似弱引用,只不过JAVA虚拟机会尽量让软引用的存活时间长一些,迫不得已才清理。
通过new softreference<obj>(obj)实现
弱引用,也是用来描述那些非必需对象,只被弱引用关联的对象,只能生存到下一次垃圾收集发生为止。在系统gc时,只要发现弱引用,不管系统堆空间使用是否充足,都会回收掉只被弱引用关联的对象。
但是,由于垃圾回收器的线程通常优先级很低,因此并不一定能很快的发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长时间。
弱引用和软引用一样,在构造弱引用时,也可以指定一个引用队列。当弱引用对象被回收时,就会加入指定的引用队列。通过这个队列,可以跟踪对象的回收情况。
软引用,弱引用都非常适合来保存那些可有可无的缓存数据,如果这么做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出,而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。
通过new weakreference<obj>(obj)实现
弱引用对象与软引用对象的最大不同,在gc进行回收时,需要通过算法检查是否回收软引用对象。而对于弱引用对象,gc总是进行回收。弱引用对象更容易,更快被gc回收。
也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个,一个对象是否有虚引用的存在,完全不会决定对象的生命周期。如果一个对象仅持有虚引用,那么他和没有引用几乎是一样的,随时都可能被垃圾回收器回收。
他不能单独使用,也无法通过虚引用来获取被引用的对象。当试图通过虚引用的get方法取得对象时,总是null。
当一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程,比如能在这个对象被收集器回收时收到一个系统通知。
虚引用,必须和引用队列一起使用,虚引用在创建时必须提供一个引用队列作为参数。当垃圾回收器准备回收一个对象时,如果发现他还有需引用,就会在回收对象后,将这个虚引用,加入引用队列,以通知应用程序对象的回收情况
由于虚引用可以跟踪对象的回收时间,因此也可以将一些资源释放操作放置在虚引用中,执行和记录。
通过new phantomreference<obj>(obj)实现
它用于实现对象的finalize方法,也可以称为终结器引用。
无需手动编码,其内部配合引用队列使用。
在gc时终结器引用入队,由finalizer线程通过终结器引用找到被引用对象,并调用它的finalize方法。第二次gc时才能回收被引用对象。
垃圾收集器概述
1垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商不,不同版本的gvm来实现。2由于jdk的版本处于高速迭代过程中,因此JAVA发展至今已经衍生了众多的gc版本。3从不同角度分析,垃圾收集器可以将gc分为不同的类型。
垃圾收集器分类
按线程数分可以分为串行垃圾收集器和并行垃圾收集器。
串行回收指的是在同一时间段内,只允许有一个CPU用于执行垃圾回收操作。此时,工作线程被暂停,直至垃圾收集工作结束。1诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器和并发收集器,所以串行回收默认被应用在客户端的client模式下的jvm中。2在并发能力比较强的CPU上并行回收器产生的停顿时间要短于串行回收器。
和串行回收相反,并行收集可以运行在多个CPU同时执行垃圾回收,因此提升了应用的吞吐量。不过,并行回收仍然与串行回收一样,采用独占式使用了stop the word机制。
按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器。
并发式垃圾回收器与应用程序线程交替工作已尽可能减少应用程序的停顿时间。独占式垃圾回收器stop the word 一旦运行,就停止应用程序中所有用户线程,直到垃圾回收过程完全结束。
按碎片处理方式,可分为压缩式垃圾回收器和非压缩式垃圾回收器。压缩式垃圾回收器会在回收完成后对存活对象进行压缩整理,消除回收后的碎片。非压缩式的垃圾回收器不进行这步操作。
按工作的内存区间,又可分为年轻代垃圾回收期和老年代垃圾回收器。
评估gc的性能指标
吞吐量:运行用户代码的时间占总运行时间的比例,总运行时间等于程序的运行时间加内存回收的时间。
垃圾收集开销:吞吐量的补数,垃圾收集所用的时间与总运行时间的比例。
暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
收集频率:相对于应用程序的执行,收集操作发生的频率。
内存占用:JAVA堆区所占的内存大小。
快速:一个对象从诞生到被回收所经历的时间
暂停时间\内存占用\吞吐量构成不可能三角。
这三项,暂停时间的重要性日益凸显,因为随着硬件发展,内存占用多些,越来越能容忍,硬件性能的提升也有助于降低收集器运行时对应用程序的影响,即提高了吞吐量。而内存的扩大对延迟反而带来负面效果,主要抓吞吐量和暂停时间。
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量等于运行用户代码时间/(运行用户代码时间加垃圾收集时间)。
这种情况下,应用程序能容忍较高的暂停时间,因此高吞吐量的应用程序有更长的时间基准,快速响应应是不必考虑。
吞吐量优先,意味着在单位时间内stw的时间最短。
暂停时间是指一个时间段内应用程序线程暂停,让gc线程执行的状态暂停,时间优先意味着尽可能让单次stw的时间最短。
高吞吐量较好,因为这会让应用程序的最终用户感觉只有应用程序线程在做生产性工作,直觉上吞吐量越高,程序运行越快。
低暂停时间较好,因为从最终用户的角度来看,不管是gc还是其他原因,导致一个应用被挂起始终是不好的,这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停都可能打断终端用户体验。因此具有低的较大,暂停时间是非常重要的,特别是对于一个交互式应用程序。
不幸的是,高吞吐量和低暂停时间是一对互相竞争的目标,因为如果选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致gc需要更长的暂停时间来执行内存回收。
相反的,如果选择以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,也只能频繁的执行内存回收,但这又引起了年轻代内存的缩减和导致程序吞吐量的下降。
在设计gc算法时,我们必须确定我们的目标,一个gc算法只可能针对两个目标之一,即只专注于较大吞吐量或最小暂停时间,或尝试找到一个二者的折中。现在标准:在最大吞吐量优先的情况下降低停顿时间。
垃圾收集机制是JAVA的招牌能力,极大的提高了开发效率,这当然也是面试热点。JAVA常见的垃圾收集器有哪些?
-xx:+printcommandlineflags 打印默认的垃圾收集器
JDK 6-8、JDK 12和JDK 17的默认垃圾收集器有所不同,具体如下:
**JDK 6-7**
CMS(Concurrent Mark Sweep)收集器是 JDK 6 和 JDK 7 版本中的默认收集器之一,但不是唯一默认收集器。在这两个版本中,CMS 收集器主要用于老年代的垃圾回收,新生代默认还是使用 Parallel Scavenge 收集器。
- **JDK 8**
- **默认垃圾收集器**:新生代采用Parallel Scavenge收集器,老年代采用Parallel Old收集器。这两个收集器都侧重于吞吐量,它们的组合能高效利用CPU资源,适合在后台处理大量数据的应用场景,能在较短时间内完成垃圾回收,让应用程序有更多时间用于处理业务逻辑。
- **JDK 12**
- **默认垃圾收集器**:G1(Garbage-First)收集器。G1收集器将堆内存划分为多个大小相等的Region,通过标记-压缩算法,优先回收垃圾最多的Region,能在实现高吞吐量的同时,尽可能降低停顿时间,适用于堆内存较大、对停顿时间有一定要求的应用。
- **JDK 17**
- **默认垃圾收集器**:ZGC(Z Garbage Collector)。ZGC是一种可扩展的低延迟垃圾收集器,它采用了染色指针和读屏障等技术,能够在处理大内存时实现极低的停顿时间,并且随着堆内存的增大,停顿时间也不会明显增加,非常适合大规模内存的应用场景,如大型数据中心、云计算等环境中的应用程序。
Parallel Scavenge收集器-吞度量优先
CMS收集器 低延迟
G1 收集器:它将堆内存划分为多个大小相等的独立区域(Region),可以独立管理这些 Region,支持并发和并行收集。G1 收集器在后台维护一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region 进行回收,从而尽量减少停顿时间,同时也能兼顾内存回收的效率,在堆内存较大时能表现出较好的性能,对于垃圾回收的停顿时间有更好的控制,能满足很多应用场景对于低延迟的要求
是一款面向服务端应用的拦截收集器,主要针对配备多核CPU及大容量内存的机器。以极高概率满足gc停顿时间的同时,还兼具高吞吐量的性能特征。
从经验上来说,在小内存应用上CMS的表现大概率会优于记忆,而记忆在大内存应用上则发挥其优势平衡点。在6到8g之间。
参考资料
康师傅jvm
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。