相信很多人在面试的时候都会被问道你知道垃圾回收算法,机制吗?能展开说说吗?当然,笔者也不例外,面试的时候基本上都会被问道类似于这样的问题,下面主要是来自于我自己的总结。
直接上图,一般情况下在堆里面会被逻辑性的划分为三个区域。笔者在前面的文章曾经说过堆详细
我们都知道java的世界里面new的对象一般都是在堆上面的,但是也有可能是在栈上面(对象逃逸分析)。
对象逃逸分析:就是发现对象的作用域,当一个对象在方法中被定义后,它可能会被外部方法引用,例如作为参数传递到其他的方法中。如下图所示:
在jdk18后对于此方面做了部分优化,对象在创建的时候首先会在栈上分配内存,如果失败,再在堆上分配。
既然java创建的对象大部分都是在堆上面分配内存,如果堆上面内存不够了应该怎么办呢,就像我们的垃圾桶一样,满了就需要去有专门的人去倒掉,堆上面满了也是如此。这就是jvm的垃圾回收。
在平时我们写代码的时候,启动项目可以通过jvisualvm查看运行状态(可以看到meteSpeace(元空间(堆))的使用情况 有新生代 和老年代),下图是我本地运行项目的情况(下面是不同的版本显示的情况)
说垃圾回收之前说一下JVM内存分配(jvm内存分配的策略)
1.1 对象优先在Eden区分配
1.2 大对象直接进入老年代(因为大对象需要连续的存储空间 当大对象加入Eden去放不下 直接加入老年代,而且大对象在Eden区和survivor区来回被复制程非常消耗性能) 有一个参数可以 比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC ,再执行下上面的第一个程序会发现大对象直接进了老年代这样做可以避免为大对象在内存时的频繁复杂操作而降低效率 这个参数只在Serial 或者PaNew两个垃圾回收器下有效
当你知道系统里面很多都是大对象而且不会被垃圾回收器回收可以使用这个参数 让这些对象提早进入老年代
1.3 长期存活的对象将进入老年代 当对象超过15次minor GC还没有被回收 就会进入老年代 也是有一个参数可以设置
什么情况下进行这个参数设置? 一般经验值大概就是5(比如经过5次minor gc还没有被回收可能经过几分钟了 我,可以认为再多的minor gc不会回收 直接进入老年代 类似于spring bean容器的对象 线程池对象.......... )
知道我new的对象可能需要用一段时间 没有执行时间特别长的方法 (比如有一个方法里面new的对象,而这个方法结束的时间特别快)
1.4 对象动态年龄判断
当survivor区域里面的一批对象总大小超过survivor区域50%
假如 目前survivor区域里面 年龄1+年龄2+年龄3的对象总大小已经超过survivor区域大小的50%
那么就会将年龄大于3的所有对象移入老年代(这些对象年龄不需要到达15岁) 这个规则其实是希望那些可能是长期存活 的对象,尽早进入老年代。对象动态年龄判断机制一般是在minor gc之后触发的。 这个也是有一个参数可以设置占比都是可以移入老年代
1.5 Minor gc后存活的对象Survivor区放不下
1.6 老年代空间分配担保机制(执行full gc条件)
(原理 : 当jvm每次执行monior gc 的时候都会去计算老年代剩余的可用空间,如果这个剩余老年代空间
再去判断是否有一个担保参数(“-XX:-HandlePromotionFailure”)(如果没有这个参数直接full gc) JDK1.8之后这个参数默认就有了
如果有这个参数还会去判断 老年代剩余空间是否
(图为老年代空间分配担保机制原理图) 每一次执行minor gc都会去判断是否要执行full gc 如果这个担保机制判断要执行full gc 先执行full gc (full gc也会做minor gc动作) 减少一次minor gc
如果不是这样 先执行minor gc后 最后还是要执行full gc 所以jvm这样设计先执行full gc 主要目对是确保老年代空间可以存下minor gc过来的对象
1.7 Eden与Survivor区默认8:1:1。
如何判断对象可以被回收
1、引用计数法 创建对象都有个引用计数器,有地方引用就+1 引用失效-1 最后为引用为0的回收 ,如果两个对象相互引用GC是无法回收的 目前基本上不用
2、可达性分析算法:这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象 GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等
目前jvm使用的算法
大概原理过程: 先会遍历栈内存里面局部变量表里面所有的局部变量 看这些变量是否有被引用的对象 逐个逐个找
3、常见引用类型:在jvm里面有的对象被引用也会被GC回收
java的引用类型一般分为四种:
强引用:在代码中new()对象
软引用:将对象用SoftReference软引用类型的对象包裹 当释放不出新的空间的时候,会将这些对象回收,,,,
软引用在实际中有重要的应用:例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略 比如 new SoftReference(new User())叫做软引用 软引用可以用来实现内存敏感的高速缓存 了。 在GC后面。
弱引用、虚引用 在GC前
四个区别就是 在jvm做gc的时候 当强引用的对象被引用的的时候不会被回收
而软引用当释放不出新的空间的时候即使被引用也可能被回收 虚引用和弱引用gc直接回收
4、finalize()方法最终判定对象是否存活 ,,有的对象即使在可达性分析中不可达,也不一定会被回收,类似于缓刑的阶段 还要再进行一次标记(这个前提是这个对象没有GC Root根引用) 当这个对象没有覆盖finalize()方法就会被直接回收 如果覆盖了finalize()方法同时将直接和GC Root更上面关联上就不会被回收
finalize方法只会触发一次 ,finalize底层其实使用一个集合,当第一次执行的时候,这个对象就会被从集合中移出,第二次就不会触发finalzie方法
jvm在gc的时候 会判断这个对象是否被finalize()覆盖,如果被覆盖可能不会被回收,finlize()只会自救一次
5、如何判断一个类是无用的类(主要针对于方法区(元空间)) 方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢? 类需要同时满足下面3个条件才能算是 “无用的类” : 1 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。(类的实例对象有一个指针如果指针引用不存在) 2 加载该类的 ClassLoader 已经被回收。(jvm内置的不可能被回收,自定义的类加载器可以被回收)类加载器就是对象。 3 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。