0%

第10章:Java虚拟机

需要注意的是,Android中的Dalvik 和 ART 并不属于Java 虚拟机。

当一个Java 文件经过Java编译器后会生成Class 文件,这个Class 文件会由 Java 虚拟机进行处理,Java 虚拟机与 Java 语言没有必然的联系,它只与特定的二进制文件:Class 文件有关。任何语言只要能编译成 Class 文件,都能被Java 虚拟机识别并执行。

Java虚拟机结构

按照Java虚拟机规范,抽象的Java 虚拟机如下图所示:

Java虚拟机结构

从图可以看出,Java虚拟机结构包括 运行时数据区域、执行引擎、本地库接口和本地方法库,其中类加载子系统并不属于Java 虚拟机的内部结构。下面针对这个图来介绍Android 开发需要掌握的Class 文件格式和运行时数据区域。

类的生命周期

类的生命周期如下图所示:

类的生命周期

类加载的各个阶段的工作:

  1. 加载: 查找并加载Class文件

  2. 连接: 包括验证、准备和解析

    • 验证:确保被导入类型的正确性
    • 准备:为类的**静态字段分配字段,并用默认值初始化这些字段
    • 解析:虚拟机将常量池内的符号引用替换为直接引用
  3. 初始化: 将类变量初始化为正确的初始值。

运行时数据区域

可取的几点:

  • 为了在线程切换后能恢复到正确的执行位置,每个线程都会有个独立的程序计数器。如果线程执行的方法是Native方法,则程序计数器的值为空(Undefined),否则,保存正在执行的字节码指令地址。
  • 可以选择在方法区不显示垃圾收集

其他的可以参考深入理解Java虚拟机的这篇文章即可。

对象的创建

当虚拟机接收到一个 new 指令时,它会做如下操作:

  1. 判断对象对应的类是否加载、链接、以及初始化。
  2. 为对象分配内存:如果内存规整,则只需要将指针指示器向空闲的内存移动若干距离即可;若不规整,则需要由虚拟机维护一个列表记录哪些内存是可用的。
  3. 处理并发安全问题,有两种方式解决:
    • 对分配内存空间的动作做同步处理,比如采用 CAS 方式配合失败重试
    • 为每个线程在Java堆中预先分配一小块内存,只有这块内存被使用完后,才需要同步的方式分配新的内存。
  4. 初始化分配到的内存空间:将分配到的内存,出对象头以外都初始化为零值。
  5. 设置对象的对象头:将对象所属的类、对象的hashcode、以及GC分代年龄等存放在对象头。
  6. 执行init方法进行初始化。

垃圾标记算法

GC 主要做两个工作:一是内存的划分和分配,二是对垃圾进行回收。关于内存的划分和分配,目前Java虚拟机内存的划分是依赖于GC 的,比如现在的GC 都采用分代收集算法来回收垃圾的。Java 堆作为GC 的主要管理区域,被细分为新生代和老年代。

查看gc日志的时候,[GC(Systemt.gc()) 和 [Full GC(Systemt.gc() 用于说明此次垃圾收集的停顿类型(而非区分新生代和老年代),后者表示此次GC发生了 STW(Stop the World,只有GC线程在运行,其他线程都暂停)。

其余主要内容,参考以前写的这篇文章

垃圾收集算法

标记-清除算法

首先标记可以被回收的对象,之后回收被标记的对象所占的空间。它主要有两大缺点:一个是标记和清除两个过程的效率都不高,另一个就是,容易产生大量的内存碎片,碎片太多可能会导致没有足够的连续内存分配给较大的对象,从而提前触发新的GC。

复制算法

把空间分为两个相等部分,每次只使用其中一部分,垃圾收集时,把存货的对象复制到另一部分,再把当前部分全部清理掉,这样不需要考虑碎片化问题,缺点就是可用内存变为了原来的一半。

标记-压缩算法

新生代一半存活的对象比较少,可以使用复制算法,但是老年代不能选择复制算法了,因为老年代的对象存活率会较高,这样会有很多复制操作,导致效率降低。它的主要方式是,在标记可回收的对象后,将所有还存活的对象压缩到内存的一端,使它们紧凑地排列在一起,然后对边界外的内存进行回收。标记-压缩算法解决了标记-清除算法容易产生大量内存碎片的问题,但是它的效率仍然是很低的。

分代收集

垃圾收集的类型分为两种,分别是:

  • Minor Collection: 新生代垃圾收集
  • Full Collection: 老年代垃圾收集,也称 Major Collection, Full Collection 通常会伴随至少一次的 Minor Collection。 Full Collection 收集频率低,耗时长。
谢谢你的鼓励