Android 面试回答 老司机教你深入理解gc回收机制

Dalvik VM

Dalvik 虚拟机采用寄存器架构,而不是 JVM 的栈结构。Java 程序编译后的 .class 文件并不能在 Dalvik 中解释执行。因此 Google 提供了一个 dx 工具,

用于将 .class 文件转换成 Dalivk 能够识别的 .dex 格式。具体 Dalvik VM 的细节不是本文重点,以下不再讨论。

APP虚拟机:
  安卓Android上的应用是java,当然需要虚拟机,而安卓Android上的应用是带有独立虚拟机的,也就是每开一个应用就会打开一个独立的虚拟机.这样设计的原因是可以避免虚拟机崩溃导致整个系统崩溃,但代价就是需要更多内存。

 触发GC(Garbage Collector)的条件

 
    1)GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用。但下面的条件例外。
 
    2)Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制调用GC线程。若GC一次之后仍不能满足内存分配,JVM会再进行两次GC,若仍无法满足要求,则JVM将报“out of memory”的错误,Java应用将停止。

二:垃圾回收机制的算法

java语言规范没有明确的说明JVM 使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做两件基本事情:(1)发现无用的信息对象;(2)回收将无用对象占用的内存空间。使该空间可被程序再次使用。


1。引用计数法(Reference Counting Collector)

     1.1:算法分析:

      引用计数算法是垃圾回收器中的早起策略,在这种方法中,堆中的每个对象实例都有一个引用计数器,点一个对象被创建时,且该对象实例分配给一个变量,该变量计数设置为1 ,当任何其他变量赋值为这个对象的引用时,计数加1 ,(a=b ,则b引用的对象实例计数器+1)但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1,任何引用计数器为0 的对象实例可以当做垃圾收集。 当一个对象的实例被垃圾收集是,它引用的任何对象实例的引用计数器减1.


引用计数算法无法解决循环引用问题,例如:

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
          
        object1.object = object2;
        object2.object = object1;
          
        object1 = null;
        object2 = null;
    }
}

最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。

.可达性分析算法
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常GC采用有向图的方式记录并管理堆中的所有对象,通过这种方式确定哪些对象时“可达”,哪些对象时“不可达”。当对象不可达的时候,即对象不再被引用的时候,就会被垃圾回收。

根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

java中可作为GC Root的对象有

1.虚拟机栈中引用的对象(本地变量表)

2.方法区中静态属性引用的对象

3. 方法区中常量引用的对象

4.本地方法栈中引用的对象(Native对象)

二、GC是如何工作的

GC的工作流程主要分为如下几个步骤:

1、标记(Mark)

2、计划(Plan)

3、清理(Sweep)

4、引用更新(Relocate)

5、压缩(Compact)

Android 面试回答 老司机教你深入理解gc回收机制


这些假设我们称之为弱年代假设( weak generational hypothesis)。为了强化这一假设,HotSpot虚拟机将其物理上划分为两个–新生代(young generation)和老年代(old generation)。
新生代(Young generation): 绝大多数最新被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可到达,所以很多对象被创建在新生代,然后消失。对象从这个区域消失的过程我们称之为”minor GC“。

老年代(Old generation): 对象没有变得不可达,并且从新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。也正由于其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,我们称之为”major GC“(或者”full GC“),在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中


上图中的持久代( permanent generation )也被称为方法区method area)。他用来保存类常量以及字符串常量。因此,这个区域不是用来永久的存储那些从老年代存活下来的对象。这个区域也可能发生GC。并且发生在这个区域上的GC事件也会被算为major GC。


Android 面试回答 老司机教你深入理解gc回收机制

我的总结:
1.一般new出来的对象会存在在新生代(minor GC),很多新的对象没有使用,会被回收
2.GC一次回收,第二次回收,还有可达的对象会存放到老年代(full GC)



强引用
弱应用
阮引用

减少GC开销的措施
 
    1)不要显式调用System.gc()。此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。大大的影响系统性能。
 
     2)尽量减少临时对象的使用。临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
 
    3)对象不用时最好显式置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
 
    4)尽量使用StringBuffer,而不用String来累加字符串。由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
 
    5)能用基本类型如Int,Long,就不用Integer,Long对象。基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
 
    6)尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
 
    7)分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。
 
 

①Java和C++在内存分配和管理上有什么区别?

Java与C++之间有一堵由动态内存分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人想出来。
对于从事C和C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权利的皇帝,也是从事最基础工作的劳动人民-----既拥有每一个对象的所有权,又担负着每一个对象从生命开始到终结的维护责任。
对于Java程序员来说,虚拟机的自动内存分配机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,而且不容易出现内存泄露和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是因为Java程序员把内存控制的权利交给Java虚拟机,一旦出现内存泄露和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误将会是一项异常艰难的工作。

并且好的Java程序在编写的时候肯定要考虑GC的问题,怎样定义static对象,怎样new对象效率更高等等问题,简称面向GC的编程

也可以说Java的内存分配管理是一种托管的方式,托管于JVM。

C++经过编译时直接编译成机器码,而Java是编译成字节码,由JVM解释执行。

C++是编译型语言,而Java兼具编译型和解释型语言的特点。

②Java虚拟机规范将JVM虚拟机所管理的内存分为几部分?


回答重点:
能说明minor gc/full gc的触发条件、OOM的触发条件,降低GC的调优的策略。 
分析:列举一些我期望的回答:eden满了minor gc,升到老年代的对象大于老年代剩余空间full gc,或者小于时被HandlePromotionFailure参数强制full gc;gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制新生代老年代比例,通过MaxTenuringThreshold控制进入老年前生存次数等……