Java学习之Java虚拟机(JVM)原理
一、Java虚拟机工作原理
首先Java源文件经过前端编译器(javac或ECJ)将.java文件编译为Java字节码文件,然后JRE加载Java字节码文件,载入系统分配给JVM的内存区,然后执行引擎解释或编译类文件,再由即时编译器将字节码转化为机器码。
(1)类加载
类加载指将类的字节码文件(.class)中的二进制数据读入内存,将其放在运行时数据区的方法区内,然后在堆上创建java.lang.Class对象,封装类在方法区内的数据结构。类加载的最终产品是位于堆中的类对象,类对象封装了类在方法区内的数据结构,并且向JAVA程序提供了访问方法区内数据结构的接口。
启动类加载器(BootstrapClassLoader):在JVM运行时被创建,负责加载存放在JDK安装目录下的jre\lib的类文件,或者被-Xbootclasspath参数指定的路径中,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类无法被JAVA程序直接引用。
扩展类加载器(Extension ClassLoader):该类加载器负责加载JDK安装目录下的\jre\lib\ext的类,或者由java.ext.dirs系统变量指定路径中的所有类库,开发者也可以直接使用扩展类加载器。
应用程序类加载器(AppClassLoader):负责加载用户类路径(Classpath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有定义过自己的类加载器,该类加载器为默认的类加载器。
用户自定义类加载器(User ClassLoader):JVM自带的类加载器是从本地文件系统加载标准的java class文件,而自定义的类加载器可以做到在执行非置信代码之前,自动验证数字签名,动态地创建符合用户特定需要的定制化构建类,从特定的场所(数据库、网络中)取得java class。
注意如上的类加载器并不是通过继承的方式实现的,而是通过组合的方式实现的。而JAVA虚拟机的加载模式是一种委派模式,如上图中的1-7步所示。下层的加载器能够看到上层加载器中的类,反之则不行。类加载器可以加载类但是不能卸载类。
(2)运行时数据区
运行时数据区分为方法区、Java堆、虚拟机栈、本地方法栈、程序计数器。其中方法区和Java堆一样,是各个线程共享的内存区域,而虚拟机栈、本地方法栈、程序计数器是线程私有的内存区。
程序计数器:线程私有的,他是一块较小的内存空间,他相当字节码于解释器中的指针,也就是该内存存放下一条即将执行指令的地址。字节码解释器就是通过改变 这个计数器的值来选择下一条即将执行的指令。每一个线程都有一个程序计数器(内存),这样线程切换的时候就能找到自己各个线程各自即将执行的下一条指令。 所以说是线程私有的。
Java虚拟机栈:线程私有的,每一个方法在执行的时候就会创建一个栈帧来存放方法的局部变量,操作数栈,返回地址等,当方法执行完成的时候就释放该栈帧。 栈帧:虚拟机栈中是一栈帧为单位存储的,所以一个虚拟机栈中有很多栈帧,每一个栈帧中分为:局部变量区(存放方法的参数和局部变量),操作数栈,方法的返回地址,动态链接(一般解析解阶段是将部分符号引用转换成直接应用(类加载),而动态链接是另外一部分的符号引用转换成直接引用(运行时))
本地虚拟机栈:线程私有,本地方法指的是那种不是用java语言写的方法,java虚拟机栈只针java方法,而不是本地方法。hotspot虚拟机支持别的语言写的方法在虚拟机上运行,本法方法栈和java虚拟机栈一样。只是他们服务的对象不一样而已,一个为java方法服务,一个为native方法服务。
Java堆:线程共享的,不过也可能为多个线程分配私有的buffer,也就是每个线程有自己的缓存器,java堆可以是物理上连续的,也可以是不连续的。java堆是垃圾回收器管理的主要区域,所以也叫gc堆。java堆可以分为:新生代和老年代
方法区:线程共享的,可以理解为gcc中所所说的静态区,不过也不是确切的准确,因为在hotspot虚拟机中他存放的是类中静态变量和常量(注意是常量哦)。因为他能存储常量,所以还有存储常量的区域有一个特别的名称,叫做常量池(包括引用和基本数据类型的常量),方法区并不是堆,这一点和静态区很相似。所以别名叫non-heap,java堆中可以选择不实现gc回收,但是实际上呢还是会的,只能说垃圾回收器在这个区域不活跃而已,但是回收都是回收常量池中的常量,而不是静态变量。可以称为永久代。
运行时常量池:它是方法区的一部分,但是和方法区的常量池有区别,他存放的常量是在运行时产生的,而不是编译时产生的。注意与普通方法区的区别
(3)字节码的加载是第一步,其后分别是认证、准备、解析、初始化。
加载:加载有两种情况,①当遇到new关键字,或者static关键字的时候就会发生(他们对应着对应的指令)如果在常量池中找不到对应符号引用时,就会发生加载 ,②动态加载,当用反射方法(如class.forName(“类名”)),如果发现没有初始化,则要进行初始化。(注:加载的时候发现父类没有被加载,则要先加载父类)
验证:这一阶段的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全(虽然编译器会严格的检查java代码并生成class文件,但是class文件不一定都是通过编译器编译,然后加载进来的,因为虚拟机获取class文件字节流的方式有可能是从网络上来的,者难免不会存在有人恶意修改而造成系统崩溃的问题,class文件其实也可以手写16进制,因此这是必要的)
准备:该阶段就是为对象分派内存空间,然后初始化类中的属性变量,但是该初始化只是按照系统的意愿进行初始化,也就是初始化时都为0或者为null。因此该阶段的初始化和我们常说初始化阶段的初始化时不一样的
解析:解析就是虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用其实就是class文件常量池中的各种引用,他们按照一定规律指向了对应的类名,或者字段,但是并没有在内存中分配空间,因此符号因此就理解为一个标示,而在直接引用直接指向内存中的地址
初始化:简单讲就是执行对象的构造函数,给类的静态字段按照程序的意愿进行初始化,注意初始化的顺序。(此处的初始化由两个函数完成,一个是,初始化所有的类变量(静态变量),该函数不会初始化父类变量,还有一个是实例初始化函数,对类中实例对象进行初始化,此时要如果有需要,是要初始化父类的)
- 点赞
- 收藏
- 关注作者
评论(0)