想要成为一名优秀的Android开发,你需要一份完备的 知识体系,在这里,让我们一起成长为自己所想的那样~。
GC需要完成的三件事情
哪些内存需要回收
什么时候回收
如何回收
在Java内存运行时区域的各个部分,其中程序计数器,虚拟机栈 本地方法栈 3个区域随着线程生,随着线程灭;栈中 的栈帧随着方法的进入和退出执行入栈和出栈的操作。每一个栈帧分配多少内存基本上在类结构确定下来 的时候就已经知道了,因此这几个区域的内存分配和回收都具备确定性,在这几个区域不考虑内存回收的问题,而堆和方法区不一样,一个接口多个实现类所需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间才能知道创建那些对象,这部分对象的分配和回收的动态的。
如何判断对象已死
-
引用计数法:每当一个地方引用他,计数器加1,引用失效计数器减一。 在大部分情况下是一个不错的算法,但是无法解决循环引用问题。
-
可达性分析算法:该算法的基本思路是 通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始往下搜索,搜索所走过的路径 称为链,当一个对象到GC roots 没有任何引用链,就证明这个对象不可用。
在JAVA中客作为GC roots的对象有以下几种,虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI 引用的对象
引用
在jdk1.2之前 在java引用的定义很传统,导致对象只有两种状态被引用和没被引用的状态,无法描述一种状态如“食之无味,弃之可惜”的情况,我们希望能描述一个对象 当内存足够的时候,保留在内存中,如果内存紧张 则可以抛弃他们,在jdk1.2后将引用由细分为强引用,软引用,弱引用,虚引用等。
- 强引用,只要强引用存在,垃圾收集器不会回收掉被引用的状态
- 软引用,用来描述一些还有用,但并非必需的对象,在系统将要发生内存溢出异常之前,将会对这些对象进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
- 弱引用,强度比软引用还弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。乌云内存是否足够们都会被回收
- 虚引用,它是最弱的引用,有无这个引用完全不对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾收集器回收的时候收到一个系统通知。
生存还是死亡
当被可达性分析算法发现 一个对象 是不可达对象,不一定就会被回收。宣告一个对象的死亡,至少需要经历两次标记。当对象被发现要被回收,会被第一次标记并进行筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖这个方法,或者这个方法已经被调研过了,会被视为没有必要执行,如果被判断需要执行的,会将对象放到一个F-Queue的队列中,并且稍后会由一个虚拟机自动建立,第优先级的Finalizer的线程去执行。(这里的执行指让虚拟机去触发这个机会,但并不承诺等待他运行结束,原因在于,对象如果在finalize方法中执行缓慢,或死循环,导致队列处于等待状态,会让内存回收系统崩溃),之后GC会对F-Queue中的对象进行第二次标记,如果对象在finalize有何其他对象建立关联,则会被移除即将回收的集合中,如果没有则就被真正回收
注意事项
任何一个对象的finalize方法只会背调用一次,如果面临下一次回收,他的finalize方法不会被执行。
回收方法区
方法区进行垃圾回收的性价比比较低。永久代的垃圾回收主要回收两个部分:废弃的常量和无用的类
判断一个类是否是无用的类条件
- 该类的所有实例都已经被回收
- 加载该类的classload被回收
- 该类所对应的java.lang.class对象没有被任何地方引用,无法在任何地方通过反射访问该类的方法。
垃圾算法
- 标记-清除算法,
算法分为两个部分,一个是标记一个是清除,首先标记出所需要回收的部分,回收标记的对象,是一个最基础的收集算法,但是不足点有两个在于:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,清除后会出现大量不连续的内存碎片,如果出现大内存对象 需要再次提前出发垃圾回收操作来进行垃圾收集的工作如图
2.复制算法:
将内存分为大小相等的两块,每次只使用一块。当一块用完了,将还存在的对象复制到另一块上,然后再次吧使用过的内存一次清理掉,使得每次支队半个区域进行内存回收。按顺序分配内存,实现简单,运行高效,缺点在于将内存缩小为原先一半
IBM的专门研究表明,新生代中的对象98%是朝生夕死的,所以并不需要按照1∶1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor的空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1
也就是新生代为80+10 只有10的内存是浪费的,但是无法保障,只有不多与10的对象存活,如果空间不够需要到其他内存中进行分配担保。
3.标记-整理算法
复制算法在对象存活率比较高的时候,进行较多的复制操作,效率会变低。如果不浪费一半的内存就需要而外的空间做担保,在老年代中不考虑用这种算法
这个算法标记部分与标记-清除一样,但是后徐步骤,不是将可回收的对象进行清理,而是让所有存货的对象向一段移动,清理边界外的内存
分代收集算法
把堆中分为新时代和老年代,在新生代中对象存活率低,使用复制算法,在老年代中,使用标记-清理或者标记整理进行回收
可达性分析的几个问题,如果不做优化处理,对于检查每一个引用的检查,必然要消耗很多时间,另外,在gc停顿上,因为需要做引用链的处理,所以需要在某一个时刻各个线程停止操作,让对象引用关系不能变化,在主流的java虚拟机中,使用准确式GC,当系统停顿下来,不需要做检查完所有全局的引用,虚拟机就可以知道那些地方存放对象的引用。在HotShop中的实现使用一组OopMap的数据结构,在类加载的时候,吧对象相关的信息计算出来,在JIT编译的时候,也会在特定的位置记录栈和寄存器那些地方有引用。 在Oop的协助下,HotShop就可以快速完成GC roots的枚举
内存分配策略
- 对象优先在Eden分配,当空间不够,发动一次gc
- 大对象进入老年代,如很长的字符串以及数组,
- 长期存活的对象进入老年代,虚拟机给每个对象定义一个年龄,对象在Eden出生并且第一次被gc还存活,并且被Survior容纳,每次熬过一次gc,年龄加1,默认中增加到15岁进入老年代,这个年龄阀值客调整
- 动态对象年龄判断,虚拟机并不要求对象年龄必须要大与多少就要进入老年代,如果在Survivor空间中相同年龄所有对象大小大一Survivor空间的一半,年龄大于或者该年龄的对象就可以直接进入老年代。
- 空间担保,每次GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果是gc是安全的,如果不处理,会判断设置是否允许担保失败,如果允许 会检查老年代中最大可用的空间是否大于历次进入老年代对象的平均大小,如果是 则会进行gc,如果小与或者设置不允许担保免责改为一次Full gc
本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...
网友评论