第一章 JAVA体系结构介绍
- java应用程序可以使用两种类装载器,启动(bootstrap)类装载器和用户自定义装载器。
每一个类被装载的时候,java虚拟机都监视着这个类,看它是被什么装载器装载的,当被装载的类引用了其他类的时候,
会使用相同的类装载器去装载被引用的类。因此默认情况下只能看到被同一个类装载器装载的类,通过这种方法,java允许
在java程序中简历多个命名空间,每一个类加载器都有自己的命名空间。 -
class
文件,运行在JVM上的二进制文件
第三章 安全(--看完再回顾本章)
- JAVA沙箱中类装载体系结构是第一道防线
- 防止恶意代码干涉善意代码
- 守护被信任类库的边界
- 将代码分类,确定该类代码可以执行些些操作
- class 文件检测器
- 检验字节码的的完整性
- 类型数据语义检查
- 字节码验证
- 符号引用验证
- 二进制兼容
- JVM内置安全特性
- 类型安全的引用转换
- 结构化的内存访问
- 自动垃圾收集
- 数组边界检查
- 空引用检查
- 禁止对内存进行非结构化访问
- 异常的结构化处理
- 安全管理器和java api
- 代码签名和认证,将一个未签名的class文件通过散列和私钥得到一个带有签名后散列的文件
- 访问控制器
第五章 java虚拟机
- 生命周期,main作为函数的起点,每一个程序都运行一个虚拟机实例上。java虚拟机上两种线程,一种是守护线程,另一种是非守护线程
守护线程是由jvm自己使用的,比如垃圾回收的线程,而开始于main函数的线程是非守护线程。只要还有非守护线程在运行,虚拟机还是存活的
,所有非守护线程退出时,虚拟机会自动退出。
java 虚拟机的结构体系
-------------------
| 类装载器子系统 |
-------------------
|
------------------------------------------------------------------
| | | | | | |
| 方法区 堆 java栈 PC寄存器 本地方法栈 |
------------------------------------------------------------------
| |
执行引擎 ————————————————> 本地方法接口
- 一些点
- 方法区和堆内存是由所有的线程共享的,虚拟机装载class文件时,会把类的数据放在方法区中,当程序运行时,会把根据类数据创建的对象
放在堆内存中。 - 当新线程被创建时,都将得到一个
PC寄存器
和一个JAVA栈
,PC寄存器
总是指向下一条被执行的指令,java栈则存储方法的调用状态。(局部变量
,参数
,返回值
,运算中间值
) - java 栈是有很多的栈帧组成,当线程调用一个方法的时候,,JVM将新的栈帧入栈,当函数返回时,栈帧被出栈。
- 数据类型
- 基本类型(float, double, byte, short, int, long, char, boolean, returnAddress)
64 32 8 16 32 64 16 - 引用类型(类,接口 ,数组)
方法区
- 方法区的数据共享,因此是必须线程安全的。虚拟机允许程序员指定方法区的大小
- 方法区会在内存中存储类的以下信息:
类型的全限定名(包名+.+类名)
此类超类的全限定名
类还是接口
修饰符(public ,abstruct, final)
接口全限定名的有序列表
类型的常量池
字段信息(字段名,类型,修饰符)
方法信息(方法名,类型,修饰符)
一个到类ClassLoder的引用
一个到Class类的引用(forName(),让用户得到已装载对象的Class实例)
方法字节码
操作数栈和该方法在栈帧中局部变量的大小
异常表
下面看一看流程
class Volcano(){
public static void main(String args[]){
Lava lava = new Lava();
lava.flow();
}
}
- 例如我们要执行一个叫
Volcano
的类,当我们告诉JVMVolcano
这个名字
- JVM会找到并读入
Volcano.class
文件 - 然后导入其中的二进制流,并把相应的数据存在方法区
- 通过执行方法区中的字节码,开始执行main()方法,在执行时会一直持有指向常量池的指针
- main方法中第一条指令是给常量池第一个类分配象的内存,于是在常量池中找到第一项,发现是对另一个类Lava的引用
- 检查方法区,看是否被装载,发现没有被装载,于是查找
Lava.class
文件装载,同样把信息存储在方法区中 - 接着指向常量池第一行的指针替换掉常量池的第一项(
Lava
的全限定名)->常量池的解析
(符号引用替换成直接引用,本地指针) - 虚拟机准备为Lava分配内存,并用这个指针访问它的类信息,找出类信息中的需要为这个类分配多少堆内存
- 虚拟机确定了大小,就在堆上为对象分配内存,并初始化常量值。然后把对象的引用入栈,第一条指令执行完成。
- 接下来通过这个引用调用flow方法
堆
java一个虚拟实例中只有一个堆空间,所有线程共享
java 只有在堆中分配内存的指令,没有释放的指令,垃圾收集器会负责堆和方法区的内存回收
堆和方法区一样也不是一个连续的内存区,是可扩展的
堆上的对象还有一部分数据,是对象锁(互斥锁),请求可以追加,请求几次,必须释放几次,例如请求了4次,只释放了3次,那还是持有这个对象锁。
堆上对象还有一部分数据与垃圾收集器有关,垃圾回收器必须跟踪每个对象。
数组是一个对象,总是存储在堆中
PC(程序计数器)寄存器,在每个线程中有一个,它有一个字的大小,存储一个本地指针,总是指向下一条将要执行的指令。
java 栈
- 每当启用一个线程,JVM会为它分配一个java 栈。
- 栈帧的组成
- 局部变量区 -> 一个数组,以字(32bit)为单位,类型int,float,returnAddress的值再数组中占一项,byte,short,char会被转化成int,也占一项,double,long占2项。任何一个实例方法(非static),的数组第一项都是对象本身的reference.
- 操作数栈 -> byte,short,char会被转化成int,和上面一样,只有在存回堆中是会被转化为原来类型。他也是一个数组,但是按照栈操作来访问。由于java虚拟机没有寄存器,java的指令是通过操作数栈中取得操作数的。
- 栈帧数据,支持常量池的解析正常方法返回和异常派发机制的数据。
为了处理执行期的异常退出情况,帧数据区保存一个对此方法的异常表的引用。当方法抛出异常,会在异常表中查找对应的异常,如果找到了匹配的catch语句,就会交给ccatch中的代码处理,如果没有则异常中止。 -
两站不同的虚拟机实现的帧分配
- 本地方法栈,取决于设计者的实现
执行引擎(没看太明白)
- 运行中java程序的每一个线程都是一个独立的虚拟机执行引擎的实例。
- 从线程的生命周期开始,它要么在执行字节码,要么在执行native方法。
- 指令集,指令集关注的中心是操作数栈。操作数栈中的数值必须按照适合他们类型的方式使用。比如压入栈4个
int
,却把他们当做两个long
来做操作,是非法的 。
第六章 JAVA class文件 (粗略看)
java class文件是对java程序二进制文件的精确定义。一个class文件中指包括一个class 或者interface.
class文件不一定与java语言有关,别的语言也可以编译成class文件在虚拟机上运行。
- class 文件组成
- magic ,每个class文件的前四个字节 0xCAFEBABE,用来分辨是否是class文件。
- minor_version major_version b版本号。
- constant_pool_count 常量池的数量,constant_pool 常量池
- access_flags 类的修饰信息,public ,private ,static 等
- this_class 指向常量池的索引
- super_class 超类常量池索引
第七章 类型的生命周期
- 类主动装载的时机
- 创建实例时(new, 反射,克隆,反序列化)
- 调用类中的静态方法
- 使用类或接口的静态字段或者对字段赋值(final 修饰的静态变量除外,它被初始化为一个编译时的常量表达式)
- 调用api反射方法
- 初始化子类
- 含有main函数的类
- 所有类的初始化都要求它的超类在此之前初始化了。(接口不是)
- 装载
- 通过完全限定名产生一个二进制流
- 解析二进制文件
- 创建一个该类型Class实例
- 初始化
- 所有类变量初始化语句和类型的静态初始化器都被java编译器收集到一起放在一个特殊的方法<clinit>中,并非所有的类都有这个方法。
- 如果多个线程需要初始化一个类,仅仅允许一个线程来执行初始化,其他线程需要等待。完成后需要通知其他等待线程。
对象生命周期
- 实例化
new
reflect.newInstance()
clone()
ObjectInputStream.getObject
堆中分配内存,然后赋予实例变量初始值
类型当没有引用的时候会被卸载,同样通过垃圾回收器。