详细的图又需要的,评论区留言,后面发给你
JVM事件大概回顾一下,主要就办了两件事呗:(当然细枝末节需要大家自己扣扣)
- 小胡和敏小言去民政&局领证(类加载器和双亲委派机制,玩转~类加载器和双亲委派机制)
- 一个打手和一堆打手地盘的故事(java内存区域或者运行时数据区域的故事,玩转~java内存区域或者运行时数据区域)
然后呢,最后剩下了几个主要点:
- 打手们(线程们)私有的地盘他们自己可以打扫好,不用专人来帮忙
- 打手们共享的区域中有俩地需要专人来帮们善后,打扫。
- 打手们互相打斗完的舞台(堆,人称堆子哥),上面有很多脏牙套、xue渍、汗水......,需要专人来帮们善后,打扫。
- 民政&局处理完给类似小胡和敏小言类似的办结婚证事件之后,会在方法区(人称区子哥)有废弃的纸张、图片等材料废材,所以也需要专人来帮们善后,打扫。
咱们上面所谓的来打扫的专人叫做GC垃圾回收器,是一个保洁集团。人家的公司标语倒也贴切“我们的服务宗旨是,就是让堆子哥和区子哥,永远~ 靓~白 ~白,无(垃)圾可施(工)”
好啦,开始施工呗,远方传来保洁施工队的阵阵施工声~ 一个打手坐在拳台角上边擦汗边斜眼问道,你们这可别把我有用的东西给我错当垃圾给清了,到底有没有谱呀。(打手心里念叨,要不是我要陪我媳妇去叠烤肉,我早都自己来了)。 保洁队长说,大锅呀,nen(你)瞧瞧nen(你)说的这是啥话,我们是专业的哦。接着翘起指头说到,我们有一套专门的标准,里面有好几个方法来判断出您们客户哪些是垃圾(可回收)哪些不是垃圾(不能回收),有时候也说对象存活着就不是垃圾,没存活的对象就是垃圾。
被引用的对象就一定能存活吗?
不一定,看 Reference 类型,弱引用在 GC 时会被回收,软引用在内存不足的时候,即 OOM 前会被回收,但如果没有在 Reference Chain 中的对象就一定会被回收。
打手:nen倒是说呀。
保洁队长:额(我)们有好多方法呢。bai急呀,先给你说**堆子哥**要得不呀?
打手:要得要得。
- 引用计数法:(给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,引用计数器就+1,引用失效时,引用计数器就-1;当引用计数器为0的时候,就说明这个对象没有被引用,也就是垃圾对象,等待回收)
- nen知道堆中的每个对象吧,额(我)们会 给每个对象的引用贴一张小纸条,就像这样,呸,再用口水贴上去,然后我们有专门的仪器来记录每个对象贴的纸条数。
- 缺点:如图中所示,无法解决循环引用的问题,当A引用B,B也引用A的时候,此时AB对象的引用都不为0,此时也就无法垃圾回收,所以
**一般主流虚拟机都不采用这个方法**; - 打手心里默念到,哇唔,口水......真......
- 缺点:如图中所示,无法解决循环引用的问题,当A引用B,B也引用A的时候,此时AB对象的引用都不为0,此时也就无法垃圾回收,所以
- nen知道堆中的每个对象吧,额(我)们会 给每个对象的引用贴一张小纸条,就像这样,呸,再用口水贴上去,然后我们有专门的仪器来记录每个对象贴的纸条数。
- 可达性分析算法:(从一个被称为GC Roots的对象为起始点向下搜索(搜索所走过的路径称为引用链(Reference Chain)),如果一个对象到GC Roots没有任何引用链相连接时或者说GC Roots到这个对象不可达时,说明此对象不可用)。请注意,可达~可到达 ~,我们公司有个很长的杆子,我们会找个起点,然后站在起点(起点就是一个被称为GC Roots的对象)上开戳,可达的(能戳到的)就是活着的(活着的就不是垃圾嘞,是不能回收的)
- 在java中可以作为GC Roots的对象有以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的变量
- 方法区常量池中常量引用的对象
- 本地方法栈JNI引用的对象
- 但是现在很多应用仅仅方法区都有好几百兆,如果逐个检查这里面的引用,那么会消耗很多时间:
- 在java中可以作为GC Roots的对象有以下几种:
- 但一个对象满足上述条件的时候,不会马上被回收,也就是说就算一个对象不可达也并非是非死不可的,暂时处于缓刑状态,
要宣告这个对象死亡还需要进行两次标记;- 第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那么这个对象**
将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。-----书的作者有这么一句话“建议大家完全忘掉Java中的finalize()方法的存在”**- 若对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,此时虚拟机认为此对象没有必要执行finalize()方法(也就是判断当前对象是否有finalize()方法并且该方法没有被执行过)
若这个对象被判定为有必要执行finalize()方法,那么会将当前对象放入F-Queue队列中,等待第二次小规模标记,并在稍后由一个虚拟机自动建立的、低优先级的finalize线程去执行(这个执行是指虚拟机会触发这个finalize()方法,虚拟机不保证该方法一定会被执行,原因是:)- 如果线程中的一个对象执行的很缓慢或进入了死锁,很有可能会导致F-Queue队列中其他对象永久处于等待,会导致回收系统的崩溃;
- 另外就是任何一个对象的finalize()方法都只会被系统自动调用一次,当对象面临下一次回收时就不能再指望这执行finalize()进行自救了
- 如果线程中的一个对象执行的很缓慢或进入了死锁,很有可能会导致F-Queue队列中其他对象永久处于等待,会导致回收系统的崩溃;
- 若对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,此时虚拟机认为此对象没有必要执行finalize()方法(也就是判断当前对象是否有finalize()方法并且该方法没有被执行过)
- 第二次标记:此时GC会对F-Queue中的对象进行**
第二次小规模的标记**,如果此时对象要在finalize()方法中拯救自己逃脱死亡命运,只要与引用链上的任何一个对象建立上关联即可(比如把自己(this关键字)赋值给某个类变量或者对象的成员变量,就可以在第二次标记时使自己被移出即将回收的范围之中)
- 第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那么这个对象**
哦对啦,还有我们公司有个新员工,叫小胡,有些对这个方法的补充,可以分享给你这个大老粗看看喽。乱用有风险哈,使用需谨慎......
好,说完了堆子哥到**
区子哥**,区子哥是用下面这个方法,不用上面俩方法。
- 方法区(HotSpot虚拟机中的永久代),Java虚拟机规范中说的是方法区或者说永久代的垃圾收集效率很低
- 永久代的垃圾收集主要回收两部分内容:
- 废弃常量:举个例子,比如说一个字符串(字面量)“hhb”已经进入了常量池中**
但是没有任何一个String对象或者其他地方引用这个常量池中的"hhb"常量**,如果此时发生内存回收必要时这个"hhb"就会被系统清理出常量池。常量池中的其他类、接口、方法、字段的符号引用等是类似的 - 无用的类,
- 类同时要满足下面三个条件这个类才能算是无用的类:
- 该类所有的实例都已经被回收(Java堆中不存在该类的任何实例)
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问到该类的方法们
- 但是不是说满足了就被回收,是否对类进行回收,
- 类同时要满足下面三个条件这个类才能算是无用的类:
- 废弃常量:举个例子,比如说一个字符串(字面量)“hhb”已经进入了常量池中**
- 永久代的垃圾收集主要回收两部分内容:
老规矩,买一个图赠送一个图。 无论是引用计数还是根可达算法,判断一个对象是否为垃圾都要和引用挂钩,所以,咱们看看引用到底都有些啥:
- 强引用、软引用、弱引用、虚引用是什么,有什么区别:
- 强引用,就是在程序代码中普遍存在的普通的对象引用关系,如 String s = new String("hhb")
- 只要强引用还存在垃圾回收器就永远不会回收掉被引用的对象
- 软引用,用于维护一些可有可无(有用但是并非必须的对象)的对象。
只有在内存不足时或者说将要发生内存溢出异常时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。JDK1.2后提供了SoftReference类来实现软引用 - 弱引用,相比软引用来说,要更加无用一些,它拥有更短的生命周期(被弱引用关联的对象只能生存到下一次垃圾回收发生之前),当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。JDK1.2后提供了WeakReference 实现类来实现弱引用
- 虚引用,也叫幽灵引用或者幻影引用。是一种形同虚设的引用,一个对象无法通过虚引用来取得一个对象实例,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动,也就是
为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收器回收时收到一个系统通知。JDK1.2后提供了PhantomReference实现类来实现虚引用
- 强引用,就是在程序代码中普遍存在的普通的对象引用关系,如 String s = new String("hhb")
打手:知道啦知道啦,啥时候开扫呢,都快臭了。
保洁组长:要不是你我早开工了。兄得们,整起来......咱们分为五个小组哈.....先上A组
A组:就叫做“标清”(标记清除法)
打手心里默念:标清,我还高清呢,给谁一天天在那秀呢......
A组:不给您吹,我这标清,是先把垃圾们给"标记"出来,再把标记出来的垃圾给一个一个干掉。
不和你废话,直接上干货。
打手:不是听说有个啥买一赠一嘛,
保洁组长:肯定有呀,来呀,上货
- 标记清除法(Mark-Sweep):
- 算法分为标记和清除两个阶段
- 第一步:利用可达性去遍历内存,
把存活对象和垃圾对象分别进行标记; - 第二步:在遍历一遍,将所有标记的对象回收掉
- 第一步:利用可达性去遍历内存,
- 特点:
- 效率不行,标记和清除的效率都不高;
- 标记和清除后会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次GC;
- 算法分为标记和清除两个阶段
保洁组长:咳咳咳,咱们时间有限哈,上B组
B组:我们biao整~
打手:不要争,啥不要争。
B组:我们会再巡视一遍,会把不是垃圾的正派物件放在一块,no散放
打手:可以呀,还知道物以类聚,人以群分
别说话,买一赠一
- 标记整理法:
- 第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;
- 第二步:将所有的存活的对象向一段移动,将端边界以外的对象都回收掉;
- 特点:
- 适用于存活对象多,垃圾少的情况;
- 需要整理的过程,无空间碎片产生;
保洁队长:C组,赶紧上,快下班了都,抓点紧
C组,我们主要把我们所有的地盘平分为两块,一块用时另一块闲置,等把用的这一块用完了,就把这一块上的好东西放到闲置的那一块上,然后把之前用的这一块里面的脏垃圾处理一下。然后,换角色....
打手:啥
C组:算了,大老粗,直接给你上图吧
老规矩,不偏心,买一赠一
- 复制算法(为了解决标记清除的效率问题): 将**
内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上**,然后在把使用过的内存空间一次性清理掉;- 现在的商业虚拟机都采用**
复制算法来回收新生代,但是新生代中的对象98%都是朝生夕死的所以不用按照1:1来平分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间(HotSpot默认Eden和Survivor大小比例是8:1),每次使用Eden和其中一块Survivor**- 回收时将Eden和Survivor中还存活的对象一次性都复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间
- 当Survivor内存不够时需要依赖其他内存(老年代)进行分配担保,这些放不下的对象会直接通过分配担保机制进入老年代
- 回收时将Eden和Survivor中还存活的对象一次性都复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间
- 特点:
- 不会产生空间碎片;
- 内存使用率极低
- 现在的商业虚拟机都采用**
保洁队长:D组上,上完吃饭
D组:我们是那啥,分代回收滴
打手:分代,爷爷、孙子......
保洁队长,nonono,大老粗,算了,给你上图吧,一看便知
- 分代收集算法: 根据内存对象的存活周期不同,将内存划分成几块,
java虚拟机一般将Java堆内存分成新生代和老生代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收;
嗖......嗖......嗖...... 打手:哎呦,还来个这,我倒要看看你啥赠品,刚转过头 保洁员和四个组的组员已经撒腿像一楼大门跑去了 保洁组长边跑边说:我们饿了,明天再说 打手喊道,哎,你四四组就光说了个咋扫垃圾,还没说:
- 你们四个组都用的啥工具呀,我咋给你们准备道具呢?
- 或者,你也没说你们哪个组扫哪里呢?,
哎,你快回来,我已经忍受不住...... 打手:算了,我也饿了,先吃饭再说吧,明在和他们细说。
巨人的肩膀: 深入理解Java虚拟机