0%

jvm虚拟机

JVM架构

jvm有几个区域:方法区、堆、Java栈、程序计数器、本地方法栈

  1. 方法区
    方法区被所有线程共享。存放 类信息、类的字段信息和方法信息、常量、static类变量

  2. Java栈
    Java栈线程之间不共享。
    Java栈是由很多的栈帧(stack frame)或者说帧(frame)组成的,一个栈帧包含一个Java方法调用状态。用来存放方法的局部变量、被调用时传进来的参数、返回值 以及运算的中间结果等等。

  3. Native栈
    用来存放Native方法的调用状态


  4. 用于存放对象。 Java程序在运行时创建的所有类实例或数组(数组在Java虚拟机中是一个真正的对象)都放在同一个堆中。由于Java虚拟机实例只有一个堆空间,所以所有线程都将共享这个堆。

  5. 程序计数器(PC寄存器)
    每一个线程有一个程序计数器。当线程执行某个Java方法时,程序计数器的值总是下一条被执行指令的地址。

  6. 类加载子系统
    读取class文件,加载Java类到内存。

基于栈的jvm 和 基于寄存器的jvm

在虚拟机栈的每一个栈帧中(即每一个方法调用中),存在一个「操作数栈」,用于存放运算的临时数据。
基于栈 和 基于寄存器 的区别在于这个「操作数栈」

  • 基于栈
    对于基于栈的jvm,这个「操作数栈」的区域当然是一个栈,方法体运行过程中生成的临时数据,会压入栈中,需要的时候再从栈中弹出。
    实现比较简单,只是单纯的压栈出栈
    对于每一条字节码指令,无需指定操作数地址,所以单个指令长度较短
    因为要频繁的压栈出栈,所以字节码指令会增加
  • 基于寄存器
    对于基于寄存器的jvm,这个「操作数栈】的区域变成了多个虚拟寄存器,方法体运行过程中的临时数据会存放到这些寄存器中。
    避免了频繁的压栈出栈操作,所以字节码指令数量更少
    因为要明确指定操作数地址,所以字节码单个指令长度会翻倍

为什么 dalvik 和 art 要基于寄存器

从上面的分析看,很明显,基于寄存器的jvm运行更快!

类加载器

类加载器负责动态加载Java类到Java虚拟机的内存空间中。

  • 双亲委托机制
    “双亲委托机制”是指先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类 路径中查找并装载目标类。这一点是从安全角度考虑的

GC垃圾回收机制

如何判断对象是否可以被回收

  • 引用计数法
  • 根搜索算法:
    • 从GC root出发查找引用链,无法触及到的对象可以被回收。
    • GC-root包括:虚拟机栈中引用的对象、类静态属性引用的对象、常量引用的对象

强引用、弱引用、软引用、虚引用

GC算法

jvm将对象分为三个区域, 新生代(Young Generation) 、 老年代(Old Generation) 和 持久代(Permanent Generation)。

  • 持久代:
    • 存放java类的类信息,存放静态文件,如final常量,static常量,常量池等。几乎不回收
  • 新生代:
    • 新生代使用拷贝算法:新生代中存在一个Eden区和两个Survivor区,其中一个Survivor始终为空。 eden区gc时,存活的对象会复制到其中一个survivor区。 Survivor区gc时,会拷贝到另一个空闲的Survivor区。 当对象多次gc存活一定次数之后,会移动到老年代
    • jvm中 eden区和Survivor区的大小为8:1
  • 老年代:
    • 使用标记整理算法。标记需要清除对象,将存活的对象往内存的一端移动,再将需要清除要回收的对象。这样的算法可以避免内存碎片的产生
    • 大对象会一生成就会直接放到老年代。新生代容量较小,避免新生代频繁gc

GC Root

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

本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

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

方法区中常量引用的对象

jar文件和dex文件的差异

一个jar文件包含多个class文件,不同的class文件之间没有关联。 如果有相同的常量信息等数据,会产生冗余,增大文件大小。
一个dex文件包括索引区和数据区,多个类共用一个索引区。这样相同的常量信息等可以避免重复冗余,可以大大减小文件大小。 同时,因为只有一个文件,也可以减少文件io的开销。

对象一定是在堆上的吗?

不一定,取决于不同jvm的设计优化。

在虚拟机栈的调用中,jvm会对方法体力创建的对象做逃逸分析。 通过逃逸分析,可以分析出这个对象的引用是否只存在于方法体内部。
如果发现某个对象并没有逃逸到方法体之外的话,就可能对其进行优化。不会将对象分配到堆上,可以减少gc的消耗。
具体的方法是,把这个对象拆解成若干个其中包含的若干个局部变量来代替。这个过程就是标量替换