怎样免费个人网站建设,郑州上海做网站的公司有哪些,免费建站软件,建设项目公示网站一.java程序运行过程 JDK,JRE,JVM
JVM把我们的字节码翻译成机械能执行的机械码。
JRE除了包含JVM之外#xff0c;还包含很多java的原生依赖库。
JDK除了包含JRE之外#xff0c;还包含很多工具#xff0c;比如javac工具。
.java文件是怎么被执行的
我们的.java文件会被…一.java程序运行过程 JDK,JRE,JVM
JVM把我们的字节码翻译成机械能执行的机械码。
JRE除了包含JVM之外还包含很多java的原生依赖库。
JDK除了包含JRE之外还包含很多工具比如javac工具。
.java文件是怎么被执行的
我们的.java文件会被jdk里面的javac工具编译成.class文件最后在JVM中通过类加载器加载交给执行引擎来执行执行有两种方式一种是通过字节码解释器解释执行一种事通过JIT编译器执行。
解释执行JVM是C语言写的我们在java语言中new出一个对象JVM的字节码解释器会帮我我们自动解释成C最后new出一个对象。 解释执行缺点经过JVM的翻译速度慢一点。
如果一个方法一段代码循环次数达到一定次数后会通过JIT执行(hotsport)。
JIT就是直接把java代码翻译成汇编码(放在JVM的codecache里面)不需要经过解释器处理好处是比较快坏处是要对代码进行提前翻译编译速度会比较长。
JVM的跨平台性我们写一个类可以在不同的平台运行(windows,linux,android)。并不是说所有平台安装的是一个JVM官网上对不用操作系统做了不同的JVM包适配。
JVM的语言无关性与上层使用什么语言无关只要最后生成的.class文件符合JVM的语法规范就行。
二.运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
运行时数据区JVM把它管理的内存区域虚拟化后就是运行时数据区。
直接内存(堆外内存)假如手机内存是8GJVM虚拟化后的运行时数据区是5G还有3G内存是JVM没有虚拟化的但是在Java里面可以通过某个方法区申请这块内存使用后需要释放。这块内存就叫直接内存也叫堆外内存。
运行时数据区按类型区分可以分为线程私有区域和线程共享区域。
线程共享区
方法区和堆
方法区存储信息主要类型信息域Field信息方法Method信息常量静态变量即时编译器编译后的代码缓存(JIT的缓存)。
在java加载类的时候第一步就是Java 虚拟机会根据类的全限定名Package Class Name加载 .class 文件生成对应的二进制字节码并将其存储在方法区中。在加载阶段之前Java 虚拟机会先检查该类是否已经被加载过如果已经被加载过则不会再加载该类。
public class ObjectAndClass {//静态变量存储在方法区里面static int age 18;//常量存储在方法区里面final static int sex 1;//成员变量是一个对象存储到堆里面但是person引用存储在栈里面Person类型存储在方法区里面Person person new Person();//成员变量基础数据类型分配在堆里面private boolean isRight;/*** 引用类型总是被分配到“堆”上。不论是成员变量还是局部* 基础类型总是分配到它声明的地方成员变量在堆内存里局部变量在栈内存里。* param args*/public static void main(String[] args) {//局部变量但是是个对象存储在堆上但是person引用存储在栈里面Person类型存储在方法区里面Person person1 new Person();//局部变量int x 8;//局部变量String a aaa;//局部变量(对象)ObjectAndClass objectAndClass new ObjectAndClass();objectAndClass.isRight true;}static class Person {private int age;public int getAge() {return age;}public void setAge(int age) {this.age age;}}
} * 引用类型总是被分配到“堆”上。不论是成员变量还是局部 * 基础类型总是分配到它声明的地方成员变量在堆内存里局部变量在栈内存里。
方法区具体存储什么可参考Java虚拟机JVM的方法区(Method Area)存储了什么内容_jvm方法区存放什么-CSDN博客
java类的加载顺序
Java 类的加载顺序 - 我爱学习网 (5axxw.com)
线程私有
虚拟机栈
每次启动一个线程就会为当前线程创建一个虚拟机栈。
虚拟机栈存储当前线程运行java方法所需的数据指令返回地址。
大小限制-Xss public class MethodAndStack {public static void main(String[] args) {A();}public static void A(){B();}public static void B(){C();}public static void C(){}
}
现在我们看到上诉代码当我们启动这个程序会为当前的线程创建一个虚拟机栈。这个虚拟机栈中存储当前线程运行java方法所需的数据指令返回地址。 当我们执行到main方法的时候会往当前虚拟机栈中压入一个栈帧。 当我们执行main的时候会跳入A方法又往虚拟机栈里面压入A方法的栈帧。A方法有调B方法又会往虚拟机栈里面压入B方法的栈帧。B方法最后调用C方法会往虚拟机栈里面压入C方法的栈帧。 如果C方法执行完毕C方法的栈帧从虚拟机栈里面退出B方法A方法main方法执行完后对应的栈帧都要从虚拟机栈里面退出。 栈溢出如果我在A方法里面调用A方法形成死递归的话就会一直往虚拟机栈里面压入A方法的栈帧。栈帧会有一定的大小当数量足够多的时候虚拟机栈的内存就会不够造成栈溢出。(方法调用层次太深也有可能)
栈帧 虚拟机栈存储当前线程运行java方法所需的数据指令返回地址。
程序计数器指向当前线程正在执行的字节码的地址。
栈帧包括局部变量表操作数栈动态连接完成出口。
我们先写一个Person类
public class Person {public int work()throws Exception{int x 1;int y 2;int z (xy)*10;return z;}public static void main(String[] args) throws Exception{Person person new Person();person.work();//方法属于本地方法 ---本地方法龙person.hashCode();}
}
我们知道当执行到work方法的时候会往当前线程的虚拟机栈压入一个栈帧我们来看一下work方法执行的过程栈帧里面都干了什么。
我们知道JVM执行的是.class文件所以我们先找到.class文件。 我们cmd到这个文件夹目录下然后执行javap -v Person.class生成的就是字节码 里面的Constant pool是静态常量池。
可以看到为我们自动生成了一个Person的构造方法除此之外还有一个work方法一个main方法。(就算我们自己不定义类的构造方法字节码里面也会自动帮我们生成一个)
我们这边重点来看work方法里面的代码 这些代码是什么意思呢执行这些方法的时候栈帧又会怎么变化
我们看到最前面有0,1,2,3等等这些是字节码的地址给程序计数器使用的。0后面的iconst_1表示需要内存new出一个int的常量并且把它压入操作数栈。 当执行到1istore_1的时候程序计数器里面的count变成1istore_1表示把操作数栈栈顶的数据放倒局部变量表下标为1的地方。istore_1最后面的表示的是局部变量表的位置。 让我们回到我们的java代码里面我们int x 1;这一行代码在字节码里面变成了 0: iconst_1 1: istore_1
我们的x的值被存到局部变量表中了。
那我们就可以推导得到 2: iconst_2 3: istore_2
这两行字节码首先程序计数器的count变成2然后在操作数栈里面压入一个2的常量然后程序计数器的count变成3最后把操作数栈栈顶的2移出到局部变量表下标为2的位置。 接下来我们java代码执行int z (xy)*10我们的x和y已经定义出来了查看字节码发现 4iload_1 5iload_2
这两行字节码的意思是把局部变量下标为1局部变量下标为2的数据压入操作数栈中。 6iadd表示从操作数栈里面取两个数出栈相加把结果入栈。 7bipush 10 这行字节码表示往操作数栈里面推入一个10因为iconst命令只能压入-1到5的数字大于5就压不动了需要使用bipush来推入操作数栈。
接下来执行9imul我们可以推理出这个是把操作数栈里面的两个数字先取出来然后做乘法最后把得到的值重新压入操作数栈。 10istore_3就是把位于操作数栈栈顶的30移动到局部变量表下标为3的地方。这里也就是给z赋值。 因为我们定义的方法是有返回值的所以接下来执行11iload_3把局部变量表下标为3的数字压入操作数栈。 最后执行12ireturn带着操作数栈里面的30返回到main方法里面继续执行。
以上就是work方法在执行过程中栈帧的变化但是为什么0到12中间少了一个8呢
我们在cmd里面得到的是字节码的指令0到12是针对work方法字节码的偏移量有的指令比较大就会大于1行。所以我们程序计数器记录的是当前方法的偏移量。
完成出口我们执行main方法的时候拿到字节码也会看到和work方法一样的0...12这表示的是字节码的行号(针对本方法的偏移量)。我们从main方法执行person.work()跳到work方法假如person.work()的行号是3跳转到work方法work的栈帧的完成出口就是3。当work方法执行完成后通过完成出口回到main方法相应的行数继续执行。
动态连接与多态有关。
注意程序计算器记录的东西可能会重复因为我在main方法里面会记录0,1,2,3,4。当跳到work方法的时候也会记录0,1,2,3,4。但是没关系虚拟机栈同时只会执行一个方法只会执行一个栈帧。
程序计数器的作用当CPU切到另外一个线程的时候当前线程暂停执行当回到当前线程的时候可以从程序计数器记录的行号继续执行。 问题匿名内部类使用局部引用为什么要用final