Android JVM学习(扎实基础版)

发布于:2023-01-20 ⋅ 阅读:(12) ⋅ 点赞:(0) ⋅ 评论:(0)

简述

JVM 全称 Java Virtual Machine,它是一种规范。JVM 是一个虚拟化的操作系统,类似于 Linux 或者 Windows 的操作系统,只是它架在操作系统上,接收字节码,把字节码翻译成操作系统上的机器码且进行执行。

JVM基本结构

由下图可以清楚的看到JVM的内存空间分为3大部分:

  • 堆内存

  • 方法区

  • 栈内存

虚拟机栈

虚拟机栈是先进后出(FILO)的数据结构,是每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,动态链接,方法出口 等信息,然后放入栈。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。

栈帧

在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。

栈帧一般包含四个区域:(局部变量表、操作数栈、动态连接、返回地址)

局部变量表:

用于存放我们的局部变量的(方法中的变量),主要存放我们的 Java 的八大基础数据类型,如果是局部的一些对象,只需要存放它的一个引用地址即可。

操作数栈

存放 java 方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的元素可以是任意的 java 数据类型。

动态连接

每个栈帧中都包含一个在常量池中对当前方法的引用, 目的是支持方法调用过程的动态连接。

返回地址

正常退出,即正常执行到任何方法的返回字节码指令,如 RETURN、IRETURN、ARETURN 等、

异常退出

我们来写一个简单的java代码查看汇编指令

public class Stack {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = a + b;
        System.out.println(c);
    }
}

执行 javac 生成class,再执行 javap -v Stack.class

Classfile /C:/Users/Jack/Desktop/Stack.class
  Last modified 2021-11-24; size 406 bytes
  MD5 checksum 38b185e462a0e2aa0a06afdc67dfe12c
  Compiled from "Stack.java"
public class com.test.Stack
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // com/Stack
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Stack.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               com/Stack
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public com.test.Stack();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10 //将常量10压入操作数栈(int取值-128~127时使用bipush指令)
         2: istore_1         //将10从操作数栈存储到局部变量表第1个位置
         3: bipush        20 //将常量20压入操作数栈
         5: istore_2         //将20从操作数栈存储到局部变量表第2个位置
         6: iload_1          //加载局部变量第1个变量压入操作数栈
         7: iload_2          //加载局部变量第2个变量压入操作数栈
         8: iadd             //操作数栈中的前两个变量相加,并将结果压入操作数栈顶
         9: istore_3         //将相加的得出的结果30从操作数栈存储到局部变量表第3个位置
        10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: iload_3
        14: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        17: return           // 返回
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 10
        line 9: 17
}
SourceFile: "Stack.java"

程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,是线程私有的,各线程之间独立存储,互不影响。它可以看作是当前线程所执行的字节码的行号指示器。比如如下字节码内容,在每个字节码`前面都有一个数字,我们可以认为它就是程序计数器存储的内容。它主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。

方法区

方法区(Method Area)是可供各条线程共享的运行时内存区域。它存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。方法区是 JVM 对内存的“逻辑划分”,在 JDK1.7 及之前很多开发者都习惯将方法区称为“永久代”,是因为在 HotSpot 虚拟机中,设计人员使用了永久代来实现了 JVM 规范的方法区。在 JDK1.8 及以后使用了元空间来实现方法区。

本地方法栈

本地方法栈跟 Java 虚拟机栈的功能类似,它服务的对象是 native 方法。你甚至可以认为虚拟机栈和本地方法栈是同一个区域。虚拟机规范无强制规定,各版本虚拟机自由实现 ,HotSpot 直接把本地方法栈和虚拟机栈合二为一 。

运行时常量池

运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池(Constant_Pool)的运行时表示形式,它包括了若干种不同的常量:从编
译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池是方法区的一部分。

堆是 JVM 上最大的内存区域,我们创建的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。

对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。比如,把这个引用保存在虚拟机栈的局部变量表中。对于基本数据类型来说(byte、short、int、long、float、double、char),有两种情况。当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,都是在堆上分配。

直接内存

直接内存有一种更加科学的叫法,堆外内存。JVM 在运行时,会从操作系统申请大块的堆内存,进行数据的存储;同时还有虚拟机栈、本地方法栈和程序计数器,这块称之为栈区。操作系统剩余的内存也就是堆外内存。

文章主要浅析了JVM的原理解析。作为基础学习,Android开发路上肯定是要打好基础的。当然关于JVM知识还有其他深入学习。关注博主,每次都会带来不一样的《Android技术分享》。博主也整理了一些Android进阶的技术知识,我分享在这里哦!点击上方粗体可领取!


文末

Android的运行环境都是在ART虚拟机或者是Dalvik虚拟机上,为什么Android程序员需要学习JVM,理解JVM可以帮助我们更好的了解Java内存区域、对象的创建和内存分配、垃圾的回收以及常见的垃圾回收算法等等,然后将其运用到Android开发中,有助于处理app中的内存问题。