Android -- JVM,DVM,ART虚拟机

Java虚拟机–JVM,类加载器,双亲委派

https://www.iteye.com/blog/welcome66-2216572

1、JVM(Java Virtual Machine),java虚拟机

JVM就是虚拟出来的计算机,有自己完善的架构,处理器,堆栈,寄存器,指令系统。使用jvm就是为了支持与操作系统无关,java跨平台的原理,因为java代码都在这上运行,.java文件通过javac命令编译后生成.class字节码文件,JVM的java解释器负责把.class字节码文件转化为特定的机器码文件运行。

1.进程级别,守护线程和非守护线程(用户线程)
守护线程:后台线程,为前台线程提供便利服务,比如GC线程。不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。
非守护线程:前台线程

设置守护线程:public final void setDaemon(boolean on)
判断守护线程:public final boolean isDaemon()

2.生命周期:
启动:启动java是开启,起点是public static void main
运行:main起点,两种线程:守护线程(JVM),非守护线程(JAVA),java也可以创建自己的守护线程
消亡:程序终止则退出,也可以用System.exit或Runtime类来退出
Android -- JVM,DVM,ART虚拟机_第1张图片
3.体系结构
类装载器(ClassLoader)–用来装载.class文件
执行引擎(执行字节码,执行本地文件)
运行时数据区(方法区,堆,java栈,PC寄存器,本地方法栈)

2. ClassLoader类加载器

https://blog.csdn.net/poorcoder_/article/details/80258725
1 JVM整个类加载过程的步骤:
①.装载:二进制字节码加载到JVM,通过三个标识:类名,类所在的包名和ClassLoader实例ID
②.连接:1.对二进制字节码的格式进行校验初始化装载类中的静态变量以及解析类中调用的接口、类; 2.完成校验后,初始化静态变量赋默认值;3.最后对所用的属性,方法进行验证,确保存在,具有应有的权限。(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值)
③.初始化
执行静态代码块,构造器代码,静态属性初始化,类变量(static)会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
四纵情况会被触发执行:new,反射,子类调用初始化,jvm指定初始化类
4.2. JVM整个类加载顺序:
①java类装载器包括:启动类装载器(JVM的一部分) 和 用户自定义装载器(JAVA的一部分,必须是ClassLoader的子类)
②装载顺序:JVM启动时,由bootstrap 向User-Defined方向加载类;应用进行ClassLoader时,由User-Defined向Bootstrap方向查找并加载类

3 双亲委派

背景:判定两个类是否为同一个类,判定原则是是否由一个类加载器加载;所以导致多个类加载器可能会导致有多个不同的类,比如Object类如果有其他类加载器加载,就会导致有多个Object类,无法保证java的行为执行,会混乱。所以引入双亲委派:加载类的时候,先去让父类加载(顶层父类是启动类加载器,所以顶层都会走加载的方法)。如果父类不能加载,则传给子类加载,父子关系不是继承,而是组合(Composition)。带有优先级。
Java中的类加载器

  • 启动类加载类 Bootstrap,顶级加载器,加载java核心API,用navive code(特定于处理器的机器码的代码)创建
  • 扩展类加载器 Extension ,被java变量所制定的类库,开发者可以直接使用
  • 应用程序类加载器 Application ,加载类路径ClassPath上指定的类库,开发者可以直接使用,如果没有自定义类加载器,那么这就是默认的。
    在这里插入图片描述
    在这里插入图片描述
    ClassLoader重要方法:
  • loadClass(),ClassLoader自己实现的,JDK1.2后用户可以直接调用,代码逻辑就是双亲委派的体现,先去缓存找,找得到直接返回,找不到去父加载器,父亲没有去启动加载器,最后都没有去findClass
  • findClass,直接抛出ClassNotFound,所以自己可以重写,来让其实现类的加载,这样也就保证自定义的加载器符合双亲委派模型。
  • defineClass,byte字节流解析成JVM能够识别的Class对象(ClassLoader中有这个方法逻辑),也可以通过其他方式,比如网络接收字节码转化为byte,再转化为Class,通常匹配findClass使用,自定义的时候直接覆盖findClass,然后去取得字节流,在使用defineClass生成Class对象。
  • resolveClass() ,loadClass中被调用,连接指定java类,主要是对字节码进行验证,

类加载器命名空间
不同的类加载器在虚拟机中处于不同的命名空间下,他们不可见
但是,例子:自己写的A类有AppClassLoader加载器加载,List由Bootstrap加载器加载,A访问List,先去自己的AppClassLoader,找不到,再去找父类,一直到顶层Bootstrap 找到List类
Android -- JVM,DVM,ART虚拟机_第2张图片
Android中的类加载器

Android的ClassLoader分为:BootClasssLoader、PathClassLoader和DexClassLoader三种。其中:

  • BootClassLoader:是预加载一些常用类,动态加载的。
  • PathClassLoader:加载系统类和已安装的APK。
  • DexClassLoader:支持加载外部的APK、Jar或dex文件。(所有的插件化方案都是使用它来加载插件APK中的.class文件,也是动态加载的核心依据!)
  • InMemoryDexClassLoader:继承自BaseDexClassLoader,在Android 8.0后加入,加载内存中的dex文件

4 自定义ClassLoader

  • 背景,可以对class文件进行加密和解密。
  • 实现方式
    重写findClass方法,

5. java的堆内存情况,性能分析工具

5.1 java内存模型

在这里插入图片描述

  • 主内存就是硬件内存,本地内存是抽象内存:缓存,写缓存区,寄存器

  • volatile修饰之后,本地内存可以达到互相可见,原理是会把本地内存刷新到主内存上。

  • java内存同步的八种操作
    作用于主内存(Main Memory):
    lock,unlock,read(主–》工作,之后进行load),write(store之前的操作,工作–》主)
    作用于工作内存:
    load(read之后的操作,主–》工作的变量副本),use(工作–》执行),assign(执行–》工作变量),store(工作–》主,之后进行write)

  • 规则
    顺序操作,必须全顺序
    不允许(read,load )或者 (Store,Write)单独出现
    不允许线程丢弃assign,即变量改变后必须把数据同步到主内存
    没有进行assign()操作,不允许同步到主内存
    允许单一线程多lock,但需要多unlock,成对出现

在这里插入图片描述

6. Java垃圾回收(GC)机制详解

https://blog.csdn.net/woainimax/article/details/75560973?ops_request_misc=%7B%22request%5Fid%22%3A%22158322800019724845059314%22%2C%22scm%22%3A%2220140713.130056874…%22%7D&request_id=158322800019724845059314&biz_id=0&utm_source=distribute.pc_search_result.none-task

  • 原理:没有引用的对象,内存将变为垃圾
  • 对象的回收:1.没有引用,引用为null,引用对象转移,2. 弱引用
  • 方法区的垃圾回收:1. 废弃常量。2. 无用的类。
6.1 强制系统垃圾回收方式
  • System.gc();
  • Runtime.getRuntime().gc();
  • 备注:并不是立刻回收,也会做一些算法进行加权,使垃圾回收容易发生,提早发生,回收较多
6.2 finalize

是方法名,在Object类中定义,所以所有的类都继承了他
在垃圾回收机制清除内存前做必要的清理工作
只能运行一次
用于一些不容易控制,而且非常重要的资源释放
程序本身释放为主,finalize函数释放为辅,双保险管理

6.3 回收root根节点,和判定回收(jvm怎么确定哪些对象应该进行回收)
  • Java中可作为gc root 的对象有哪些?
    1 、 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    2、 本地方法栈中JNI(即一般说的native方法)引用的对象。
    3、 方法区中的静态变量和常量引用的对象。
    从程序的角度来说就是,找到一段程序运行的整个过程中始终会存活的对象,这些对象的特点是始终会存活,不会死亡。即一些静态变量 和常量所引用的对象等。
  • 判定方法
  1. 引用计数,来判断一个对象是否可以被回收,但是循环指向就不会回收
  2. 可达性分析,关系图,节点,引用链,判定是否可达
    在这里插入图片描述
6.4 jvm会在什么时候进行回收
  1. 会在cpu空闲的时候自动进行回收
  2. 在堆内存存储满了之后
  3. 主动调用System.gc()后尝试进行回收
6.5 垃圾回收算法(如何回收)–四个算法
  1. Mark-Sweep(标记-清除)算法:最基础,容易实现,会产生大量的不连续内存
  2. Copying(复制)算法:平均分为两块内存,只使用一块,然后用完之后把依旧存活的对象转移到另一块,原来区域清理。特点,简单不会产生碎片,但是牺牲内存
  3. Mark-Compact(标记-整理)算法:在标记清除算法上添加了整理,使其没有内存碎片,
  4. Generational Collection(分代收集)算法:目前大部分JVM的垃圾收集器采用的算法,核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域,一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation)
  • Generational Collection(分代收集)算法详解
    新生代,频繁大量回收,通常使用时间占优的GC,需要复制的操作较少,所以Copying算法。
    老年代,少量被回收,通常使用善于处理大空间的GC,因为老年代的空间大,GC频率低引用,使用Mark-Compact算法。

  • 引用分为强引用、软引用、弱引用、虚引用4种,这4种引用强度依次减弱。
    1、强引用
    代码中普遍存在的类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
    2、软引用
    描述有些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围,进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。Java中的类SoftReference表示软引用。(网站缓存)
    3、弱引用
    描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。Java中的类WeakReference表示弱引用。(handler内存泄露,bitmap缓存)
    4、虚引用
    这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象,和其生存时间完全没关系。Java中的类PhantomReference表示虚引用。

Android虚拟机4.4之前–DVM(Dalvik VM)

JVM标准执行的是.class的字节码(bytecode ),
而DVM执行的是其专有的(.dex)执行文件。
使用.dex格式原因:
去除掉.class的陈余信息,所有的.class整合到.dex文件中
减少了文件内存和I/O操作,提高查找速度
增加了对新的操作码的支持

Android虚拟机4.4之后–ART(Android Runtime)

ART 的机制与 Dalvik 不同。
在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率
而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。预编译,app启动和执行会更快
优点:

  • 1、系统性能的显著提升。
  • 2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。
  • 3、更长的电池续航能力。
  • 4、支持更低的硬件。
  • 5、垃圾回收优化
    Dalvik虚拟机每次垃圾回收包含两次停顿,共耗时10ms左右,ART每次垃圾回收只停顿一次,耗时在2ms左右。并行的垃圾回收策略。以及基于手机运行状态的回收策略。ART也会引入Compact collector来移动内存中的数据,从而减少内存碎片;并且通过杀掉老的应用来获取更多内存。
    缺点:
  • 1.机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20%(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
  • 2.应用的安装时间会变长。

类替换,热修复

1.热修复

动态修复、更新App功能、行为等,也被称为动态更新。

热修复原理

(1)底层替换方案
从底层C的二进制来解决问题,这样做限制颇多,但时效性最好,加载轻快,立即见效;
直接在已加载类中替换掉原有方法,即在原来类基础上进行修改,因此无法实现增减原有类方法或字段,这样会破坏原有类的结构。
(2)类加载方案
从Java加载机制来解决问题,这样做时效性差,需要重新冷启动才能见效,但修复范围广,限制少;
在app重新启动后让Classloader加载新的类。因为当app运行到一半时,所需发生变更的类已经被加载过,而在Android上无法对一个类进行卸载操作,若不重启,原来的类还存储于虚拟机中,新类无法被加载。因此只有在下次重启时,在业务逻辑运行之前 抢先加载补丁中的新类,这样后续访问此类时,才会Resolve为新类,从而达到热修复的目的。

public class DexClassLoader extends BaseDexClassLoader {
     

    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
     
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

DexClassLoader类注释:用来加载包含dex的jar包或apk中的类,也可以执行于尚未安装到应用中的代码,因此它才是动态加载的核心!
DexClassLoader类中只有一个构造方法,4个参数含义分别是:

  • dexPath:指定要加载dex文件的路径;
  • optimizedDirectory:指定dex文件需要被写入的目录,一般是应用程序内部路径(不可以为null);
  • librarySearchPath:包含native库的目录列表(可能为null);
  • parent:父类加载器;

BaseDexClassLoader类 :类中只有一个成员变量DexPathList,其构造方法,
其中创建了DexPathList对象,传入了四个参数,分别是:

  • DexClassLoader:父类加载器本身;
  • dexPath:需要加载的dex文件路径;
  • 0librarySearchPath:包含native库的目录列表(可能为null);
  • optimizedDirectory: dex文件需要被写入的内部目录(可能为null);

如何通过DexClssLoader类加载实现热修复

1. 找到突破口——DexPathList

类中有一个重要的成员变量——Element类型数组,和两个重要的方法——构造函数和findClass:

  • 主要在构造函数中处理对Element类型数组赋值(遍历指定路径中的所有文件,将dex文件相关信息赋值到数组中);
  • 在findClass方法中遍历Element数组,找到对应类名的dex文件(Element类型的dexFile方法可转化为DexFile类型),调用本身native方法获取字节码文件返回。
    因此可以说所有加载的类都在Element数组中!
2. 思路

1.反射获取到dexElement,循环遍历dexElement数组,然后找到对应文件进行替换,比较麻烦
2.在遍历前加载指定的类,相当于拦截
原理:DexPathList类的findClass方法,发现内部循环的一个重点逻辑:在遍历dexElement数组时,若加载到的指定类class不为空时,直接return结束遍历,将class字节码返回!
Android -- JVM,DVM,ART虚拟机_第3张图片

3.具体实现方法

实现热修复功能思路:直接将已修复的dex(.class文件)放到dexElement数组中有bug类的dex前面!

你可能感兴趣的:(Java,Android)