3. java 虚拟机
https://www.cnblogs.com/like-minded/p/5157667.html
https://www.jianshu.com/p/904b15a8281f
将.class文件进行反汇编
3.1 java技术体系
-
Sun官方定义的java技术体系包括一下几个组成部分:
- java程序设计语言
- 各种硬件平台上的java虚拟机
- Class文件格式
- java API类库
- 来自商业机构和开源社区的第三方java类库
-
JDK:用于支持程序开发的最小环境。可以把java程序设计语言、java虚拟机、java API类库这三部分统称为 JDK 。
-
JRE:是支持java程序运行的标准环境。可以把java API类库中的java SE API子集和java虚拟机这两部分统称为JRE。
3.2 java 虚拟机
3.2.1 java虚拟机的组成
JVM 被分为三个主要的子系统:
(1)类加载器子系统
(2)运行时数据区
(3)执行引擎
3.2.2 类加载器子系统
类加载器用来加载Java 类到java虚拟机中。
一般来说,java虚拟机使用java类的方式如下:java源程序(.java文件)在经过java编译器之后就被转换成java字节代码(.class文件)。类加载器负责读取java字节代码,并转换成java.lang.Class类的一个实例(类类型)。每个这样的实例用来表示一个java类。通过此实例的newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如java字节代码可能是通过工具动态生成的,也可能是通过网络下载的。但第二次实例化一个类时,就从对应的Class类newInstance(),不用每次都读取.class文件。
简单说,类加载器就是根据指定全限定名称将class
文件加载到JVM
内存,转为Class
对象
java的动态类加载功能是由类加载子系统处理。当它在运行时(不是编译时)首次引用一个类时,它加载、链接并初始化该类文件。
3.2.2.1 类加载过程
-
加载
加载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载,同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID,因此不同类加载器加载相同的类是不同的。有继承,将先加载父类。
将类.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区中的数据结构。
-
链接
链接过程负责对二进制字节码的格式进行:校验、解析类中调用的接口、类。校验是防止不合法的.class文件,然后对中的所有属性对类中的所有属性、调用方法进行解析,以确保其需要调用的属性、方法存在,以及具备相应的权限。
链接:
(1)校验:确保被加载的类的正确性
(2)准备:为类的静态变量分配内存,并将其初始化为默认值。
(3)解析:将所有的符号内存引用 被方法区的原始引用 替代
-
初始化
这是类加载的最后阶段,这里所有的静态变量会被赋初始值**,** 并且静态块将被执行。。
在下列情况下初始化过程会被触发执行:
(1)调用了new
(2)使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
(3)子类调用了初始化(先执行父类静态代码和静态成员,再执行子类静态代码和静态变量,然后调用父类构造器,最后调用自身构造器。);
(4)调用某个类的类方法(静态方法)。
(5)访问某个类变量或为某个类变量。或为该变量赋值
3.2.2.2 类加载器的代理模式(双亲委派模型)
-
什么是双亲委派模型
-
三类加载器:
- 启动类加载器(Bootstrap ClassLoader):由
C++
语言实现(针对HotSpot
),负责将存放在<JAVA_HOME>\lib
目录或-Xbootclasspath
参数指定的路径中的类库加载到内存中。 - 扩展类加载器(Extension ClassLoader):负责加载
<JAVA_HOME>\lib\ext
目录或java.ext.dirs
系统变量指定的路径中的所有类库。 - 应用程序类加载器(Application ClassLoader):负责加载用户类路径(
classpath
)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
- 启动类加载器(Bootstrap ClassLoader):由
-
双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载。
-
-
为什么要使用双亲委派模型?
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。因此,使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处:类随着它的类加载器一起具备了一种带有优先级的层次关系。
例如类
java.lang.Object
,它由启动类加载器加载。双亲委派模型保证任何类加载器收到的对java.lang.Object
的加载请求,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为
java.lang.Object
的类,并用自定义的类加载器加载,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为就无法保证,应用程序也将会变得一片混乱。
3.2.3 运行时数据区
运行时数据区域被划分为5个主要部分:
-
方法区(线程共享):用于存储JVM加载的类信息、常量、静态变量、即时编译器(JIT)编译后的代码等数据。每个JVM只有一个方法区,它是一个共享的资源。
-
堆(线程共享):虚拟机启动时创建,用于存放对象实例,几乎所有的对象(包含常量池)都在堆上分配内存。每个JVM只有一个堆,它是一个共享的资源。
由于方法和堆的内存由多个线程共享,所以存储的数据不是线程安全的。
-
栈(线程私有):一个线程对应一个栈,每个方法在执行的时候都会创建一个栈帧(用于存储局部变量表、操作数栈、动态链接、方法出入口等信息),不存在垃圾回收问题,只要线程一结束,该栈就释放。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中的入栈和出栈的过程。它的生命周期和线程相同。
- 本地方法栈(线程私有):
与java虚拟机栈的作用非常相似,区别是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是为虚拟机使用到的native方法服务。
-
程序计数器PC(线程私有):就是一个指针,指向方法区中的方法字节码,用来存储指向下一条指令的地址,也就是下一条要执行的指令代码。
-
直接内存:
并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
JDK1.4加入了NIO,引入一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。因为避免了在Java堆和Native堆中来回复制数据,提高了性能。
3.2.4 执行引擎
分配给运行时数据区的字节码将由执行引擎执行。执行引擎读取字节码并逐段执行。
-
解释器
-
编译器
-
垃圾回收器(GC)
收集并删除未引用的对象。可以通过调用
System.gc()
来触发垃圾回收,但并不保证确实进行垃圾回收。JVM的垃圾回收只收集那些由new关键字创建的对象。所以,如果不是用**new **创建的对象,可以使用finalize()
函数来执行清理。