JVM:一种能够运行java字节码的虚拟机,不单单只能运行java程序,只要符合jvm文件(.class文件)格式的要求都能运行
java为什么说是“一次编译处处运行”呢,这就是因为JVM帮我们屏蔽掉了操作系统层面的实现细节,所以在不同的操作系统上面需要下载不同的JVM。
那么JDK、JRE、JVM有什么区别呢?
JRE包含JVM,JDK包含JRE
JVM,java虚拟机,具有运行.class文件的能力
JRE称为java最小的运行时环境,它包含java运行的核心类库,但没有编译能力,只能运行基本的.class文件
JDK,java运行时环境,不仅包含了java核心类库,还包含了各种java工具,如javac、javap等
.class文件是怎么进入JVM中去的呢?
类加载机制,通过java类加载,将.class文件加载到JVM中去
类加载机制:加载-》链接-》初始化
1、加载:
(1)寻找到class文件全限定路径名,然后通过二进制流加载到JVM中,也就是所谓的ClassLoader(类装载器)
我们有很多不同的类,比如rt.jar、自定义的类,这些都是通过一个类加载器进行加载吗,如果你自定义了一个和系统类一模一样的(全限定路径名一样)类,那么这个装载是怎么样去进行判断的?
java通过不同的类路径,使用不同的类装载器去进行装载(由上往下)
Bootstrap ClassLoader(启动装载器):主要负责装载jre的核心类库
Extension ClassLoader(扩展类装载器):主要负责装载jre扩展目录ext中的jar类包
App ClassLoader(系统类装载器):主要负责装载ClassPath路径下的类包
Customer ClassLoader(用户自定义的类装载器):主要负责装载用户自定义路径下的类包
这些装载器为了避免重名带来了麻烦就想了个办法,当出现一个类出现了,子类装载器不会马上去进行装载,而是先把它交给父装载器进行加载,除非父装载器装载不了,才会返回到子类装载器进行加载,如果子类也装载不了,抛出异常。这就是类加载器机制中的双亲委派机制。
在Tomcat中就自定义了类装载器,改变了这样一个机制,在Tomcat中,是优先自己装载,然后父类进行装载。
(2)需要把类文件静态存储结构里面对应的内容,存储到JVM方法区中去
(3)将java.lang.Class对象,存储到JVM堆内存中去
2、链接:
验证:验证.class文件的正确性
准备:给类准备静态变量分配内存,并赋予默认值
解析:将符号引用转化为直接引用,符号引用,可以简单理解成class文件中的那些内容,这些内容是没有实际含义,并不能代表真实的物理存储,直接引用,在java进程中能够代表真实含义的
3、初始化:
对类的静态变量赋予真正的值(前面只是赋予默认值),执行静态代码块
java运行时数据区包含哪几部分?
方法区
堆
虚拟机栈
本地方法栈
程序计数器
在这五块中,方法区和堆属于线程共享的,也就是说存在线程安全问题,而其它三块区域是线程独有的。
方法区
方法区中存储着,类信息、常量、静态变量等等
堆
存储对象,对象的类信息就存储在方法区
虚拟机栈
java虚拟机栈属于线程私有的,也就是说所有的线程都有一个java虚拟机栈,在虚拟机栈中存储中着一个个的栈帧,这一个个的栈帧对应着一次次的方法调用,既然是栈的结构也就是说,对于虚拟机栈的操作只有入栈和出栈,当栈空间超过其最大值时,会报StackOverFlowError错误。
既然虚拟机栈是线程私有的,那么在多线程环境下,两个线程同时去竞争CPU资源时,一个线程执行到一半,突然失去了CPU控制权,那下次在轮到它执行的时候,它是怎么样恢复到之前执行的状态的呢?
这个时候就需要程序计数器登场了
程序计数器
程序计数器是线程私有的,当线程正在执行的是一个java方法时,程序计数器会指向该方法的JVM指令地址,当线程执行被中断了,可以从程序计数器中进行恢复。如果正在执行一个本地的方法(native),则是一个空值
那如果正在执行一个本地方法,你怎么去进行恢复执行状态?
如果是一个本地方法的话,就需要另外一个线程私有的区域了,没错,就是本地方法栈
本地方法栈
本地方法栈和虚拟机栈非常相似,如果线程正在执行本地方法,这就需要本地方法栈来进行记录了
那你再说说虚拟机栈中栈帧的具体结构吧
虚拟机栈中栈帧分为:局部变量表、操作数栈、动态链接、方法正常退出或者异常退出的定义(返回地址)等
局部变量表:存储着方法中局部变量的值
操作数栈:将方法中的常量暂时存储在操作数栈中,然后将该常量赋值给局部变量表中的变量。
动态链接:将符号引用转化成直接引用,为什么之前在类加载的链接阶段已经做过一次了,为什么这里还要做呢,这就是因为,假如,你在方法中写了一个接口或者抽象,然后new了一个子类的具体类,那么在类加载阶段是不能解析出来你到底执行的方法是哪个子类的,必须在方法运行的时候动态的指定到底是哪一个子类中的哪一个具体的方法。所以在类加载阶段可以称之为静态加载,而在这里可以称为动态加载。
返回地址:方法执行完成之后,应该从哪里开始执行下一个方法呢,这就存储在返回地址里面