机械门户网站建设特点,注册域名有什么意义,网站倒计时,企业网站模板湖南岚鸿模板运行时数据区
概述 内存
内存是非常重要的系统资源#xff0c;是硬盘和CPU的中间桥梁#xff0c;承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请#xff0c;分配#xff0c;管理的策略#xff0c;保证了JVM高效稳定运行。不同的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寄存器中存储的数值。 两个常见问题 为什么使用PC寄存器记录地址PC寄存器有什么作用 因为CPU是不断的切换线程执行的切换回来后CPU要知道接下来从哪一条指令继续执行。JVM使用PC寄存器来明确接下来应该执行什么指令。 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;)Iflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack2, locals22, args_size90: iconst_01: istore 113: iconst_14: istore 126: ldc2_w #20 // long 2147483647l9: lstore 1311: iconst_112: istore 1514: bipush 816: istore 1618: ldc #22 // float 1.23f20: fstore 1722: ldc2_w #23 // double 3.14d25: dstore 1827: bipush 6529: istore 2031: ldc #25 // String 100033: invokestatic #26 // Method java/lang/Integer.valueOf:(Ljava/lang/String;)Ljava/lang/Integer;36: astore 2138: iload 1240: ireturnLineNumberTable:line 29: 0line 30: 3line 31: 6line 32: 11line 33: 14line 34: 18line 35: 22line 36: 27line 37: 31line 38: 38LocalVariableTable:Start Length Slot Name Signature0 41 0 paramShort S0 41 1 paramInt I0 41 2 paramLong J0 41 4 paramBoolean Z0 41 5 paramByte B0 41 6 paramFloat F0 41 7 paramDouble D0 41 9 paramChar C0 41 10 paramStr Ljava/lang/String;3 38 11 localShort S6 35 12 localInt I11 30 13 localLong J14 27 15 localBoolean Z18 23 16 localByte B22 19 17 localFloat F27 14 18 localDouble D31 10 20 localChar C38 3 21 integer Ljava/lang/Integer;//...省略代码
}
SourceFile: Main.java我们那典型的test2这个方法来分析可以看到Code下面的locals22说明局部变量有22个args_size9代表参数有9个。接下来LocalVariableTable就是局部变量表。Start和Length规定了当前变量作用域Start为指令开始位置Length代表从Start开始在Length内可以用Slot可以理解为下标
Start Length Slot Name Signature0 41 0 paramShort S //参数paramShort 类型为short0 41 1 paramInt I0 41 2 paramLong J0 41 4 paramBoolean Z0 41 5 paramByte B0 41 6 paramFloat F0 41 7 paramDouble D0 41 9 paramChar C0 41 10 paramStr Ljava/lang/String; //参数paramStr 类型为java/lang/String 最前面的L代表是引用类型
3 38 11 localShort S6 35 12 localInt I11 30 13 localLong J14 27 15 localBoolean Z18 23 16 localByte B22 19 17 localFloat F27 14 18 localDouble D31 10 20 localChar C38 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也被转换成int0代表false非0代表truelong和doubule占两个slotreference类型和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: ()Vflags: (0x0001) ACC_PUBLICCode:stack2, locals1, args_size10: 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;)V8: returnLineNumberTable:line 16: 0line 17: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 this Lcom/example/demo/Main;public int testInnerHasReturnValue(int);descriptor: (I)Iflags: (0x0001) ACC_PUBLICCode:stack3, locals2, args_size20: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;3: new #7 // class java/lang/StringBuilder6: dup7: invokespecial #8 // Method java/lang/StringBuilder.init:()V10: ldc #17 // String 我是实例方法-无返回值 参数12: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;15: iload_116: 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;)V25: iconst_126: ireturnLineNumberTable:line 19: 0line 20: 25LocalVariableTable:Start Length Slot Name Signature0 27 0 this Lcom/example/demo/Main;0 27 1 paramInt IMethodParameters:Name FlagsparamIntpublic static void testStatic(long, java.lang.String);descriptor: (JLjava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack2, locals3, args_size20: 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;)V8: returnLineNumberTable:line 24: 0line 25: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 paramLong J0 9 2 str Ljava/lang/String;MethodParameters:Name FlagsparamLongstr可以看到实例方法testInnerNoReturnValue和testInnerHasReturnValue本地变量表中第一个就是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: ()Iflags: (0x0001) ACC_PUBLICCode:stack2, locals4, args_size1//...省略指令信息//...省略指令行号与代码行号对应信息LocalVariableTable:Start Length Slot Name Signature12 4 3 b I0 24 0 this Lcom/example/demo/Main;2 22 1 a I4 20 2 c I22 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的设计者们踢出了栈顶缓存ToSTop-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_14 iconst_25 invokestatic #3 com/example/demo/Main.add : (II)I8 invokevirtual #4 java/io/PrintStream.println : (I)V
11 return
//第5行字节码中的 #3 就是引用后面注释中的com/example/demo/Main.add就是符号链接常量池如下 方法的调用
在JVM中将符号引用转化为调用方法的直接引用于方法的绑定机制相关。 静态链接 当一个字节码文件被装在进JVM内部时如果被调用的目标方法在编译期可知且运行期保持不变时这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。绑定类型为早期绑定即编译期绑定后面不再变化。上面的字节码中 #2 #3 #4都是静态链接。子类调用父类中的方法或类调用当前类中的方法编译期可确定方法表现为静态链接早期绑定。 动态链接 如果被调用的方法在编译期无法被确定下来也就是说只能够在程序运行过程中将调用方法的符号引用转换为直接引用由于这种引用转换过程具备动态性因此也就被称之为动态链接。绑定类型为晚期绑定即运行时绑定。多态中参数是父类调用时传入子类都是动态链接只有在被调用时才知道方法具体所属类是哪个。
虚方法多态中的概念一般指父类中可被子类重写覆盖的方法Java中除了构造方法私有方法final修饰的方法其它的都可以认为是虚方法。
相关联的字节指令
普通指令
invokestatic:调用静态方法解析阶段确定唯一方法版本invokespecial: 调用init方法、私有及父类方法解析阶段确定唯一方法版本invokevirtual: 调用所有虚方法如果父类中的final方法在子类中调用用此指令如果添加了super.就会使用invokespecialinvokeinterface:调用接口方法
动态调用指令
invokedynamic: 动态解析出需要调用的方法然后执行
普通指令固化在虚拟机内部方法的调用执行人为不可干预而invokedynamic指令支持由用户确定方法版本。其中invokestatic执行令和invokespecial指令调用的方法为非虚方法其余的除final修饰的称为虚方法。
Java为了实现动态类型语言支持添加了invokedynamic指令在JDK7添加最初没有什么代码能编译出这个指令主要靠ASM这类工具使用此指令JDK8添加了lambda表达式会编译成此指令。
动态类型语言和静态类型语言对类型进行检查是在编译期还是运行期在编译期就是静态类型语言在运行期就是动态类型Java是静态类型。
方法返回地址
栈帧当中的一个内存结构存储该方法的PC寄存器的值PC寄存器日常存储程序的下一条指令地址。
一个方法的结束有两种
正常返回抛出异常
不管是哪种都会返回调用处。正常退出PC寄存器存储方法的下一条指令地址异常退出根据异常表确定此时栈帧中不存储任何信息。
如果发生了异常在异常表中未找到走异常出口找到了走异常表处理逻辑。
返回指令包含ireturen当返回类型是booleanbytecharshort和int类型使用lreturnlongfreturnfloat,dreturndouble以及areturn引用类型void时使用return
一些附加信息
无特定标准具体情况依据虚拟机实现一般会有调试虚拟机的信息。
本地方法栈
本地方法
一个NativeMethod就是一个Java调用非Java代码的接口。该方法的实现由非Java语言实现。在定义一个Native 方法时只定义签名不提供方法体类似一个接口。本地方法的作用是融合不同的编程语言为Java所用最初用来融C/C程序。
为什么要使用Native方法
与Java外环境交互与操作系统交互经典的Hotspot虚拟机使用的是C实现为了调用虚拟机中实现的一些高性能的程序
native关键字和abstract不可同时使用。
本地方法栈
本地方法栈线程私有可以固定也可以动态扩容。固定时超出或者动态扩容时整体内容不够时会抛出StackOverFlowException。
并不是所有虚拟机都支持本地方法。JVM规范没有明确规定本地方法栈的要求。
堆空间
概述
一个JVM只有一个堆内存堆也是Java内存管理的核心区域。
Java堆区在JVM启动时即被创建其空间大小也会随之确定。
堆是JVM管理的最大的一块内存空间。
堆的大小是可以调整的。
《JVM虚拟机规范》规定堆在物理上可以不连续但在逻辑上必须是连续的。
所有线程共享Java堆在这里还可以划分线程私有的缓冲区Thread Local Allocation BufferTLAB
《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 dup4 invokespecial #3 com/example/demo/Main.init : ()V7 astore_18 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未完待续。。。。