声明:学习笔记,有错误希望指出,谢谢。
一:为什么需要GC?为什么需要懂GC?
前言:或许你会问:为什么GC是自动化完成的,我们还要关注它的实现原理呢?
因为在高并发量时,我们对这些自动化技术需要监控和调节。
虽然程序计数器,虚拟机栈,本地方法栈三个区域随线程而生,随线程而销毁,在这些区域里面不需要太多的考虑垃圾的回收,但是在Java堆和方法区,一个Interface的多个实现类可能内存不一样,一个方法的多个分支也可以不一样,而且明显已知,在Web项目里面,很多对象都是随着程序的运行而产生的。
二:什么对象需要被回收?
现在我们需要来判断哪些对象是死的对象,也就是不可能再被使用的对象。
常见的垃圾收集算法。
算法名 | 如何实现 |
---|---|
引用计数算法(Reference Countinng) | 当被引用加,引用失效减1为0的默认为不可能再被使用,但是JVM没有使用它,由于它的性能消耗较大 |
根搜索法(GC Roots tracing) | 通过一系列的GC Roots(一般为虚拟机栈(栈帧的本地变量表),方法区的静态属性引用的变量,方法区常量区引用的对象,Native引用的对象)的对象作为起始点,向下开始搜素,,搜索走过的路径叫做引用链,当一个对象到GC Roots没有任何任何引用链的时候那就证明该对象不可用,需要被回收。 但是呢,要想被回收,还需要进行一次判断,也就是第二次标记,前面为第一次标记,第二次标记看的是他是否会执行finalize()方法,下面表格会有介绍 |
标记-清除算法(marks-sweep) | 上面的方法已经简述了标记的过程,但是他的缺点也是显而易见的,效率不高,而且被回收后,将会有大量的不连续的内存,当下次在进行回收的时候,需要连续分配空间的时候将会遇到问题。 |
复制算法(copying) | 复制一块和以前一样大的内存,然后将存活的对象放在另外一块上面,再清除掉原内存上的所占空间,这样就不会考虑内存碎片得我问题。但是这个算法会增加内存占用。但是现在大部分虚拟机都使用这种算法进行垃圾回收,由于前面已经说了内存占用问题,所以在考虑分配内存时,会考虑分配的比例,一般来说是8:1,这样未被回收的对象,将直接进入老年代。 |
标记-整理(mark-compact) | 显而易见,对于老年代来说,他们的存活率较高,如果采用复制算法,可想而知,内存的占用将非常的大,所以现在对于这一代派生出标记整理算法进行解决。思路大概就是在标记清除的基础上,将存活对象顺序的存放在内存中,避免了内存碎片化。 |
分代收集算法(Generational Collection) | 思路就是根据对象所属那一代而进行收集,看上述表格各个算法适用场景,简单易知。 |
final finally finalize介绍
名字 | 重点内容 介绍 |
---|---|
final—修饰符(关键字) | 如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。 |
finally | —再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。 |
finalize—方法名 | Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。它存在的意义就是,如果一个独享被判定需要执行该方法。它就会被放在一个F-Queue的队列里面,之后到JVM自己建的线程Finalizer执行,之后GC会进行第二次标记,确定该对象是否被回收。 |
三:回收方法区(永久代)
方法区主要回收内容 | 判断 |
---|---|
废弃常量 | 这个很好判断,一般没有被引用就会被“请”出常量池 |
无用的类 | 该类的所有的实例都被回收,加载该类的ClassLoader已被回收,该类对应的Java.lang.class没在任何地方被引用,无法在任何地方通过反射访问该类的方法。但是满足这些条件不是必须回收,虚拟机提供参数进行控制。 |
四:垃圾回收与内存分配的关系
内存分配遵循下面几个特点:
- 对象优先分配在Eden区(Eden简介链接:http://blog.csdn.net/wy5612087/article/details/52369677)。
- 大对象(需要大量连续内存空间的java对象 例如很长的字符串 数组等等)直接进入老年代。你或许会问为什么,大概我的理解就是因为在新生代中采用的垃圾回收算法是复制算法,由于这种对象占用大量的内存空间,在回收的时候性能消耗很大。
- 长期存活的对象进入老年代,上面也已经讲到。这也就是分代收集算法的前提。
- 空间分配担保(在极端情况下,内存回收后新生代全部存活,那么现在就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年区。)