什么是内存溢出,内存泄漏,判别对象是可以回收,垃圾回收方法,垃圾回收器

本文详细解析了Java中的内存溢出与内存泄漏概念,介绍了垃圾回收的基本原理,包括不同类型的引用、垃圾回收算法(如标记清除、复制算法、标记整理算法),以及各种垃圾回收器(如Serial、ParNew、ParallelScavenge、CMS和G1收集器)的特点和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当出现内存溢出和内存泄漏的时候,当垃圾回收成为制约系统达到更高并发量的瓶颈的时候要进行内存回收
1):内存溢出(out of memory):指的是程序在申请内存的时候,没有足够的内存空间可以分配,系统不能满足需求,出现了out of memory或者说申请了一个int的内存,但是存放了long才能存放的数,这就是内存溢出
2):内存泄露(memory leak):指的是程序申请了内存之后,无法释放掉已经申请到的内存空间,它始终占用着内存,越积越多,最后内存被占光,是指程序在申请内存之后,无法释放掉已经申请到的内存空间,它始终占用着内存,这样越积越多,最后内存被占光。内存泄露一般都是因为内存中有一块很大的对象,但是无法释放。内存泄露最终将导致内存溢出!
定位虚拟机的内存问题时候第一步要判读按是内存溢出还是内存泄漏,内存溢出跟踪堆栈的信息就行,内存泄漏一般是老年代中的大对象没有释放掉要通过各种方法找到老年带中大对象没有被释放的原因。https://www.cnblogs.com/haitaofeiyang/p/7771628.html
java中经常会出现内存泄漏和内存溢出的问题,java有自己的内存回收机制,但还是会出现内存泄漏的现象,因为java中对内存对象的访问是通过引用的方式,java中会维护一个引用变量,通过引用变量的值可以访问内存地址中的内存对象空间,GC线程会从引用变量开始追踪,从而判断哪些内存是正常使用的,GC线程不能追踪到某一块堆内存的时候,就认为这块内存不在使用了。当一个对象失去了所有的引用之后,GC将会被回收,如果对象的引用还存在,即使出现outofmemoryError该对象也不会被GC回收。java中出现内存泄漏的原因就是,对象明明已经不需要了,但是还保留着这块内存和它的引用。
程序计数器,栈,本地方法栈,随着线程而生随着线程而灭
堆和方法区不一样,堆和方法区中要进行GC回收,通过引用计数方法和根节点可达性算法判断是否要进行GC回收,引用计数方法的方法在对象中添加一个引用计数器,每当一个地方引用这个对象时候,引用计数器的值就加1,当应用失效的时候,计数器的值-1,任何时刻计算数值为0的对象就是不能再使用的,但是这种算法不能解决对象相互引用的情况,可达性算法中,通过一系列称为GCroots的对象作为起始点,向下搜索,搜索走过的路径称为引用链,当一个对象到GCroot对象没有任何引用的时候,就证明该对象是不可达的,被判定为不可达的对象不一定会称为可回收的对象,要证明一个对象是可回收的至少要进行两次标记,使用可达性分析之后发现对象没有与GCroots相连接的引用链,则该对象被第一次标记并进行一次筛选,筛选的条件是是否有必要执行该对象的finalize(),如果对象没有覆盖finalize()方法或者该方法已经被虚拟机执行过了,则均视为不必要执行该对象的finalize()方法,即该对象将会被回收,如果该对象中覆盖了finaliize()方法,并且该方法没有被执行过,则这个对象将会被放在一个F-queue队列中,之后会由虚拟机自动建立的优先级低的线程去执行,而不必要等待该线程执行结束。对队列中的对象进行第二次标记,如果对象在执行finalize()方法的时候关联上了GCroots的引用链,则在第二次标记的时候将会从即将回收的集合中移除,如果对象没有拯救自己,就只能被回收,对象只能拯救自己一次,因为finalize()方法只会执行一次
GCroot对象包括栈帧中本地变量表引用的对象,方法区中类的静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象
java中的引用分为四种,强引用,软引用,弱引用,虚引用
垃圾回收算法:标记清除算法,算法分为标记和清除两个阶段首先将所有要回收的对象进行标记,标记完成之后统一回收被标记的对象,这种方法简单直接,但是会产生大量的内存碎片,不利于后续连续内存的分配,这种方式回收效率也不高
在这里插入图片描述
复制算法就是标记复制清除算法,为了解决垃圾回收的效率和解决内存碎片问题,复制算法是将可用的内存分为两块,每次只用其中的一块,当这一块内存用完了之后就将还存活的对象复制到另一块内存上面,然后把已经使用过的内存空间一次性的清理掉,但是这种方式会浪费一半的内存,复制算法一般用于新生代的回收,因为新生代中百分之98的对象都是很快需要被回收的,复制算法采用1:1的内存分配是不合理的,因此,新生代的内存按照8:1的比例划分为eden区和两块较小的survivor区,每次使用eden区和其中的一块survivor区,每次会后的时候将eden区中还存活的对象复制到两一块survivor去上,最后清理掉eden区和刚才使用的survior对象,这样每次保证新生代百分之90的内存空间是可用的,当survivor空间不足的时候,将这些对象直接放进老年代空间中,通过分配担保机制
目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;新生代又被划分成Eden 空间、 From Survivor 和 To Survivor 三块区域。 我们把Eden : From Survivor : To Survivor 空间大小设成 8 : 1 : 1 ,对象总是在 Eden 区出生, From Survivor 保存当前的幸存对象, To Survivor 为空。一次 gc 发生后: 1)Eden 区活着的对象 + From Survivor 存储的对象被复制到 To Survivor ; 2) 清空 Eden 和 From Survivor ; 3) 颠倒 From Survivor 和 To Survivor 的逻辑关系: From 变 To , To 变 From 。可以看出,只有在 Eden 空间快满的时候才会触发 Minor GC 。而 Eden 空间占新生代的绝大部分,所以 Minor GC 的频率得以降低。当然,使用两个 Survivor 这种方式我们也付出了一定的代价,如 10% 的空间浪费、复制对象的开销等。
标记整理算法,如果在对象成活率较高的场景下要进行大量复制操作,效率低并且成活率高就需要额外的空间进行担保,老年代是不容易被回收的对象,对象的存活率高,采用复制算法不合适,采用标记整理算法,先将所有对象进行标记,标记完成之后将所有成活的对象移动到一段,直接清理掉边界之外的内存,
在这里插入图片描述
在这里插入图片描述
分代回收算法,根据对象的生命周期的不同将内存划分为几块,根据各块的特点采用最适当的收集算法。新生代中采用复制算法,老年代中采用标记清除算法或者是标记整理算法
https://www.cnblogs.com/xiaoxi/p/6486852.html

方法区就是永久代,方法区中需要回收废弃的常量和无用的类,判断废弃常量,以字面量回收为例如果一个字符串abc已经进入了常量池,但是当前系统没有任何一个String对象引用了叫做abc的字面量,在进行垃圾回收并且有必要的时候,abc就会被系统移除常量池,常量池中的其他类,接口,字段的符号引用也是类似的,判断是否是无用的类,要满足该类的所有的实例都已经被回收,java堆中不存在该类的任何实例,加载该类的classLoader已经被回收,该类对象的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类,满足这些条件中hi后,就可以进行垃圾回收,可以进行垃圾回收不等于被垃圾回收。
https://blog.youkuaiyun.com/aijiudu/article/details/72991993
垃圾回收器
Serial收集器:serial收集器是单线程的收集器,只会使用一个CPU或者一条线程完成垃圾收集工作,在进行垃圾收集的时候必须暂停其他所有线程的工作,直到收集结束为止,意味着在用户不可见的情况下要把正常工作的线程全部的停掉,这多很多应用是很难接受的,但是Serial收集器依然是虚拟机运行在Client模式下默认默认的新生代的收集器,一位它十分的简单高效采用的是复制算法,
在这里插入图片描述
parNew收集器
parNew收集器实际上是Serial收集器的多线程版本,parNew收集器在进行垃圾回收的时候使用的是多条线程进行垃圾回收,在进行垃圾回收的时候会暂停其他线程的工作,直到收集完成为止,意味着用户在不可见的情况下会把正常工作的线程全部停掉,parNew采用的垃圾回收的算法是复制算法,parNew收集器是虚拟机运行在Server模式下首选的首选的和默认的是不一样的的垃圾回收器,另外一个和性能无关的原因是,目前除了Serial收集器之外parNew收集器是唯一可以和CMS收集器配合使用的,CMS收集器是
是很有意义的垃圾回收器,可以让垃圾收集线程和用户线程基本上同时工作
。parNew收集器在单CPU的情况下运行的效果不如Serial,因为Serial是单线程的收集器在单cpu下不用进行上下文切换,甚至是两个cpu的情况下也不会比Seria收集器好很多,当cpu数量增多的时候,性能就体现了,默认情况下,parNew收集器的垃圾收集线程数目与CPU的数量一直,在cpu数量很多的情况下,使用-xx:ParallGCThreads参数可以限制垃圾回收的线程数
在这里插入图片描述
Parallel Scavenge收集器
parallel Scavenge收集器用与parNew收集器一样是多线程收集器采用的是复制算法,parallel Scavenge关注的是目标是达到一个控制的吞吐量,吞吐量指的是 cpu用于运行用户代码时间与CPU总的消耗时间的比值,即吞吐量指的是cpu运用运行用户代码的时间与cpu用于运行代码的时间加上垃圾收集器进行垃圾收集的时间。高吞吐量可以高效的利用cpu挖成运算任务,主要适合在后台运算而不需要太多交互的任务,因为与吞吐量密切,因此Parallell Scavenge收集器又称为吞吐量优先收集器,虚拟机提供了-XX:MaxGCPauseMillis和-XX:GCtimeRatio两个参数来精确控制最大垃圾停顿时间和吞吐量大小,参数可以精确的控制吞吐量的大小,GC的停顿时间的缩短是以牺牲吞吐量和新生代空间换取的,Paallel Scavenge收集器的-xx:+useAdaptiveSizePolicy参数,这是一个开关参数,把这个参数打开之后,就不需要手动指定新生代的大小 eden区和survivor区等传参数了,虚拟机会根据当前系统的运行情况,动态的调整这些参数,提供最大的吞吐量,对于对垃圾收集器不太了解,在进行优化的时候遇到困难的时候,使用Parallel Scavenge收集器使用useAdaptivePolicy自适应调节策略把调优任务交给虚拟机完成是一个不错的选择

serial Old收集器,serialold收集器是serial收集器的老年版本,是单线程的收集器,使用的是标记整理算法,这个收集器也是在Client模式下虚拟机使用的
parallel old 是parallel scavenge收集器的老年版本,是多线程的收集器,采用的是标记整理算法,注重的是吞吐量,在注重吞吐量和cpu敏感的场合可以优先考虑parallel scavenge 和parallel old组合
CMS收集器是以获取最短垃圾回收停顿时间为目标的收集器,使用的是标记清除算法,收集的过程分为4步,1.初始标记,标记GCroots能够直接关联到的对象,时间很短,2.并发标记,进行可达性分析的时间,时间很长,3.重新标记,修正并发标记期间用户程序继续运行导致变动的对象标记记录,4,并发清除,其中并发标记(可达性算法和并发清除(内存回收的时候 时间较长)cms收集器对CPU资源敏感,可能会导致应用程序变慢,吞吐率下降。2. 无法处理浮动垃圾,因为在并发清理阶段用户线程还在运行,可能会产生新的垃圾,而在此次收集中无法收集他们,只能留到下次收集,这部分垃圾为浮动垃圾,同时,由于用户线程并发执行,所以需要预留一部分老年代空间提供并发收集时程序运行使用。3. 由于采用的标记 - 清除算法,会产生大量的内存碎片,不利于大对象的分配,可能会提前触发一次Full GC。虚拟机提供了-XX:+UseCMSCompactAtFullCollection参数来进行碎片的合并整理过程,这样会使得停顿时间变长,虚拟机还提供了一个参数配置,-XX:+CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后,接着来一次带压缩的GC。


G1收集器没有讲清楚:
G1收集器,G1收集器可以并行和并发,使用多个CPU来缩短Stop the world 停顿时间并且与用户线程并发执行,使用标记整理算法,没有内存碎片产生,能够分代收集,独立的管理整个堆内存,但是能够采用不同的方式去处理新创建的对象和已经存活了很长时间经过了多次GC的旧对象。能够预测停顿,G1收集器
在这里插入图片描述

https://blog.youkuaiyun.com/java2000_wl/article/details/8030172

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值