JVM学习之运行时数据区

运行时数据区

概述

在这里插入图片描述

内存

内存是非常重要的系统资源,是硬盘和CPU的中间桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请,分配,管理的策略,保证了JVM高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。

运行时数据区-详细图

在这里插入图片描述

方法区和堆区是随着JVM启动而创建,随着JVM关闭而回收,生命周期与JVM一致,一个Java进程内只有一个堆区,一个方法区,线程之间是共享使用这些区域的(Metaspace和CodeCache也是线程共享的)。本地方法栈,程序计数器和虚拟机栈都是线程私有的,每个线程都有自己的一份。一般来讲95%的垃圾回收都发生在堆区,5%的垃圾回收发生在方法区。JDK8以后的Metaspace使用直接内存,直接内存一般比较大,但不代表不会发生垃圾回收。

线程

线程是一个程序里的运行单元。JVM允许一个应用里面有多个线程并行执行。

在HotSpot JVM中,每个线程都与操作系统的本地线程映射。当一个Java线程准备好执行以后,此时一个操作系统本地线程也会创建。Java线程终止后,操作系统本地线程也会回收。

操作系统负责把所有的线程调度到某个可用的CPU上,一旦本地线程初始化成功它就会调用Java线程中的Run方法。Run()方法如果有抛出未捕获的异常,JVM的线程就终止了,操作系统此时会判断是否应该结束JVM进程。当此线程是最后一个非守护线程时,JVM就被停止了。

Hot Spot JVM运行的后台线程
  • 虚拟机线程:这种线程的操作需要JVM到达安全点才会出现,这些操作必须在不同的线程中发生的原因是他们都需要JVM到达安全点,这样堆才不会变化。这种线程的执行类包括“stop-the-world”的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
  • 周期任务线程:这种线程是时间周期事件的体现(比如中断)。它们一般用于周期性操作的调度执行。
  • GC线程:这种线程对在JVM不同种类的垃圾收集提供支持。
  • 编译线程:这种线程在运行时会将字节码编译成本地代码。
  • 信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理。

程序计数器(PC寄存器)

介绍

JVM的PC寄存器是对物理PC寄存器的抽象模拟,是软件层面的概念,主要存储指令相关的现场信息。CPU只有把数据加载到寄存器才能使用。PC寄存器是用来存储指向下一条指令的地址,也可以理解为即将要执行指令的代码。由执行引擎读取下一条指令。

在这里插入图片描述

  • 它是一块很小的内存空间,小到可以忽略不计,也是运行速度最快的存储区域。
  • 在JVM规范中,每个线程都有自己的程序计数器,是线程私有的,生命周期与线程一致。
  • 任何时间,一个线程只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址,如果在执行native方法,是未指定值(undefined)
  • 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个计数器来完成。
  • 字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
  • 是唯一一个在Java虚拟机规范中没有OOM的区域
寄存器的使用
public static void main(String[] args) {
  int a = 1;
  int b = 2;
  int c = a + b;
}

编译成字节码为:

0 iconst_1
1 istore_1
2 iconst_2
3 istore_2
4 iload_1
5 iload_2
6 iadd
7 istore_3
8 return

前面的那一串数字就是指令行号,就是PC寄存器中存储的数值。

在这里插入图片描述

两个常见问题
  1. 为什么使用PC寄存器记录地址?(PC寄存器有什么作用)

    因为CPU是不断的切换线程执行的,切换回来后CPU要知道接下来从哪一条指令继续执行。JVM使用PC寄存器来明确接下来应该执行什么指令。

  2. PC寄存器为什么设置为线程私有

    CPU能调度的最小单元是线程,为了能够准确的记录各个线程正在执行的指令,最好的办法就是每个线程都分配一个PC寄存器。

CPU时间片

CPU时间片即CPU分配给各个线程的执行时间,每个线程执行时被分配一个时间段,称为CPU时间片。

从宏观上看:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。

从微观上看:由于只有一个CPU,一次只能处理一个应用程序要求的一部分,如何处理比较公平?引入时间片,每个程序轮流执行。

虚拟机栈

概述

栈是运行时的单位,堆事存储时的单位。

Java虚拟机栈

每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的方法调用,是线程私有的。生命周期与线程一致。它保存方法的局部变量(8中基本变量类型,引用类型的地址),部分结果,并参与方法的调用和返回。

优点
  • 栈是一种快速有效的存储分配方式,访问速度仅次于程序计数器
  • JVM对栈的操作有2个:
    • 每个方法被调用,伴随着入栈(压栈)
    • 执行结束后出栈
  • 对于栈来说不存在垃圾回收问题

Java虚拟机规范规定了虚拟机栈可以是固定不变的,也可以是动态的。虚拟机栈大小设置参数 -Xss

栈的存储单位
  • 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存储的
  • 在当前线程正在运行的每个方法各自对应一个栈帧
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
栈运行原理
  • JVM对栈只有两个操作:入栈和出栈,遵循先进后出的原则
  • 在一条活动线程中,一个时间点上,只有一个活动的栈帧。即只有当前正在执行方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧。与当前栈帧相对应的方法就是当前方法,定义这个方法的类就是当前类
  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作
  • 如果该方法中调用了其它方法,对应新的栈帧会被创建出来,放在栈的顶端,成为新的当前栈帧。
  • 不同线程中所包含的栈帧不允许存在相互引用,即不可能在一个栈帧中引用另一个线程的栈帧
  • 如果当前方法调用了其它方法,方法返回之际,当前栈帧会传回执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使前一个栈帧成为新的当前栈帧
  • Java有两种返回函数的方法,一种是正常函数返回,使用returen指令,另一种是抛出异常。不管哪种方式,都会导致栈帧弹出。
栈帧内部结构
  • 局部变量表
  • 操作数栈(表达式栈)
  • 动态链接(指向运行时常量池的方法引用)
  • 方法返回地址(正常退出或异常退出的定义)

在这里插入图片描述

局部变量表
  • 局部变量表也被称为局部变量数据或本地变量表
  • 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包含基本数据类型,对象引用以及returenAddress类型
  • 由于局部变量表是建立在线程栈上,是线程私有数据,因此不存在数据安全问题
  • 局部变量表所需容量的大小是在编译期确定的,并保存在方法的Code属性maximum local variables数据项中。在方法运行期间是不会改变局部变量表大小的。
  • 方法调用的次数由栈的大小决定,一般来说,栈越大,方法嵌套的次数就越多。局部变量表中变量多,会导致栈帧增大,在一定的栈空间下,嵌套的次数变少。
  • 局部变量表中的变量只在当前调用方法中生效,方法返回或异常退出,栈帧被销毁,局部变量也随之被销毁

随便写一些代码如下:

 public static void main(String[] args) {
        int a = 1;
        String str = "我是main函数";
        test1(678);
    }

    public static void test1(int a){
        int b = 2;
        int c = a + b;
        Date now = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(simpleDateFormat.format(now));
        short paramShort = 126;
        byte paramByte = 1;
        int re =test2(paramShort,1500,123456789L,false,paramByte,2.34f,
                1.174938493,'x',"我是test1,我调用test2");
        System.out.println(re);
    }

    public static int test2(short paramShor,int paramInt,long paramLong,boolean paramBoolean,
                            byte paramByte,float paramFloat,double paramDouble,char paramChar,
                            String paramStr){
        short localShot = 0;
        int localInt = 1;
        long localLong = 2147483647L;
        boolean localBoolean = true;
        byte localByte = 8;
        float localFloat = 1.23f;
        double localDouble = 3.14;
        char localChar = 'A';
        Integer integer = Integer.valueOf("1000");
        return localInt;
    }

使用javap -v反编译后如下(省略了部分不关注的信息):

//... 省略类信息 常量池 其他方法信息等
  public static int test2(short, int, long, boolean, byte, float, double, char, java.lang.String);
    descriptor: (SIJZBFDCLjava/lang/String;)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=22, args_size=9
         0: iconst_0
         1: istore        11
         3: iconst_1
         4: istore        12
         6: ldc2_w        #20                 // long 2147483647l
         9: lstore        13
        11: iconst_1
        12: istore        15
        14: bipush        8
        16: istore        16
        18: ldc           #22                 // float 1.23f
        20: fstore        17
        22: ldc2_w        #23                 // double 3.14d
        25: dstore        18
        27: bipush        65
        29: istore        20
        31: ldc           #25                 // String 1000
        33: invokestatic  #26                 // Method java/lang/Integer.valueOf:(Ljava/lang/String;)Ljava/lang/Integer;
        36: astore        21
        38: iload         12
        40: ireturn
      LineNumberTable:
        line 29: 0
        line 30: 3
        line 31: 6
        line 32: 11
        line 33: 14
        line 34: 18
        line 35: 22
        line 36: 27
        line 37: 31
        line 38: 38
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      41     0 paramShort   S
            0      41     1 paramInt   I
            0      41     2 paramLong   J
            0      41     4 paramBoolean   Z
            0      41     5 paramByte   B
            0      41     6 paramFloat   F
            0      41     7 paramDouble   D
            0      41     9 paramChar   C
            0      41    10 paramStr   Ljava/lang/String;
            3      38    11 localShort   S
            6      35    12 localInt   I
           11      30    13 localLong   J
           14      27    15 localBoolean   Z
           18      23    16 localByte   B
           22      19    17 localFloat   F
           27      14    18 localDouble   D
           31      10    20 localChar   C
           38       3    21 integer   Ljava/lang/Integer;
    //...省略代码
}
SourceFile: "Main.java"

我们那典型的test2这个方法来分析,可以看到Code下面的locals=22说明局部变量有22个,args_size=9代表参数有9个。接下来LocalVariableTable就是局部变量表。StartLength规定了当前变量作用域,Start为指令开始位置,Length代表从Start开始在Length内可以用,Slot可以理解为下标

Start  Length  Slot  Name   Signature
  0      41     0 paramShort   S //参数paramShort 类型为short
  0      41     1 paramInt   I
  0      41     2 paramLong   J
  0      41     4 paramBoolean   Z
  0      41     5 paramByte   B
  0      41     6 paramFloat   F
  0      41     7 paramDouble   D
  0      41     9 paramChar   C
  0      41    10 paramStr   Ljava/lang/String; //参数paramStr 类型为java/lang/String 最前面的L代表是引用类型
3      38    11 localShort   S
  6      35    12 localInt   I
  11      30    13 localLong   J
  14      27    15 localBoolean   Z
  18      23    16 localByte   B
  22      19    17 localFloat   F
  27      14    18 localDouble   D
  31      10    20 localChar   C
  38       3    21 integer   Ljava/lang/Integer; //局部变量integer 类型为java/lang/Integer 是引用类型

另外,LineNumberTable这个记录了字节码行号和代码行号的对应关系,line后面是指令行号,:后面是代码行号。

关于Slot的理解
  • 参数总是从index 0开始,长度-1结束
  • 局部变量表的基本单位就是slot(槽)
  • 局部变量表存放编译期就已知的8种数据类型,引用类型(reference)和retureAddress类型
  • 局部变量表中,32位以内(含32位)的类型,64位的类型占2个slot
    • byteshortchar在存储前被转换为intboolean也被转换成int,0代表false,非0代表true
    • longdoubule占两个slot
    • reference类型和retureAddress类型都按32位算,也占用一个slot
  • JVM为局部变量表中每个slot分配一个访问索引,通过这个索引即可成功访问到局部变量表中对应的变量值
  • 当一个实例方法被调用的时候,它的方法参数和方法内部定义的局部变量表将会按照顺序被复制到局部变量中每一个slot中
  • 如果访问局部变量表中一个64bit的局部变量值,只需要访问第一个索引即可
  • 如果当前帧是由构造方法或实例方法创建的,那么该对象引用(this)将会存放在index为0的slot处,其余参数按照参数表顺序继续排列
  • static方法不能引用this是因为this根本不存在本地变量中
public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        main.testInnerNoReturnValue();
        int re = main.testInnerHasReturnValue(2);
        System.out.println("testInnerHasReturnValue返回值:" + re);
        testStatic(1L,"test");
    }

    public void testInnerNoReturnValue(){
        System.out.println("我是实例方法-无返回值");
    }
    public int testInnerHasReturnValue(int paramInt){
        System.out.println("我是实例方法-无返回值 参数:" + paramInt);
        return 1;
    }

    public static void testStatic(long paramLong,String str){
        System.out.println("我是静态方法");
    }
}

使用javap -v反编译一下:

//...省略类信息和main方法信息
public void testInnerNoReturnValue();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #16                 // String 我是实例方法-无返回值
         5: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 16: 0
        line 17: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/example/demo/Main;

  public int testInnerHasReturnValue(int);
    descriptor: (I)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=2
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #7                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #17                 // String 我是实例方法-无返回值 参数:
        12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: iload_1
        16: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        19: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: iconst_1
        26: ireturn
      LineNumberTable:
        line 19: 0
        line 20: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      27     0  this   Lcom/example/demo/Main;
            0      27     1 paramInt   I
    MethodParameters:
      Name                           Flags
      paramInt

  public static void testStatic(long, java.lang.String);
    descriptor: (JLjava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=2
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #18                 // String 我是静态方法
         5: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 24: 0
        line 25: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0 paramLong   J
            0       9     2   str   Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      paramLong
      str

可以看到实例方法testInnerNoReturnValuetestInnerHasReturnValue本地变量表中第一个就是this,而静态方法testStatic就不是,在testStatic中可以看到long占了两个slot,若想访问,直接取index 为0的slot即可,无需关心index为1的slot

slot重复利用

栈帧中的局部变量是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的局部变量就很有可能复用过期局部变量的槽位,从而达到节省资源的目的。

public class Main {
    public static int COUNT = 1;
    public static void main(String[] args) {
        Main main = new Main();
        main.testSlot();
    }
    public int testSlot(){
        int a = 1;
        int c = 0;
         if(COUNT > 0){
             //无任何特殊逻辑,只是用来框定变量b的作用域
             int b = 1;
             c = a + b;
         }
         int d = c + COUNT;
         return d;
    }
}

使用javap -v反编译一下:

//...省略类信息和main方法信息
public int testSlot();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
       //...省略指令信息
       //...省略指令行号与代码行号对应信息
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           12       4     3     b   I
            0      24     0  this   Lcom/example/demo/Main;
            2      22     1     a   I
            4      20     2     c   I
           22       2     3     d   I
//...省略其他信息

可以看到变量b的槽位号和变量d的是一样的,变量d在变量b的作用域之外,便可复用slot

静态变量与局部变量的对比
  • 参数表分配完毕后,再根据方法体内定义的变量的顺序和作用域分配
  • 类变量表有两次初始化机会,第一次在准备阶段,执行系统初始化,类变量赋零值。第二次在初始化阶段,执行程序员对其赋值。
  • 和类变量初始化不同,局部变量表不存在系统初始化过程,这意味着一旦定义了局部变量,则必须人为初始化,否则无法使用。
public void test(){
  int i;
  System.out.println(i);
}

这会导致编译错误,提示未初始化变量i,所以局部变量没有赋值不能使用。

操作数栈

在方法执行过程中,根据字节码指令,往栈中写入数据或者提取数据,即入栈和出栈。Java虚拟机的解释引擎就是基于栈的引擎。

在这里插入图片描述

操作数栈主要用于保存计算过程的中间结果,同时作为计算过程的中变量临时存储空间。

如果被调用的方法带有返回值的话,其返回值会被压入当前栈帧的操作数栈中,并更新程序计数器。

操作数栈的数据类型要和字节码指令规定的数据类型严格一致。int32位占4个字节,long占8个字节。

JVM中的栈和局部变量表用的都是数组实现栈。

++i和i++问题
public void add(){
  int a = 1;
  a++;
  int b = 1;
  ++b;
}
//编译出的字节码如下 看起来++i和i++没啥区别
0 iconst_1
1 istore_1
2 iinc 1 by 1
5 iconst_1
6 istore_2
7 iinc 2 by 1
10 return

// 看下赋值
public void add(){
  int a = 1;
  int c = a++;
  int b = 1;
  int d = ++b;
}
//编辑字节码为 看下来也没啥区别
0 iconst_1
1 istore_1
2 iload_1
3 iinc 1 by 1
6 istore_2
7 iconst_1
8 istore_3
9 iinc 3 by 1
12 iload_3
13 istore 4
15 return

栈顶缓存技术

基于栈架构的虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令,这也意味着将需要更多的指令分派(instruction dispatch)次数和内存读/写次数。由于操作数是存储在内存中的,因此频繁的执行内存读/写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们踢出了栈顶缓存(ToS,Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的效率。

动态链接

每一个栈帧内部都包含一个执行运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能实现动态链接(Dynamic Linking)。比如 invokedynamic指令

在Java源文件被编译到字节码文件时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池中,比如:描述一个方法调用了另外的方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

public static void main(String[] args) {

  System.out.println(add(1,2));
}

public static int add(int a,int b){
  return a + b;
}
//上面代码main方法编译后指令为
 0 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
 3 iconst_1
 4 iconst_2
 5 invokestatic #3 <com/example/demo/Main.add : (II)I>
 8 invokevirtual #4 <java/io/PrintStream.println : (I)V>
11 return
//第5行字节码中的 #3 就是引用,后面注释中的com/example/demo/Main.add就是符号链接

常量池如下

在这里插入图片描述

方法的调用

在JVM中,将符号引用转化为调用方法的直接引用于方法的绑定机制相关。

  • 静态链接:

    当一个字节码文件被装在进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。绑定类型为早期绑定,即编译期绑定,后面不再变化。上面的字节码中 #2 #3 #4都是静态链接。子类调用父类中的方法或类调用当前类中的方法,编译期可确定方法,表现为静态链接,早期绑定。

  • 动态链接

    如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行过程中将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。绑定类型为晚期绑定,即运行时绑定。多态中参数是父类,调用时传入子类都是动态链接,只有在被调用时才知道方法具体所属类是哪个。

虚方法:多态中的概念,一般指父类中可被子类重写(覆盖)的方法,Java中除了构造方法,私有方法,final修饰的方法,其它的都可以认为是虚方法。

相关联的字节指令:

普通指令:

  1. invokestatic:调用静态方法,解析阶段确定唯一方法版本
  2. invokespecial: 调用<init>方法、私有及父类方法,解析阶段确定唯一方法版本
  3. invokevirtual: 调用所有虚方法,如果父类中的final方法在子类中调用,用此指令,如果添加了super.,就会使用invokespecial
  4. invokeinterface:调用接口方法

动态调用指令:

  1. invokedynamic: 动态解析出需要调用的方法,然后执行

普通指令固化在虚拟机内部,方法的调用执行人为不可干预,而invokedynamic指令支持由用户确定方法版本。其中invokestatic执行令和invokespecial指令调用的方法为非虚方法,其余的(除final修饰的)称为虚方法。

Java为了实现动态类型语言支持添加了invokedynamic指令,在JDK7添加,最初没有什么代码能编译出这个指令,主要靠ASM这类工具使用此指令,JDK8添加了lambda表达式,会编译成此指令。

动态类型语言和静态类型语言:对类型进行检查是在编译期还是运行期,在编译期就是静态类型语言,在运行期就是动态类型,Java是静态类型。

方法返回地址

栈帧当中的一个内存结构,存储该方法的PC寄存器的值(PC寄存器日常存储程序的下一条指令地址)。

一个方法的结束有两种:

  1. 正常返回
  2. 抛出异常

不管是哪种,都会返回调用处。正常退出,PC寄存器存储方法的下一条指令地址,异常退出根据异常表确定,此时栈帧中不存储任何信息。

如果发生了异常,在异常表中未找到,走异常出口,找到了走异常表处理逻辑。

返回指令包含ireturen(当返回类型是boolean,byte,char,short和int类型使用),lreturn(long),freturn(float),dreturn(double)以及areturn(引用类型),void时使用return

一些附加信息

无特定标准,具体情况依据虚拟机实现,一般会有调试虚拟机的信息。

本地方法栈

本地方法

一个NativeMethod就是一个Java调用非Java代码的接口。该方法的实现由非Java语言实现。在定义一个Native 方法时,只定义签名,不提供方法体,类似一个接口。本地方法的作用是融合不同的编程语言为Java所用,最初用来融C/C++程序。

为什么要使用Native方法

  1. 与Java外环境交互
  2. 与操作系统交互
  3. 经典的Hotspot虚拟机使用的是C实现,为了调用虚拟机中实现的一些高性能的程序

native关键字和abstract不可同时使用。

本地方法栈

本地方法栈线程私有,可以固定也可以动态扩容。固定时超出或者动态扩容时整体内容不够时,会抛出StackOverFlowException。

并不是所有虚拟机都支持本地方法。JVM规范没有明确规定本地方法栈的要求。

堆空间

概述

一个JVM只有一个堆内存,堆也是Java内存管理的核心区域。

Java堆区在JVM启动时即被创建,其空间大小也会随之确定。

堆是JVM管理的最大的一块内存空间。

堆的大小是可以调整的。

《JVM虚拟机规范》规定,堆在物理上可以不连续,但在逻辑上必须是连续的。

所有线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)

《JVM虚拟机规范》规定所有的对象实例以及数组应当分配在堆上,实际上来说:几乎所有的对象实例以及数组应当分配在堆上。

数组和对象可能永远不会存储在栈上,因为栈帧保存引用,这个引用指向对象或者数组在堆中的位置。例外情况就是逃逸分析和标量替换。

在方法结束后,堆中的对象不会马上被移除。仅仅在垃圾收集的时候才会被移除。

堆是GC执行垃圾回收的重点区域。

public class Main {

    public static void main(String[] args) {
        Main main1 = new Main();
        Main main2 = new Main();

        String[] stringArrays = new String[5];
        
    }
}
//字节码为

 0 new #2 <com/example/demo/Main> //这里new了一个类
 3 dup
 4 invokespecial #3 <com/example/demo/Main.<init> : ()V>
 7 astore_1
 8 new #2 <com/example/demo/Main> //这里new了另一个类
11 dup
12 invokespecial #3 <com/example/demo/Main.<init> : ()V>
15 astore_2
16 iconst_5
17 anewarray #4 <java/lang/String> //这里是new了一个数组
20 astore_3
21 return

第0行,第8行,第17行的操作都在堆上进行。

现代垃圾收集器大部分基于分代收集理论设计,细分为

  • Java7 及之前堆内存逻辑上分为三部分:新生区+养老区+永久区
  • Young Generation Space 新生区 Young/New 这个区又被划分为Eden区和Survivor区

20231217未完待续。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/251054.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8syaml提供的几个有意思的功能,Kubernetes在线工具网站

k8syaml.cn 提供的几个有意思的功能。 一、yaml资源快速生成 之前编写operator的helm的时候就需要自己写deployment、service、configmap这些资源&#xff0c;那么多字段也记不清&#xff0c;都是先找个模版&#xff0c;然后copy改改&#xff0c;再看官方文档&#xff0c;添加…

网络安全—学习溯源和日志分析

日志分析的步骤&#xff1a; 判断是否为攻击行为 不是&#xff1a;不用处理 是&#xff1a;判断攻击是否成功或者失败 攻击失败&#xff1a;判断IP地址是否为恶意地址&#xff0c;可以让防火墙过滤IP地址 攻击成功&#xff1a;做应急处置和溯源分析 应急处置&#xff1a;网络下…

【Linux】驱动

驱动 驱动程序过程 系统调用 用户空间 内核空间 添加驱动和调用驱动 驱动程序是如何调用设备硬件 驱动 在计算机领域&#xff0c;驱动&#xff08;Driver&#xff09;是一种软件&#xff0c;它充当硬件设备与操作系统之间的桥梁&#xff0c;允许它们进行通信和协同工作。驱动程…

OpenAI开源超级对齐方法:用GPT-2,监督、微调GPT-4

12月15日&#xff0c;OpenAI在官网公布了最新研究论文和开源项目——如何用小模型监督大模型&#xff0c;实现更好的新型对齐方法。 目前&#xff0c;大模型的主流对齐方法是RLHF&#xff08;人类反馈强化学习&#xff09;。但随着大模型朝着多模态、AGI发展&#xff0c;神经元…

MySQL数据库 DML

目录 DML概述 添加数据 修改数据 删除数据 DML概述 DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 添加数据(工NSERT)修改数据(UPDATE)删除数据(DELETE) 添加数据 (1)给指定字段添加数据 INSERT …

多线程JUC 第2季 CAS的作用介绍与自旋锁

一 CAS作用介绍 1.1 CAS作用 CAS有3个操作数&#xff0c;位置内存值V&#xff0c;旧的预期值A&#xff0c;要修改的更新值B&#xff0c;如果内存值V和预期值相同则&#xff0c;内存值改为B&#xff0c;否则什么都不做。当它重来重试的这种行为称为-自旋。 CAS是一条cpu的原…

关联规则 Fp-Growth算法实现

Fp-Growth算法实现 实现上次博客例子&#xff0c;设置最小支持度计数为3&#xff0c;3/50.6&#xff0c;所以支持度为0.6 代码 # 属于太菜了&#xff0c;做个调包侠 from mlxtend.preprocessing import TransactionEncoder from mlxtend.frequent_patterns import fpgrowth…

Swift 响应式编程:简化 KVO 观察与 UI 事件处理 | 开源日报 No.110

ReactiveX/RxSwift Stars: 23.8k License: MIT RxSwift 是 Reactive Extensions 标准的 Swift 特定实现&#xff0c;它提供了 Observable 接口来表达计算的通用抽象。该项目旨在为 Rx API 提供真正以 Swift 为先的 API&#xff0c;并允许轻松地组合异步操作和数据流。其主要功…

【MISRA C 2012】Rule 5.4 宏标识符应该是不同的

1. 规则1.1 原文1.2 分类 2. 关键描述3. Example4. 代码实例 1. 规则 1.1 原文 1.2 分类 规则5.4&#xff1a;宏标识符应该是不同的 Required要求类规范。 2. 关键描述 该规则要求&#xff0c;当定义宏时&#xff0c;其名称与: •当前定义的其他宏的名称;和 •参数的名称。…

【unity小技巧】两种办法解决FPS游戏枪或者人物穿墙穿模问题

文章目录 前言第一种解决思路第二种方法总结感谢完结 前言 当我们开发FPS游戏时&#xff08;其实3d游戏基本都会遇到这样的问题&#xff09;&#xff0c;如果我们不做处理&#xff0c;肯定会出现人物或者枪的穿墙穿模问题&#xff0c;这是是一个常见的挑战。 这种问题会破坏…

PhpStorm下载、安装、配置教程

前面的文章中&#xff0c;都是把.php文件放在WampServer的www目录下&#xff0c;通过浏览器访问运行。这篇文章就简单介绍一下PhpStorm这个php集成开发工具的使用。 目录 下载PhpStorm 安装PhpStorm 配置PhpStorm 修改个性化设置 修改字符编码 配置php的安装路径 使用Ph…

[wp]第四届江西省赣网杯网络安全大赛-web 部分wp

第四届江西省赣网杯网络安全大赛&#xff08;gwb&#xff09;线上预选赛 因为学业繁忙 只玩了1小时&#xff0c;后续看看补一下这些 2023gwb-web1 九宫格拼图 2023gwb-web2 $filexxx;extract($_GET);if(isset($fun)){$contenttrim(file_get_contents($file));if($fun!&…

uniapp的uni-im 即时通信使用教程【用户与商家对话、聊天 / 最新 / 最全 / 带源码 / 教程】

目录 使用场景用户图片商家图片 官方文档官方文档地址插件地址 项目创建uniCloud开发环境申请开发环境申请完后 概括开始使用步骤1App.vue 步骤2找到软件登录图片找到软件登录接口登录源码如下 步骤3找到软件注册图片注册源码如下 步骤4找到index.vue首页图片 index.vue源码如下…

微信小程序置顶导航,替代原生导航栏

效果图&#xff1a; 思路&#xff1a;Navigation是小程序的顶部导航组件&#xff0c;当页面配置navigationStyle设置为custom的时候可以使用此组件替代原生导航栏&#xff0c;wx.getSystemInfoSync获取可使用窗口高度 wxml代码&#xff1a; <!-- 头部 --> <view cla…

前后端交互—开发一个完整的服务器

代码下载 初始化 新建 apiServer 文件夹作为项目根目录&#xff0c;并在项目根目录中运行如下的命令&#xff0c;初始化包管理配置文件: npm init -y运行如下的命令&#xff0c;安装 express、cors: npm i express cors在项目根目录中新建 app.js 作为整个项目的入口文件&a…

时序预测 | Python实现LSTM电力需求预测

时序预测 | Python实现LSTM电力需求预测 目录 时序预测 | Python实现LSTM电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前最先进的行业预测进行比较。使用…

uniGUI之上传文件UniFileUploadButton

TUniFileUploadButton主要属性&#xff1a; Filter: 文件类型过滤&#xff0c;有图片image/* audio/* video/*三种过滤 MaxAllowedSize: 设置文件最大上传尺寸&#xff1b; Message&#xff1a;标题以及消息文本&#xff0c;可翻译成中文 TUniFileUploadButton控件 支持多…

redis:四、双写一致性的原理和解决方案(延时双删、分布式锁、异步通知MQ/canal)、面试回答模板

双写一致性 场景导入 如果现在有个数据要更新&#xff0c;是先删除缓存&#xff0c;还是先操作数据库呢&#xff1f;当多个线程同时进行访问数据的操作&#xff0c;又是什么情况呢&#xff1f; 以先删除缓存&#xff0c;再操作数据库为例 多个线程运行的正常的流程应该如下…

Android动画(二)——补间动画

目录 介绍 Xml文件定义View动画 补充 alpha_animation.xml&#xff08;透明度&#xff09; rotate_animation.xml&#xff08;旋转&#xff09; scale_animation.xml&#xff08;伸缩&#xff09; translate_animation.xml &#xff08;平移&#xff09; group_animation.…

〖大前端 - 基础入门三大核心之JS篇(55)〗- 内置对象

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;哈哥撩编程&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xff0c;目前在公司…
最新文章