Android系统分析之JVM/DVM、垃圾回收机制与类加载器

1 Java虚拟机

1.1 JVM(Java Virtual Machine,Java虚拟机)

  JVM的中文名称叫Java虚拟机,它是由软件技术模拟出计算机运行的一个虚拟的计算机。JVM也充当着一个翻译官的角色,我们编写出的Java程序,是不能够被操作系统所直接识别的,由JVM负责把程序翻译给系统“听”,告诉它我们的程序需要做什么操作。
  JVM在每个操作系统中有其对应的Java解释器,解释器会将Java程序经过编译后产生的.Class文件解释成特定的机器码,被操作系统所识别,实现一次编译到处运行

1.2 JVM的内存结构

Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第1张图片
图片来源于:Java JVM 运行机制及基本原理

(1)类加载系统(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中
(2)内存空间(也叫运行时数据区):是在JVM运行的时候所分配的内存区,运行时内存区主要可以划分为5个区域;
(3)执行引擎:负责执行class文件中包含的指令
(4)本地库接口:主要是调用C或C++实现的本地方法及返回结果

1.3 JVM的生命周期

  JVM在Java程序开始执行的时候,它才运行,程序结束的时它就停止。一个Java程序会开启一个JVM进程,如果一台机器上运行三个程序,那么就会有三个运行中的JVM进程。
  JVM中的线程分为两种:守护线程和普通线程。*守护线程是:JVM自己使用的线程,比如垃圾回收(GC)普通线程是:Java程序的线程*,只要JVM中有普通线程在执行,那么JVM就不会停止。权限足够的话,可以调用exit()方法终止程序。

2 JVM内存管理

  JVM的内存管理就是:内存对象的分配和释放问题,程序员需要为每个对象申请内存空间 (基本类型除外),对象的释放是由GC决定和执行的。
  这种收支两条线的方法确实简化了程序员的工作,但也加重了JVM的工作,这也是Java程序运行速度较慢的原因之一。因为GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

2.1 JVM内存分配

2.1.1 先理解持有引用的含义是什么?

public class B {
       A a = null//此a就是B类中对A的引用(或者说:B类对象持有A对象的引用)
       a = new A(); //用此a引用创建了A类的实例
}

2.1.2 内存分配类型

  • 方法区(静态存储区)内存在程序编译时就分配好了,这块内存在程序整个运行期间都一直存在。它主要存放静态数据和一些常量
  • 栈区方法体内的局部变量都在栈上创建,生命周期随方法而结束。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 堆区:也叫动态内存,通常使用new来申请分配一个内存。包括:①全局成员变量全部存储在堆中(包括基本数据类型,对象引用及引用的对象实体),因为他们属于类,类对象最终还是要被new出来的;②局部变量创建的对象存储于堆中。GC会根据内存的使用情况,对堆内存里的垃圾内存进行回收。
  • 本地方法栈:专门为native方法服务的,例如:C、C++方法。
  • 程序计数器(PC Register):保存当前线程执行的内存地址。由于JVM程序是多线程执行的,所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录中断的地方,可见程序计数器也是线程私有的。

2.1.3 栈与堆的区别

public class Main{
    int a = 1;//堆中
    Student s = new Student();//堆中    堆中
    public void XXX1(){
        int b = 1;//栈中
        Student s = new Student();//栈中    堆中
    }
    public void XXX2(){
        String s1 = new String("myString");//栈中    堆中
        String s2 = "myString";//栈中    方法区
    }
}

Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第2张图片

2.1.4 结论

(1)局部变量的基本数据类型和引用存储于栈中,引用创建的对象存储于堆中——因为它们属于方法中的变量,生命周期随方法而结束。
(2)全局变量全部存储在堆中(包括基本数据类型,引用,和引用创建的对象)——因为它们属于类,类对象终究是要被new出来使用的。
(3)我们所说的内存泄露,只针对堆内存,它们存放的就是引用指向的对象实体。

2.2 JVM内存释放

  释放对象的根本原则就是:对象不会再被使用
  ①给对象赋予了空值null,之后再没有调用过;
  ②另一个是给对象赋予了新值,这样重新分配了内存空间。
  
  JVM通过GC机制(内存垃圾回收机制)来释放回收堆和方法区中的内存,这个过程是自动执行的。GC会从根节点(GC Roots)开始对堆内存进行遍历,到最后,没有直接或者间接引用到根节点的就是需要回收的垃圾,会被GC回收掉。GC主要完成3件事:①确定哪些内存需要回收;②确定什么时候需要执行GC;③如何执行GC。
   

2.2.1 更好理解GC的工作原理

  为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外每个线程对象可以作为一个图的起始顶点
  例如:大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达,那么我们认为这个(这些)对象不再被引用,可以被GC回收。
Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第3张图片
图例演示–3分钟了解:Java垃圾收集器GC 如何确定哪些是“垃圾”?
 

2.2.2 GC Root对象有哪些

(1)虚拟机栈中的对象引用(引用是在栈帧中的本地变量表中的),真正的对象在堆中;
(2)方法区中的类静态对象引用
(3)方法区中的常量池对象引用
(4)本地方法栈中的JNI的对象引用

2.2.3 GC工作原理(Garbage Collection)

2.2.3.1 垃圾收集算法

(1)引用计数法:使用计数器进行内存管理。被引用1次,计数器加1,没有被引用的时候,则回收。但是引用计数法无法解决对象之前相互引用的问题,因此已经废弃。
(2)可达性算法(根搜索算法):有向图的方式进行内存管理。通过GC ROOT对象开始搜索,不可达的对象则回收。这时候可以提到引用的类型,主要用得最多就是强引用和弱引用。当存在强引用的时候,内存不足宁愿抛出OOM也不会回收;但是是弱引用的话,就有可能会被回收,这样就防止了内存泄漏。

2.2.3.2 垃圾回收算法

(1)标记-清除算法:搜索,发现没有引用的对象,直接回收,但是会导致内存碎片过多。
Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第4张图片
(2)复制算法:搜索,扫描没有引用的对象。开辟新的内存空间,将存活的对象复制到新的内存,旧的内存直接清除。由于需要多次交换内存空间,因此在对象数量比较少的时候效率比较高。
Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第5张图片
(3)标记-整理算法:在标记-清除算法的基础上,清除掉不存活的对象之后,把后面的存活对象搬移过来,似的内存连续,解决了内存碎片的问题。
Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第6张图片
(4)三种算法是混合使用的,不同情况,例如对象数量不同,采用不用的算法,已达到最大的效率。

2.2.4 触发垃圾回收方式

(1)GC_CONCURRENT:当我们应用程序的堆内存快要满的时候,系统会自动触发GC操作来释放内存
(2)GC_FOR_MALLOC:当我们的应用程序需要分配更多内存,可是现有内存已经不足的时候,系统会进行GC操作来释放内存
(3)GC_HPROF_DUMP_HEAP:当生成Hprof文件的时候,系统会进行GC操作
(4)GC_EXPLICIT:主动通知系统去进行GC操作,比如调用System.gc()方法来通知系统。或者在Android Monitor中,通过工具按钮告诉系统进行GC操作的。

3 类加载器(需要补充)

3.1 JVM的类加载器

  Android的类加载器跟原生的类加载器不一样,但是都大同小异:
Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第7张图片

3.2 类加载流程

Android系统分析之JVM/DVM、垃圾回收机制与类加载器_第8张图片

3.3 类的加载过程

3.3.1 分析对象引用与对象

public class Demo{  
    public Demo{}  
}  
Demo demo = new Demo();
//可以写成
Demo demo;//创建对象引用  
demo=/*将对象引用指向对象*/new Demo();//创建对象

(1)右边的“new Demo”,创建一个Demo对象,存储在堆内存中。
(2)末尾的()意味着:在对象创建后,立即调用Demo类的构造函数,对对象进行初始化。
(3)左边的“Demo demo”声明了Demo类引用变量,存储在栈内存中。
(4)“=”操作符使对象引用指向刚创建的Demo对象。
这里写图片描述
图片来源于:java–对象引用与对象的区别

3.3.2 Person person = new Person()为例进行说明类加载过程

1.首先虚拟机读取指定的路径下的Person.class文件,并加载至内存(如果该对象有直接父类则会先加载父类)—-方法区
2.按顺序执行父类的static代码块和static变量,再执行子类的static代码块和static变量 —-方法区
3.创建Person对象,在堆内存开辟空间分配堆内存地址 —-堆中
4.将父类对象的属性和代码块默认初始化(int类型为0,String类型为null);对父类对应的构造函数进行初始化 —-栈中
5.将子类对象的属性和代码块默认初始化(int类型为0,String类型为null);对子类对应的构造函数进行初始化 —-栈中
6.进行子类构造函数的特定初始化例如声明赋值变量(这种情况较少) —-栈中
7.声明一个person对象引用—-栈中
8.初始化完毕,栈中的person对象引用指向堆内存中Person对象。
参考链接:Person p=new Person()的感悟

4 Dalvik VM(DVM)

4.1 DVM

  Dalvik是Google公司自己设计用于Android平台的Java虚拟机。dex格式是专为Dalvik应用设计的一种压缩格式,适合于内存和处理器速度有限的系统Dalvik允许同时运行多个虚拟机的实例,并且每一个应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭
  (1)Dalvik指令集是基于寄存器的架构dex字节码更适合于内存和处理器速度有限的系统允许同时运行多个虚拟机的实例
  (2)而JVM是基于栈的。执行的是class文件

4.2 ART虚拟机是DVM的进化版本

  2014年6月谷歌I/O大会,Android L 改动幅度较大,Google将直接删除Dalvik,代替它的是传闻已久的ART。
  (1)在Dalvik下,应用每次运行都需要通过即时编译器(JIT)将字节码转换为机器码,即每次都要编译加运行,这虽然会使安装过程比较快,但是会拖慢应用以后每次启动的效率
  而在ART 环境中,应用在第一次安装的时候,字节码就会预编译(AOT)成机器码,虽然设备和应用的安装会变慢,但是以后每次启动执行的时候,都可以直接运行,因此运行效率会提高
  (2)ART占用空间比Dalvik大(字节码变为机器码之后,可能会增加10%-20%),这也是著名的“空间换时间大法”。
  (3)预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了 CPU 的使用频率,降低了能耗。

5 参考链接

虚拟机

类加载

Android类加载器ClassLoader

学习Java的内存分配机制和内存泄漏问题

JVM内存管理及GC机制

你可能感兴趣的:(Android系统分析)