java自从问世以来,一直号称一处编译,处处运行。那么java的底层的是什么样的呢?我们所熟知的JDK(Java Development Kit)其实是一个很大的东西,包含了JRE(Java Runtime Environment),Java 语言以及一些工具集。
一、 Java 类的加载过程
(一)、装载(Load)
装载(Loading) 是类加载的一个阶段,需要Java 虚拟机来完成以下过程
1). 通过一个类将一个类的二进制文件字节流读取进JVM中。
2). 将这些二进制文件字节流装载进运行时数据区中。
3). 在Java 堆中生成一个代表这个java 类的对象,并把它作为方法区的数据的入口。
(二)、链接(Link)
1).验证(Verify)
加载完成后,JVM就会去验证Class文件的格式,保证Class文件中包含的数据格式符合JVM的要求,且不会对JVM的自身安全造成影响。
如果您打开过class的二进制文件就会知道前面4字节是cafe babe ,那这个又代表什么呢?通过下面官方提供的对照表可以知道,前面四个字节就是表明这个是一个标识类的文件格式,U4 代表的是4字节,U2代表是2字节,按照这个对照表可以知道class二进制文件中包含了类的主版本、次版本、方法、常量池等信息
加载过程中就会涉及到java加载机制-双亲委派机制
①. 类加载器收到加载请求;
②. 不会直接去加载,而是委托给其父类加载器去加载 ,直到Bootstrap加载器去加载;
③. 如果父类加载器无法加载则让其子类加载器去加载;
④. 重复③,直到加载完成。
这样做的一个好处就是可以避免一个类在JVM中多次加载,也可以避免JVM自身的类被修改,保证JVM的自身安全。
主要的JVM加载器
Bootstrap Class Loader : 主要加载 %JAVA_HOME%/jre/lib下java自带的jar包;
Extension Class Loader : 主要加载 %JAVA_HOME%/jre/lib/ext下的jar包或者-Djava.ext.dirs指定目录下的jar包
Application Class Loader : 主要加载 classpath指定的jar包以及-Djava.class.path指定目录下的jar包
Custom Class Loader : 是java.lang.ClassLoader的子类自定义的加载类,通常加载自定义的类
2). 准备(Prepare)
接下来就是准备阶段了,这一过程主要是为类的变量分配内存并设置默认值,这些内存都是在方法区中分配的。这里只会分配final、static关键字修饰的变量,非这些关键字修饰的变量会在虚拟机栈中分配。
3). 解析(Resolve)
解析主要是将类中的符号引用变为直接引用,这里涉及类或接口的解析,字段的解析、类方法的解析、接口方法的解析。符号引用就是以一组符号的形式表示出一些特定的信息;直接引用就是就是将这些信息转化为内存地址或一个偏移量
(三)、初始化(Initialize)
类或接口的初始化包括执行类或接口的初始化方法。Java虚拟机是多线程的,所以类或接口的初始化需要同步,因为其他一些线程可能同时尝试初始化同一个类或接口。还可能递归地请求类或接口的初始化,作为该类或接口初始化的一部分。Java虚拟机的实现负责通过以下过程来处理同步和递归初始化。它假定Class对象已经进行了验证和准备,而且Class对象包含的状态指示了以下四种情况之一:
- 对这个Class对象进行了验证和准备,但没有初始化。
- 这个Class对象是由某个特定线程初始化的。
- 这个Class对象已经完全初始化,可以使用了。
- 这个Class对象处于错误状态,可能是因为初始化尝试失败。
二、JVM 内存模型
JVM内存模型就是在符合现有内存模式的基础上屏蔽了各种硬件和和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范,其主要的运行时数据区处理相关差异。JVM定义了许多在程序运行时的运行数据区。一些数据区在随着JVM生命周期活动,即与JVM的启动停止一致。另一些则和线程的生命周期相同,与线程同生共死。
(一)、 方法区(Method Area)
方法区是在JVM中是所有线程所共享的,它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化和接口初始化中使用的特殊方法。它是随着JVM的启动而启动的,在逻辑上方法区是属于堆的一部分,在JVM的规范中并没有强制性的要求方法区进行垃圾回收,而且方法区的大小可以固定也可以对其进行扩展,并没有要求方法区的内存空间是连续的,但是在方法区内存不够的时候会OutOfMemoryError
(二)、堆(Heap)
堆与方法区一样是所有线程所共享的,也是随着JVM的启动而启动,它是为所有类实例或数组分配内存的运行时数据区域。该区域中的对象的堆存储由垃圾回收机制回收。堆可以是固定大小的,也可以根据计算的要求进行扩展,如果没有必要使用更大的堆,则可以缩小。堆的内存同样不需要是连续的。但是在堆内存不够的时候会OutOfMemoryError
(三)、虚拟机栈(Java Virtual Machine Stacks)
虚拟机栈是每个线程独有的,随着线程的创建而存在,线程结束而死亡。它存储的是局部变量表、操作数栈、动态链接和方法的出口等信息。但是在虚拟机栈内存不够的时候会OutOfMemoryError,在线程运行中需要更大的虚拟机栈时会出现StackOverFlowError.
(四)、本地方法栈(Native Method Stacks)
本地方法栈允许java程序调用底层封装的C语言编写的方法实现,如果在线程需要使用本地方法栈的时候,JVM会为每个线程创建一个。与虚拟机栈一样在一些情况下会出现OutOfMemoryError或者StackOverFlowError
(五)、程序计数器(Program Counter Register)
由于JVM可以并发执行线程,因此会存在线程之间的切换,而这个时候就程序计数器会记录下当前程序执行到的位置,以便在其他线程执行完毕后,恢复现场继续执行。JVM会为每个线程分配一个程序计数器,与线程的生命周期相同。
(六) 运行时常量池(Runtime Constant Pool )
运行时常量池是类文件中常量池表的运行时形式,它包含编译时已知的常量信息、必须在运行时解析的方法和字段引用等信息。
任何形式的转载都请联系作者Jackieonway获得授权并注明出处。
微信搜索"JackieOnWay"关注我们,第一时间获取最新技术文摘。