HotSpot(Java)& ART(Android)

  • 前言

      对技术的深层次研究是技术人员的永久追求。由于公司有分享的目标,自己对HotSpot和ART有一定的掌握,借此机会把这块知识好好整理一下,形成一个文档,供大家和我参考。

  • 目标

      曾经一段时间以来,虚拟机就“鬼”一样,从未见过而且让人感到恐惧(通俗点叫“吓尿了”)。战胜恐惧最好的方法就是直面恐惧,就像学琴一样,想要到达下一个高度,发现很难,如果放弃,自己的技艺也就不会提高了,如果坚持战胜它,收获的不仅仅是琴艺的提升,还有战胜困难的勇气和方法。
      故写这篇文章的目的不仅仅是作为分享的文档,也为了打消大家对虚拟机的恐惧感(让“鬼”成“仙”)。

  • 开始

      首先让我们从Java的开始吧,下面我会结合理论和实际(HotSpot源码和例子)来介绍。

一. HotSpot(Java)
  1. Java虚拟机规范和HotSpot的关系:
      很多同学容易把Java虚拟机规范和Java虚拟机搞混。
      其实这两个东西不完全是同一个。主要区别在于一个是规范,一个是符合这个规范的产品。举个🌰,Java虚拟机规范就是类似于Java中的接口,HotSpot类似于Java中的实现类。HotSpot只是一个遵守了Java虚拟机规范,除了它,业界里还有其他的虚拟机产品,比如:
      Classic VM:世界上第一款商用的虚拟机,Java1.0发版的时候使用的虚拟机, 但其现在已经淘汰,技术非常落后。只能使用纯解释器的方式来执行Java代码
      Exact VM:Java1.2的时候,编辑器和解释器混合工作以及两级即时编译器。
      HotSpot VM:全球著名的虚拟机产品,也是目前使用最广泛的虚拟机。
      J9:IBM由生产,类似于Hotspot,其开发目的是作为IBM各种Java产品的执行平台。
      Dalvik VM:Android虚拟机,运行在Android系统上,不完成遵守Java虚拟机规范。

  2. HotSpot的主要功能模块:
      本文主要介绍内存管理,类加载器,执行引擎。至于HotSpot中其他的功能,同志们结合本文自己研究。

    2.1. 内存管理

    2.1.1. HotSpot内存结构(Java Memory Model)
      不同的版本内存结构也是不一样的。
      Java8之前:方法区(Non-Head/Perm),堆区(Head),Java栈(Java Stacks),本地方法栈(Native Method Stacks),程序计数器(Program Counter Register)。 Java8以前
      Java8(包含)之后:元空间(MateSpace),堆区(Head),Java栈(Java Stacks),本地方法栈(Native Method Stacks),程序计数器(Program Counter Register)。

      2.1.1.1. 堆区
      堆分为新生代(Young Gen)和老年代(Old Memory),新生代又分为Eden,S0,S1。
    在这里插入图片描述

      2.1.1.1.1. 内存分配过程(堆)
      堆是线程共享的。存在物理上不连续的内存空间,只要逻辑是连续的即可。大小可以设为固定大小,也可以扩展。堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象默认都存储在堆内存中。
      默认情况下,所有的新创建的对象会在新生代的Eden分配。在包括但不限于以下几种情况时,会在老年代新增对象:

    1. 创建了一个大对象(Big Object),并且经过了GC(Minor GC)之后,仍然无法在Eden上分配。
    2. 新生代的对象经过多次回收(每次Minor GC之后年龄会加1)之后,仍然还活着,当年龄可以通过-XX:ParallelGCThreads时会移动到老年代
    3. Minor GC之后发现剩余的存活对象太多了,没办法放入另外一块Survivor,那么这个时候就必须得把这些对象直接转移到老年代中去

      
      2.1.1.1.2. 逃逸分析
       通过逃逸分析来决定某些实例或者变量是否要在堆中进行分配,如果开启了逃逸分析,变量(局部变量)就不一定非在堆上进行分配。发生逃逸行为的情况有两种:方法逃逸和线程逃逸。
      方法逃逸: 当一个对象在方法中定义之后,作为参数传递到其它方法中;
      线程逃逸: 如类变量或实例变量,可能被其它线程访问到;

       如果不存在逃逸行为,则可以对该对象进行如下优化:同步消除、标量替换和栈上分配
       同步消除: 线程同步本身比较耗时,如果确定一个对象不会逃逸出线程,那该对象的读写就不存在竞争,则可以消除对该对象的同步锁。通过-XX:+EliminateLocks可以开启同步消除。
       标量替换: 标量是指不可分割的量,如java中基本数据类型和reference类型,相对的一个数据可以继续分解,称为聚合量;如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换;如果逃逸分析发现一个对象不会被外部访问,并且该对象可以被拆散,那么经过优化之后,并不直接生成该对象,而是在栈上创建若干个成员变量。
       -XX:+EliminateAllocations可以开启标量替换。
       -XX:+PrintEliminateAllocations查看标量替换情况。
      栈上分配: 故名思议就是在栈上分配对象,其实目前Hotspot并没有实现真正意义上的栈上分配,实际上是标量替换。
      **对象定位:**句柄和直接指针
      句柄:Java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据各自的具体地址信息。好处就是reference中存储的是稳定的句柄地址,而对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
    在这里插入图片描述
      直接指针:Java堆对象的布局中就必须考虑如何访问对象访问类型数据的信息,而reference中存储的就是对象在堆中的地址。好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中是非常频繁的,因此这类开销积少成多之后也是非常可观的。HotSpot虚拟机是这种方式进行对象访问。
    在这里插入图片描述
      2.1.1.1.3. 堆区可用参数

    参数说明
    -Xms初始堆内存大小
    -Xmx最大堆内存大小
    -XX:NewSize初始新生代堆大小
    -XX:MaxNewSize新生代最大堆大小
    -XX:SurvivorRatio新生代和老年代比率
    -XX:SurvivorRatioEden和S比率,默认值8
    -XX:NewRatio新生代和老年代比率,默认值2
    XX:+UseTLAB是否使用TLAB分配
    -XX:ParallelGCThreads新生代移动到老年代的年龄,默认值15
    -XX:+DoEscapeAnalysis开启逃逸分析
    -XX:+EliminateAllocations开启标量替换,在变量没有逃逸的情况有用
    -XX:+PrintEliminateAllocations查看标量替换情况

      2.1.1.1.4. 堆源码
    XXXXXXXXX;

      2.1.1.1.5. 看几个例子(堆)
      源代码(堆溢出):

    public class Main{
    	static byte[] bs = new byte[1024*1024];
    	public static void main(String[] args){
    		System.out.println(bs.length);
    	}
    }
    

      第一种:java -Xmx3m -Xms1m Main在这里插入图片描述
      第二种:java -Xmx2m -Xms1m Main在这里插入图片描述
      源代码(逃逸):

    public class A {
        public static void main(String[] args) {
            A a = new A();
            System.out.println(a.fun());
        }
        public int fun() {
            long st = System.currentTimeMillis();
            for (int i = 0; i < 1000000; i++) {
                Test test = new Test();
                test.setA(i);
            }
            long et = System.currentTimeMillis();
            System.out.println(et-st);
            try {
                Thread.sleep(100000);
            } catch (Exception e) {
            }
            return 1;
        }
    }
    
    class Test {
        private int a = 0;
    
        public void setA(int a) {
            this.a = a;
        }
    
        public int getA() {
            return a;
        }
    }
    

      观察GC回收
      第一种:开启逃逸分析,把堆内存变小,查看GC回收情况
      java -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+UseTLAB -XX:+PrintGC -Xmx6M A
    在这里插入图片描述
     emsp;第二种:关闭逃逸分析,,把堆内存变小,查看GC回收情况,明显GC回收次数变多
      java -XX:-DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+UseTLAB -XX:+PrintGC -Xmx6M A
    在这里插入图片描述
      直接查看内存堆:
      第三种:把堆内存变大(没有GC)
      java -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+UseTLAB -XX:+PrintGC -Xmx600M A
    在这里插入图片描述在这里插入图片描述
      第四种:把堆内存变大(没有GC)
      java -XX:-DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+UseTLAB -XX:+PrintGC -Xmx600M A
    在这里插入图片描述在这里插入图片描述
      逃逸分析总结:
      经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有Test对象都没有在堆上分配。不稳定。
      ?新生代和老年代分配验证。

      2.1.1.2. Java栈
      Java虚拟机栈是方法调用和执行的空间,每个方法会封装成一个栈帧压入栈中。其中里面的操作数栈用于进行运算,当前线程只有当前执行的方法才会在操作数栈中调用指令。
      栈帧(方法):局部变量表,操作数栈,动态链接方法,上下文数据等信息。
      局部变量表: 这里面的作用主要是存储一系列的变量信息,而且这些变量都是以数字数组的形式来存储的,一般而言byte,short,char,类型的数据在存储的时候会变为int类型,boolean类型也是存储为数字类型,long,double则是转换为双字节大小的控件存储在栈里面。
      操作数栈: Java虚拟机栈中的一个用于计算的临时数据存储区。存储的数据与局部变量表一致含int、long、float、double、reference、returnType,操作数栈中byte、short、char压栈前(bipush)会被转为int。数据运算的地方,大多数指令都在操作数栈弹栈运算,然后结果压栈。
      动态链接: 动态链接的作用主要还是提供栈里面的对象在进行实例化的时候,能够查找到堆里面相应的类地址,并进行引用。这一整个过程,我们称之为动态链接。
      返回地址: 某个子方法执行完毕之后,需要回到主方法的原有位置继续执行程序,方法出口主要就是记录该信息

      2.1.1.2.1. 栈区可用参数

    参数说明
    -Xss单个线程栈大小

      2.1.1.3. 本地方法栈
      类似Java 栈。
      2.1.1.4. 程序计数器
      线程独享。保存线程的执行状态。记录的是正在执行的虚拟机字节码指令地址行号。如果正在执行的是Native 方法,则这个计数器值为空。没有规定任何OutOfMemoryError情况的区域(Undefined)。
      主要处理Java多线程的问题,在Native层的多线程问题是属于OS的工作。Java线程总是需要以某种形式映射到OS线程上。映射模型可以是1:1(原生线程模型)、n:1(绿色线程 / 用户态线程模型)、m:n(混合模型)。以HotSpot的实现为例,它目前在大多数平台上都使用1:1模型,也就是每个Java线程都直接映射到一个OS线程上执行。此时,native方法就由原生平台直接执行,并不需要理会抽象的JVM层面上的“程序计数器”概念。原生的CPU上真正的程序计数器是怎样就是怎样。就像一个用C或C++写的多线程程序,它在线程切换的时候是怎样的,Java的native方法也就是怎样的。
      2.1.1.5. 方法区(永久代)
      Java8之前才存在。方法区是一块所有线程共享的内存区域,用于存储class二进制文件,包含了虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
    在这里插入图片描述
      2.1.1.5.1. 方法区可用参数

    参数说明
    -XX:PermSize方法区初始大小
    -XX:MaxPermSize方法区最大大小

      2.1.1.6. 元空间
      Java7(包含)之后将运行时常量池从方法区移除到堆内存。
      Java8(包含)之后直接将方法区去掉,在本地内存中新增元空间。运行时常量池仍然在堆中。元数据区存放类加载信息。
      2.1.1.6.1. 元空间可用参数

    参数说明
    -XX:MetaspaceSize元数据区初始值
    -XX:MaxMetaspaceSize元数据区最大值

      更多的配置参数请参考:Java HotSpot VM Optioins

      2.1.1.7. 关于方法区和元空间的例子:
      xxxx????

      2.1.1.78 直接内存:
      虚拟机的内存是由大小的,程序执行过程中,如果需要使用超大的内存区域,这时候可以用直接内存来处理,具体可以参考java.nio.*包。

    2.1.2. HotSpot内存回收(Garbage Collection)
      JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,自动内存清理。因此,我们的内存垃圾回收主要集中于堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
      垃圾回收有两个目标:一:在程序中查找将来无法访问的数据对象;二:并回收那些对象使用的资源。
      2.1.2.1 对象标记: 引用计数法,根搜索算法(可达性算法)
      引用计数法: 每个对象都会有一个引用计数器,当该对象被引用一次之后,该计数器就会+1,当引用结束之后,计数器-1,当计数器为0的时候,就会被标记为垃圾回收对。优点:实现简单,而且效率高。缺点:无法解决循环引用的问题。每个对象都需要单独的计数器来记录引用次数,占用空间。
      根搜索算法: 以跟对象为起点,按照从上往下的顺序,判断链接对象是否可达。不可到达对象则被判定为垃圾对象,需要进行回收。HotSpot采用这种。
      根包括:
      a. 虚拟机栈中引用的对象。
      b. 方法区中类静态属性实体引用的对象。
      c. 方法区中常量引用的对象。
      d. 本地方法栈中JNI引用的对象。
      GC时机:
      a. 程序调用System.gc时
      b. 内存大小不足时:老年代空间不足;方法去空间不足;通过Minor GC后进入老年代的平均大小大于老年代的可用内存;由Eden和S1区向S2区复制时,对象大小大于S2可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。

      2.1.2.2 GC算法:
      2.1.2.2.1 复制算法: 用在新生代中,每次把Eden和S1区存活的对象复制到S2区,然后再把Eden和S1区的内存空间一次清理掉。这样使得每次内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。优点:实现简单;不产生内存碎片。缺点:每次运行,S2内存总是空的,内存使用率不高。
    在这里插入图片描述
      2.1.2.2.2 标记-清理算法(Mark-Sweep): 为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。优点:每个活着的对象的引用只需要找到一个即可,找到一个就可以判断它为活的。此外,更重要的是,这个算法并不移动对象的位置。缺点:效率比较低(递归与全堆对象遍历)。每个活着的对象都要在标记阶段遍历一遍;所有对象都要在清除阶段扫描一遍,因此算法复杂度较高。没有移动对象,导致可能出现很多碎片空间无法利用的情况。
    在这里插入图片描述
      2.1.2.2.3 标记-整理算法(Mark-Contact): 标记-整理法是标记-清理法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。优点:该算法不会像标记-清除算法那样产生大量的碎片空间。缺点:如果存活的对象过多,整理阶段将会执行较多复制操作,导致算法效率降低。
    在这里插入图片描述

      2.1.2.3 GC类型:
      2.1.2.3.1 Serial收集器:
      使用一个线程去回收,可能会产生较长的停顿。参数:-XX:+UseSerialGC
      a) 新生代、老年代使用串行回收;b) 新生代复制算法;c) 老年代标记-压缩。 在这里插入图片描述
      2.1.2.3.2 并行收集器 ParNew
      适用于新生代,老年代依然串行收集。Serial收集器新生代的并行版本。在新生代回收时使用复制算法。多线程,需要多核支持。参数-XX:+UseParNewGC,-XX:ParallelGCThreads 限制线程数量

      2.1.2.3.3 并行收集器 Parallel
      新生代复制算法,老年代标记-压缩,更加关注吞吐量 。参数:-XX:+UseParallelGC 使用Parallel收集器,老年代串行。-XX:+UseParallelOldGC 使用Parallel收集器,老年代并行。

      2.1.2.3.4 CMS收集器(Concurrent Mark Sweep)
      应用程序线程和GC线程交替执行(并发-标记),使用标记-清理算法。并发阶段会降低吞吐量(停顿时间减少,吞吐量降低)。适用于老年代收集器,新生代使用ParNew。参数:XX:+UseConcMarkSweepGC
    在这里插入图片描述
      a)初始标记(会产生全局停顿),根可以直接关联到的对象,速度快。
      b)并发标记(和用户线程一起),主要标记过程,标记全部对象。
      c)重新标记 (会产生全局停顿),由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正。
      d)并发清除(和用户线程一起) ,基于标记结果,直接清理对象。

      2.1.2.3.5 G1收集器
      G1最新的回收技术。
      特点:
      a) 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
      b)可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
      c)使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
      步骤:
      a)标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。
      b)Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
      c)Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域(Region)对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
      d)Remark, 重新标记,会有短暂停顿(STW)。重新标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行)。
      e)Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。

      2.1.2.3.6 总结:
      a)最基本的建议就是尽早释放无用对象的引用。设置为 null。
      b)尽量少用finalize函数。finalize函数是Java提供给程序员一个释放对象或资源的机会。但是,它会加大GC的工作量,因此尽量少采用finalize方式回收资源。关于finalize函数可参考:Java finalize
      c)如果需要使用经常使用的图片,可以使用soft应用类型。它可以尽可能将图片保存在内存中,供程序调用,而不引起OutOfMemory。
      d)注意集合数据类型,包括数组,树,图,链表等数据结构,这些数据结构对GC来说,回收更为复杂。另外,注意一些全局的变量,以及一些静态变量。这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。
      e)当程序有一定的等待时间,程序员可以手动执行System.gc,通知GC运行,但是Java语言规范并不保证GC一定会执行。使用增量式GC可以缩短Java程序的暂停时间。

    2.2. 类加载器
      来吧。

    2.3. 执行引擎
      来吧。

二. ART(Android)
  1. ART堆:
      ART内存除堆有所不同外,其他基本与JVM规范类似。
      Dalvik 拥有单独的原生代码堆栈和 Java 代码堆栈,并且默认的 Java 堆栈大小为 32KB,默认的原生堆栈大小为 1MB。ART 拥有统一的堆栈,用于改善局部位置。通常情况下,ART Thread 堆栈大小应与 Dalvik 堆栈大小近乎相同。
      Android的内存堆是一个*遗传(Generational)*堆,这意味着它会根据预期寿命和所分配对象的大小来跟踪不同的存储桶(Bucket)。例如,最近分配的对象属于年轻代。当对象保持存活状态足够长的时间时,可以将其提升为较老的一代,然后再升级为永久一代。
      每个堆生成都有自己专用的上限,该上限限制了那里的对象可以占用的内存量。每当一代开始填满时,系统都会执行垃圾回收事件以尝试释放内存。垃圾回收的持续时间取决于其收集对象的一代以及每一代中有多少个活动对象。
      共享内存 每个应用程序进程都来自一个称为Zygote的现有进程。当系统启动并加载通用框架代码和资源(例如活动主题)时,Zygote进程开始。要启动一个新的应用程序进程,系统会分叉Zygote进程,然后在新进程中加载​​并运行该应用程序的代码。这种方法允许分配给框架代码和资源的内存在所有应用程序进程之间共享。

    1.1. ART堆分类
      ART堆大概分为四个空间:Continuous Space(Image Space, Zgote Space, Alloction Space)和Discontinuous Space(Large Object Space)。Image Space和Zgote Space是进程间共享的,而Allocation Space则是每个应用进程独占的。
      1.1.1 Image Space:
      Image Space空间就包含了那些需要预加载的系统类对象(地址是固定的,保存在system@framework@boot.art@classes.dex文件开头的一个Image Header中)。需要预加载的类对象是在生成system@framework@boot.art@classes.oat这个OAT文件的时候创建并且保存在文件system@framework@boot.art@classes.dex中,以后只要系统启动类路径中的DEX文件不发生变化(即不发生更新升级),那么以后每次系统启动只需要将文件system@framework@boot.art@classes.dex直接映射到内存即可,省去了创建各个类对象的时间。(对于Dalvik虚拟机,每次系统启动时,都需要为那些预加载的类创建类对象。ART第一次启动时会比较慢,但是以后启动实际上会更快。)
      1.1.2 Zgote Space和Allocation Space:
      Zygote Space在Zygote进程和应用程序进程之间共享的。Allocation Space是每个应用进程独占的。Zygote进程一开始只有一个Image Space和一个Zygote Space。在Zygote进程fork第一个子进程之前,就会把Zygote Space一分为二,原来的已经被使用的那部分堆还叫Zygote Space,而未使用的那部分堆就叫Allocation Space。以后的对象都在Allocation Space上分配。虽然Image Space和Zygote Space都是在Zygote进程和应用程序进程之间进行共享,但是前者的对象只创建一次,而后者的对象需要在系统每次启动时根据运行情况都重新创建一遍。
      1.1.3 :验证 系统启动时,查看Settings.apk进程的地址空间
      首先是Image Space(0x60000000-0x60a94000),它映射的是system@framework@boot.art@classes.dex文件,固定映射在改地址上。
      然后是system@framework@boot.art@classes.oat文件(0x60a94000-0x64591000)。
      再接下来就是Zygote Space(0x64591000-0x646e7000)和Allocation Space(0x646e7000-0x6754e000)。
    1.2. ART对象分配
      Zygote Space在还没有划分出Allocation Space之前,就在Zygote Space上分配,而当Zygote Space划分出Allocation Space之后,就只能在Allocation Space上分配。
    1.3. ART堆GC
      在Dalvik虚拟机上,垃圾回收会造成两次停顿,第一次需要34毫秒,第二次需要56毫秒,虽然两次停顿累计也只有约10毫秒的时间,但是即便这样也是不能接受的。因为对于60FPS的渲染要求来说,每秒钟需要更新60次画面,那么留给每一帧的时间最多也就只有16毫秒。如果垃圾回收就造成的10毫秒的停顿,那么就必然造成丢帧卡顿的现象。ART主要使用标记-清理算法。
      Mark Sweep、Partial Mark Sweep和Sticky Mark Sweep三种回收力度不同的垃圾收集器。其中,Mark Sweep的垃圾回收力度最大,它会同时回收Zygote Space、Allocation Space和Large Object Space的垃圾;Partial Mark Sweep的垃圾回收力度居中,它只会同时回收Allocation Space和Large Object Space的垃圾;而Sticky Mark Sweep的垃圾回收力度最小,它只会回收Allocation Stack的垃圾,即上次GC以后分配出来的又不再使用了的对象。
      1.3.1 非并行GC的过程:
      a). GC初始化。
      b). 挂起所有的ART运行时线程。
      c). GC标记阶段。
      d). GC回收阶段。
      e). 恢复第b步挂起的ART运行时线程。
      f). GC结束阶段。
      1.3.2 并行GC的过程:
      a). GC初始化阶段。
      b). 获取用于访问Java堆的锁。
      c). GC并行标记阶段。
      d). 释放用于访问Java堆的锁。
      e). 挂起所有的ART运行时线程。
      f). 处理在GC并行标记阶段被修改的对象。。
      g). 恢复第e步挂起的ART运行时线程。
      h). 重复第e到第g步,直到所有在GC并行阶段被修改的对象都处理完成。
      i). 获取用于访问Java堆的锁。
      j). GC回收阶段。
      k). 释放用于访问Java堆的锁。
      l). GC结束阶段。

三. HotSpot VS ART

  XXXXXX

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值