目录
-----------------------------------------------------------建议先去看jvm内存划分-点这里
垃圾回收的常规问题
GC 是什么?
垃圾回收 内存管理:java的重要特性,作用是释放不再被使用的内存。
为什么要有 GC?
- 像c++就没有垃圾回收,需要手动管理,垃圾对象需要手动释放,浪费精力
- 一旦垃圾对象累积,没有及时释放、回收,就会造成内存溢出
垃圾回收的优点
- Java 语言中一个显著的特点就是引入了垃圾回收机制,使得 Java 程序员在编写程序的时候不再需要考虑内存管理。
- 由于有个垃圾回收机制,Java 中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。
- 垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。
GC如何实现、原理
- 垃圾回收器通常是作为一个单独的低级别的线程运行,
- 在不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,
- 程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。
回收的基本原理----对象的垃圾回收
- 对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。
- 通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不 可达的"。当 GC 确定一些对象为"不可达"时,GC 就有责任回收这些内存空间。
JVM GC 怎么判断对象可以被回收了?
· 对象没有引用
· 作用域发生未捕获异常
· 程序在作用域正常执行完毕
· 程序执行了 System.exit()
· 程序发生意外终止(被杀线程等)
简述几种 垃圾回收机制
回收机制有分代复制垃圾回收、增量垃圾回收、标记垃圾回收
- 增量收集器:
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。 - 分代收集器:
这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。JVM生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。
垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
- 不可以。
- 程序员可以手动执行 System.gc(),通知GC 运行,但是 Java 语言规范并不保证 GC 一定会执行。
JVM GC 回收哪个区域内的垃圾?
- 注意:JVM GC 只回收堆区和方法区内的对象。
- 而栈区的数据,在超出作用域后会被 JVM 自动释放掉,所以其不在 JVM GC 的管理范围内。
JVM GC 什么时候执行?
- eden 区空间不够存放新对象的时候,执行 Minro GC。
- 升到老年代的对象大于老年代剩余空间的时候执行 Full GC,或者小于的时候被 HandlePromotionFailure 参数强制 Full GC。
- 调优主要是减少 Full GC 的触发次数,可以通过 NewRatio 控制新生代转老年代的比例,通过 MaxTenuringThreshold 设置对象进入老年代的年龄阀值(后面介绍)。
按代的垃圾回收机制
1、分类
- 新生代(Young generation):绝大多数最新被创建的对象都会被分配到这里,由于大部分在创建后很快变得不可达,很多对象被创建在新生代,然后“消失”。
对象从这个区域“消失”的过程我们称之为:Minor GC 。 - 老年代(Old generation):对象没有变得不可达,并且从新生代周期中存活了下来,会被拷贝到这里。其区域分配的空间要比新生代多。也正由于其相对大的空间,发生在老年代的 GC 次数要比新生代少得多。
对象从老年代中消失的过程,称之为:Major GC 或者 Full GC。 - 持久代(Permanent generation)也称之为 方法区(Method area)----jdk1.8:元空间(Metaspace):用于保存类常量以及字符串常量。注意,这个区域不是用于存储那些从老年代存活下来的对象,这个区域也可能发生 GC—对Class对象的GC。
发生在这个区域的 GC 事件也被算为 Major GC 。
只不过在这个区域发生 GC 的条件非常严苛,必须符合以下三种条件才会被回收:- 1、所有实例被回收
- 2、加载该类的 ClassLoader 被回收
- 3、Class 对象无法通过任何途径访问(包括反射)
说明:
- 默认的新生代(Young generation)、老年代(Old generation)所占空间比例为1 : 2 。
2、如果老年代的对象需要引用新生代的对象,会发生什么呢?
- 为了解决这个问题,老年代中存在一个 card table ,它是一个 512byte 大小的块。
- 所有老年代的对象指向新生代对象的引用都会被记录在这个表中。
- 当针对新生代执行 GC 的时候,只需要查询 card table 来决定是否可以被回收,而不用查询整个老年代。
- 这个 card table 由一个 write barrier 来管理。write barrier 给 GC 带来了很大的性能提升,虽然由此可能带来一些开销,但完全是值得的。
3、新生代空间的构成与逻辑
1- 新生代用来保存那些第一次被创建的对象,分成三个空间:
· 一个伊甸园空间(Eden)
· 两个幸存者空间(Fron Survivor、To Survivor)
默认新生代空间的分配:Eden : Fron : To = 8 : 1 : 1
2- 每个空间的执行顺序:
- 1、绝大多数刚刚被创建的对象会存放在伊甸园空间(Eden)。
- 2、在伊甸园空间执行第一次 GC(Minor GC)之后,存活的对象被移动到其中一个幸存者空间(Survivor)。
- 3、此后,每次伊甸园空间执行 GC 后,存活的对象会被堆积在同一个幸存者空间。
- 4、当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。然后会清空已经饱和的哪个幸存者空间。
- 5、在以上步骤中重复 N 次(N = MaxTenuringThreshold(年龄阀值设定,默认 15))依然存活的对象,就会被移动到老年代。
说明:
从上面的步骤可以发现,两个幸存者空间,必须有一个是保持空的。如果两个两个幸存者空间都有数据,或两个空间都是空的,那一定是你的系统出现了某种错误。
3- 重点记住:
- 对象在刚刚被创建之后,是保存在伊甸园空间的(Eden)。那些长期存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)。
- 也有例外出现,对于一些比较大的对象(需要分配一块比较大的连续内存空间)则直接进入到老年代。一般在 Survivor 空间不足的情况下发生。
4、老年代空间的构成与逻辑
- 老年代空间的构成其实很简单,它不像新生代空间那样划分为几个区域,
- 它只有一个区域(Old generation),里面存储的对象并不像新生代空间绝大部分都是朝闻道,夕死矣。
- 这里的对象几乎都是从 Survivor 空间中熬过来的,它们绝不会轻易的狗带。
- 因此,Full GC(Major GC)发生的次数不会有 Minor GC 那么频繁,
- 并且做一次 Major GC 的时间比 Minor GC 要更长(约 10 倍)。
JVM GC 算法
GC 算法总括
- 根搜索算法
- 标记 - 清除算法
- 复制算法
- 标记 - 整理算法
分代垃圾回收的GC算法
- JVM 为了优化内存的回收,使用了分代回收的方式,
- 对于新生代内存的回收(Minor GC)主要采用复制算法。
- 而对于老年代的回收(Major GC),大多采用标记-整理算法。
各GC算法详解
1、根搜索算法(可达性分析算法)
- 根搜索算法是从离散数学中的图论引入的,
- 程序把所有引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点。
- 当所有的引用节点寻找完毕后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
上图红色为无用的节点,可以被回收。
目前 Java 中可以作为 GC ROOT 的对象有:
- 1、虚拟机栈中引用的对象(本地变量表)
- 2、方法区中静态属性引用的对象
- 3、方法区中常亮引用的对象
- 4、本地方法栈中引用的对象(Native 对象)
基本所有 GC 算法都引用根搜索算法这种概念。
2、标记 - 清除算法
- 标记-清除算法采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象进行直接回收,如上图。
- 标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活的对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,并没有对还存活的对象进行整理,因此会导致内存碎片。
3、复制算法
- 复制算法将内存划分为两个区间,使用此算法时,所有动态分配的对象都只能分配在其中一个区间(活动区间),而另外一个区间(空间区间)则是空闲的。
- 复制算法采用从根集合扫描,将存活的对象复制到空闲区间,当扫描完毕活动区间后,会的将活动区间一次性全部回收。此时原本的空闲区间变成了活动区间。下次 GC 时候又会重复刚才的操作,以此循环。
- 复制算法在存活对象比较少的时候,极为高效,但是带来的成本是牺牲一半的内存空间用于进行对象的移动。所以复制算法的使用场景,必须是对象的存活率非常低才行,而且最重要的是,我们需要克服 50%内存的浪费。
4、标记 - 整理算法
- 标记-整理 算法采用 标记-清除 算法一样的方式进行对象的标记、清除,但在回收不存活的对象占用的空间后,会将所有存活的对象往左端空闲空间移动,并更新对应的指针。
- 标记-整理 算法是在标记-清除 算法之上,又进行了对象的移动排序整理,因此成本更高,但却解决了内存碎片的问题。
jvm判断对象是否存活的算法
包括
- 引用计数算法
- 可达性分析算法
详解
1、引用计数算法
1.算法分析
- 引用计数是垃圾收集器中的早期策略。
- 在这种方法中,堆中每个对象实例都有一个引用计数。
- -当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。
-当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),
-但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。
-任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
2.优点:
- 引用计数收集器可以很快的执行,交织在程序运行中。
- 对程序需要不被长时间打断的实时环境比较有利。
3.缺点:
- 无法检测出循环引用。
- 如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
缺点-代码示例:
public class ReferenceFindTest {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;object2 = null;
}
}
2、可达性分析算法
见本文上