深入浅出JVM

深入浅出JVM学习笔记
一.Java平台
大致分为编译时环境和运行时环境两部分
编译时环境:通过javac xxx.java命令启动java编译器,编译java源程序为xxx.class字节码
运行时环境:通过本地/网络传输xxx.class到java虚拟机,即jvm(这个过程是通过ClassLoader来完成的,ClassLoader会将xxx.class文件和Java API编译的yyy.class文件(import导入的哪些类的.class文件会被一起加入)一起加载到JVM中),JVM通过执行引擎边翻译xxx.class为机器指令边执行这些机器指令。

JVM主要包括两个功能:加载java源程序编译产生的.class文件以及允许所需要的java API对应的.class文件;调用执行引擎执行.class文件

JVM中的执行引擎分为:(1)一次性解释字节码(2)即时编译器(just-in-time compile),第一次被执行的字节码会被编译成本地机器代码,本地机器代码会被缓存,当方法在以后被调用时可以重用(3)自适应优化器。虚拟机开始编译字节码,但会监视运行中程序的活动,并会记录使用最频繁的代码段,当程序运行时,虚拟机只会把活动最频繁的字节码编译成本地代码,其他的仍然保留成字节码

JVM中的类装载器ClassLoader:(1)启动(bootstrap)类装载器,以某种默认方式从磁盘装载类,包括Java API(2)用户定义的类装载器,这种类装载器能够使用自定义的方式装载类,类似于一个Java普通类,能够被Java编译器编译,能够被JVM装载,能够创建实例。当一个类被装载时,JVM会监视该类,看它被启动类装载器还是用户类装载器加载。当使用用户定义的类加载器加载A类时,会使用同一个类加载器加载与A相关的类,比如import的Java API中的类,运行时的java程序的每个类加载器都拥有自己的命名空间,被装载的类默认只能看到被同一个类加载器装载的其他类

Java保证内存完整性的三个方法:
(1)没有通过使用强制转换指针类型或者通过进行指针运算直接访问内存的方法
(2)Java用户只管申请内存,释放内存交由JVM的垃圾回收期处理
(3)数组边界检查

JVM支持平台无关性带来了Java API需要解决最小公分母问题,即尽可能选择满足绝大部分操作平台的特性加入到Java API中

Java class与Java程序是动态链接的,即指向另一个类的引用是通过字符串清楚的标明该类的名字

如果希望保持程序的平台无关性,那么只能通过Java API来访问底层系统资源,Java中包括两种方法:Java方法和本地方法(其他语言编写,编译成与平台相关的机器代码。本地方法保存在动态链接库中,格式是各平台专有的,当需要使用本地方法时,虚拟机需要装载动态库并调用本地方法)

第二章JVM平台无关性
基本数据类型的字长在不同平台上的一致性为Java的平台无关性提供了强有力的支持

编写平台独立的Java程序时,还需要满足两条原则:(1)不要依赖及时终结来达到程序的正确性(2)不要依赖线程的优先级来达到程序的正确性,采用同步机制解决

第三章安全
双亲委派模式:类加载器可以请求另一个用户自定义的类加载器来加载一个。除了启动类加载器以外的每个类加载器都有“双亲”类加载器,即某个类加载器以常用方式加载类型前,会默认把这项任务委派给其“双亲”,一直向上请求直到到达启动类装载器,如果双亲有能力装载这个类型,则这个类加载器返回这个类型,否则自己装载。

用户定义的类加载器遵循双亲委托策略,一次向上委托直到启动类加载器

内置的类装载器负责在本地装载可用的class文件,这些class文件通常包括要运行的Java应用程序的class文件以及一些类库的class文件,按照类路径指明的顺序查找目录以及 JAR文件

一句话总结,对于用户自定义的类加载器若其双亲能加载则递归向上,否则自行加载。其中启动类加载器会在Java API中检查每个被装载的类型,然后才到标准扩展、类路径上的本地类文件中检查

启动类加载器:通常使用原生代码,它在JVM启动后很快被初始化,用于加载最基本的Java API
标准扩展加载器:从标准的Java扩展API中加载类
类路径加载器:从classpath加载类
用户定义的类加载器:额外定义类加载器来加载应用程序

class文件检验器:保证装载的class文件内容结构正确,并且class文件之间是协调一致的,在执行前对字节码进行分析,每次遇到一个跳转指令时都进行校验。class文件检验器一共分为四趟分别为(1)类被装载时进行,class文件检验器检查class文件的内部结构,以确保它能够被安全编译(2~3)在连接过程中进行,class文件检验器确认类型数据遵从Java编程语言的语义,包括检验它所包含的所有字节码的完整性。(4)进行动态连接的过程中解析符号引用时进行,在这次扫描中,class文件检验器确认被引用的类、字段、方法确实存在

JVM维持健壮性的特性:
(1)类型安全的引用转换
(2)结构化的内存访问(无指针算法)
(3)自动垃圾回收
(4)数组边界检查
(5)空引用检查

JVM的内存空间包括Java栈(每个线程一个),存储字节码的方法区,垃圾回收堆(用来存储运行的程序创建的对象)

第四章网路移动性
软件可以随着数据一起通过网络传输,它可以保证软件安装和升级自动完成,JVM实现了代码的网络移动性,把单一的大Class文件分割成小的Class文件,用户只要收到第一个class文件,程序就可以执行

动态连接:只有在程序运行时才知道引用的实际类型

动态扩展:可以延迟到Java应用程序运行时才装载,使用用户自定义的类加载或者通过Driver.forName()加载额外的程序

Class文件,又称为字节码文件,因为每条指令占一个字节,除了两种特例,所有的字节码和操作数都是按照字节对齐的

Java支持对象的网络移动性,即一个JVM中的对象可以引用另一个JVM中的对象,包括使用对象序列化,RMI技术

第五章深入浅出JVM
典型JVM具有的内部组件

线程内组件:存在Java线程(用户创建)与原生线程,只有当创建Java线程的状态准备好后,原生线程才会被创建。当原生线程实例化完成后,调用Java线程上的run(),在run()方法返回后,原生线程会确认JVM是否需要随线程的终止而终止,一旦线程终止,无论是原生线程还是Java线程都会被释放资源
JVM系统线程:包括VM线程(等待一系列使得JVM到达“safe-point”的操作,即无法修改堆的状态),周期性的任务线程(响应Timer事件,例如中断),GC线程(不同类型的垃圾回收),编译器线程(用于在运行时将字节码解释成本地机器码),信号分发线程(接收发送给JVM的信号,并调用JVM合适的方法处理)

单个线程
每个线程的一次执行包括了以下组件:
程序计数器(PC):JVM使用PC跟踪正在执行的指令位置,实际上是指向方法域的一个内存地址
栈:每个线程都属于一个栈,用于存储在线程上执行的每个方法的frame,当前正在执行的方法位于栈的顶部,frame对象可能分配到堆上
原生栈:会为每个线程创建一个原生方法栈,一个原生方法会对JVM产生一个回调并执行一个Java方法,这样一个原生到Java的调用发生在栈上,与此同时线程也将离开原生栈,通常在Java栈上创建一个新frame
栈的限制:栈的大小可以是固定的
Frame:frame包括:局部变量数组,返回值,操作数栈,对当前方法所属类的常量池引用
局部变量数组:包含方法中的所有变量(定义的变量,方法参数,this引用),如果是类方法,方法参数的存储索引从0开始;如果是实例方法,索引为0的槽为存储this引用
操作数栈:入栈,出栈,复制,交换或者执行生产者/消费者的操作
动态链接:当java类被编译时,所有存储在类的常量池中的变量和方法引用都被当做符号引用,一个符号引用仅仅是一个逻辑引用,JVM可以选择解析符号引用的时机,可以发生在被Class文件检验器检查后,被Class文件加载器装载后(静态链接);也可以发生在符号引用首次被使用,动态链接。它将符号引用标识的字段,方法或者类替换为直接引用,每个直接引用都会被偏移方式存储

线程之间共享:
堆:用于运行时存储类的实例或者数组,Frame只存储指向堆中对象或者数组的指针。对象总是存储在堆上,一个方法执行结束,它们不会被立即移除,为了支持垃圾回收,堆被分成三种
1)青年代
2)Eden和Survivior
3)老年代
4)永久代

内存管理
对象或者数组只能通过垃圾回收来释放资源
通常按如下步骤进行:
1)新对象或者数组被创建在青年代
2)次垃圾回收在青年代执行,仍然存活的对象,会被从Eden移动到survivor
3)主垃圾回收将会把对象在代与代之间移动,主垃圾回收器通常会导致应用程序的线程暂停。仍然存活着的对象从青年代移动到老年代
4)永久代会在每次老年代被回收的时候同时进行,它们在两者中其一满了就会被回收

非堆式内存
1)永久代中包含
2)方法区
3)内部字符串
4)代码缓存:用于编译以及存储方法,这些方法已经被JIT编译成本地代码

JIT编译:把有规律执行的字节码编译成原生代码,并存储在代码缓存的非堆内存区

方法区:存储每个类的信息
1)类加载器引用
2)运行时常量池
3)数值常量
4)字段引用
5)方法引用
6)属性
7)方法数据
8)对每个方法的名称,返回值,参数类型,

类加载器:
加载:寻找class文件,并把它读到字节数组,与该类相关的superclass和接口会一起被加载,一旦这些工作完成,一个类或者接口对象将会从二进制数组中创建
链接:包括验证、准备和解析

其中验证:用于确认类以及接口的表示形式在结构上的正确性,同时满足Java编程语言以及JVM语义上的要求
准备:对静态存储的内存分配以及JVM所使用的任何数据结构,静态字段被创建以及实例化为它们的默认值
解析:加载引用的类或者接口来检查符号引用是否正确

运行时常量池:每个类对应一个常量池,将数据保存在常量池,字节码只包含指向常量池的引用,主要用于动态链接,这些数据包括数值字面量,字符串字面量,类的引用,字段的引用,方法的引用

异常表:存储了每个异常处理器的信息,包括起始点,终止点,处理代码的PC偏移量,被捕获异常类的常量池引用

符号表:每个类型都有一个符号表存储在永久代,符号表是一个hashtable将符号指针映射到符号,另外符号表还包含一个指针指向所有符号,”引用计数”用来控制某个符号从符号表中删除,当引用计数为0后,符号就会从符号表中删除

内部字符串:内部字符串被存储在字符串表中,字符串表是一个hashtable映射对象指针到符号,通过String.intern()内部化,如果符号表中已经包含该字符串,则指向该字符串的引用将会返回,如果没有包括在字符表中,则会被加入字符表同时返回引用

你可能感兴趣的:(Java)