编译,魔数,常量池,字面量,数据表,堆栈,方法区,程序计数器,内存引用,内存溢出,垃圾回收器,新生区,永久区,指令集java
上一篇咱们介绍了代码如何被翻译成机器级程序,而后逐条送到CPU执行。可是现代硬件的指令集架构千差万别,不一样机器上运行相同代码每每会出现指令集兼容问题。虚拟机在这个层面上把各类细节封装好,提供通用的接口供上层应用调用。封装好指令集架构的同时提供各类内存淘汰机制。本文将从宏观及微观角度来介绍类文件结构、虚拟机加载类文件机制,类文件生命周期及字节码加载引擎,更加立体的加深对虚拟机工做的认识。算法
从咱们学习JAVA语言的第一天起,就执行过JAVA/JAVAC命令。JAVAC就是把咱们写好的后缀为.java的文本文件编译成后缀为.class的字节码文件。上一章咱们介绍代码本质的时候就了解到JAVA语言的语法元素。java文件咱们能够经过文本编辑器打开,里面也是咱们熟悉的java代码,符合了java语言的语法规范。可是对于class里面的内容,咱们要陌生不少。上一章咱们知道代码经过编译器翻译成机器指令,那class文件会不会也是java虚拟机翻译成的指令呢?编程
其实当java文件被编译成class文件后,就跟java语言没什么关系了。指令执行引擎是JVM虚拟机,其余编程语言,好比Scala,Python等均可以编译成class文件,而后放到JVM来执行。这么说来,咱们更加有必要探究class文件的本质了。api
咱们先从微观的角度来介绍class文件的结构。先写一个简单的java文本文件,而后编译成class文件,来观察class的结构组成。数组
先定义一个接口文件,Add.java文件以下:浏览器
package com.lzh.jvm; public interface Add{ int add(int i,int j); }tomcat
再写一个接口的实现类AddImpl.java,这个基本包含咱们平常常用的文件结构:安全
package com.lzh.jvm; public class AddImpl implements Add{ public static final int TOP = 100; private String point; public int add(int i,int j) { return i + j; } }
因为存在包名定义咱们须要建好com/lzh/jvm的文件目录,而后在当前目录前后编译com/lzh/jvm/Add.java文件和com/lzh/jvm/AddImpl.java文件。获得了Add.class文件和AddImpl.class文件。多线程
Add.java二进制文件:架构
Add.class二进制文件:
AddImpl.java二进制文件:
AddImpl.class二进制文件:
以上四个图是用WinHex二进制编辑工具打开的,左边是文件的二进制编码,右边是ASCII标准编码,因此只能表示英式键盘上的字符,出现中文的话则显示乱码。为了阅读方便,工具展现的是16进制的格式,两个16进制的编码表示一个字节空间(8位)。直观上咱们能够看出来java文件占用的存储空间比class要少不少,这也符合咱们上一章介绍的代码翻译过程。本质上计算机并不认识java文件里面的内容,java属于高级语言,里面的语法更为接近人类的语言,可是对于计算机来讲全难以理解。因此须要把java文件的内容翻译成jvm认识的文件格式。高级语言高度抽象了语言元素,翻译为机器指令则要花费更多的“口舌”来指导计算机一步步执行代码语句。下一节咱们来解释class文件的结构,从而理解jvm如何理解执行class的内容。
本节咱们将以上图给的AddImpl.class为例子来介绍类的结构。从结构上来看,class文件只存放两种类型数据,分别为基础字段和表。
用于判断文件类型,一般咱们以文件后缀来判别文件类型,可是若是修改后缀就会致使安全问题。class以4个字节的空间做为开端,来标明class的类型,CA FE BA BE表示class类型的文件。
魔数后面紧接着4个字节表示jdk版本号。
常量池顾名思义是用于存放字符串常量,字符串常量包含:
咱们知道class本质是一些表的集合,一样常量池也不例外,只不过存放在常量池位置的表有特定的类型,共有11种类型,以下表(图片引用《深刻理解Java虚拟机 JVM高级特性与最佳实践 》):
每一个表的表结构说明以下:
这11种类型的表第一个字段统一为标志字段tag,占用u1一个字节,用于表示该表存放的数据类型。
首先进入常量池开始的两个字节(u2)表示的是常量池的长度,也就是表的个数。
咱们能够看到例子中常量池个数为0x0017,转换为十进制为23,因为第0个表为保留索引,表示没引用到任何字符串,因此实际表的索引是从1开始计算,也就是1~23共22个表。
咱们先观察AddImpl.class常量池,分析第1张表的表结构。查表可知紧接着表个数后面的u1位置为0A,转换为十进制为10,该表类型为CONSTANT_Methodref_info,观察表结构可知接下来的两个u2位置属于该表的字段,这两个字段都是表索引类型,0x0003表示引用第3个表,0x0013表示引用第19个表。
而后该表结束紧接着是第2张表第一个表,该表tag为07是CONSTANT_Class_info类型,第二个空间为u2的字段值为0x0014,引用第20个表。
接着分析第3张表,根据一样的方法,一直能够把常量池的表结构分析完。常量池的做用就是把源代码全部文本数据都集中在常量池这个区间位置内,里面各个表之间相互引用,统一管理文本数据。因为表之间的引用,最后文本数据都是存放在CONSTANT_Class_info表里面,而该表规定文本长度的字段length空间是u2类型,占用2个字节,空间2的16次方,65536/1024=64K,因此java的变量或方法名大小不能超过64K。
修饰类或接口的限定标志
在常量池结束后紧接着2个字节的访问标志,共32个标志位。
类索引、父类索引与接口索引集合:指向常量池的CONSTANT_Class_info表,再由CONSTANT_Class_info表里面的index指向特定CONSTANT_Utf8_info表的bytes字段的字面量。
字段表集合:
字段表结构以下
数组用 [ 表示,字段表用来表示类里面全部变量(不包括方法里面的局部变量)
方法表集合:
方法表结构以下
属性表集合
方法体里面的内容编译为Code属性,code表结构以下
Code,Exceptions,LineNumberTable,LocalVariableTable,SourceFile,ConstantValue,InnerClasses,Deprecated,Synthetic
class文件就像是一个产品的模具,把模具制造出来的过程就是把class加载到jvm内存的过程,而后jvm再照着class模具的样子印出对象来。重点在于模具的设计,其实模具被生产出来也是须要它自己有一套模具。这就是class严格的结构规范,class文件结构规范给出了各个方面的要求,只有按照这个要求造出来的模具才是可用的,才能够被用来制造产品,否则连产品线都上不去,就如同jvm判断class不符合规范而拒绝加载。
类加载时机
类初始化的时机,大部分为被动初始化,用不到的时候都不会初始化。
类加载过程
()方法,按源码顺序执行全部static的语句。没有静态变量或者static语句的类将不会有()。
类加载器
启动类加载器,扩展类加载器,应用程序类加载器
类加载器采用双亲委派机制来读取类文件,破坏双亲委派模型如:OSGI服务由自定义类加载器机制实现。每一个OSGI模块(Bundle)都有本身的加载器
虚拟机性能监控与故障处理工具,给一个系统定位问题的时候,知识,经验是基础,数据是依据,工具就是处理数据的手段。
JDK的命令行工具
生成堆转储文件
用于生成虚拟机当前时刻的线程快照threaddump或者Javacore
JDK的可视化工具
本节从宏观的角度讲解JVM内存结构、内存分配运行策略,垃圾回收机制。
java内存区域与内存溢出
jvm内存区域:方法区,虚拟机栈,本地方法栈,堆,程序计数器;
内存回收概述:
虚拟机栈、本地栈和程序计数器在编译完毕后已经能够肯定所需内存空间,程序执行完毕后也会自动释放全部内存空间,因此不须要进行动态回收优化。
jvm内存调优主要针对堆和方法区两大区域的内存。
引用:强Strong,软sfot,弱weak,虚phantom,强引用不会回收,软引用在内存达到溢出边界时回收,弱引用在每次回收周期时回收,虚引用专门被标记为回收对象。
内存分配与回收策略
超过3m的对象直接进入老年区 -XX:PretenureSizeThreshold=3145728(3M)
Survivor区中的对象经历一次Minor GC年龄增长一岁,超过15岁进入老年区
-XX:MaxTenuringThreshold=15
垃圾收集算法
标记-清除、复制、标记-整理、分代收集(新生用复制,老年用标记-整理)
运行时栈帧结构
每一个方法调用开始到执行完成的过程,对应这一个栈帧在虚拟机栈里面从入栈到出栈的过程。
方法调用不等于方法执行,并且肯定调用方法的版本。
基于栈的字节码解释
解释执行:
基于栈指令集与基于寄存器的指令集:
基于本地解释器执行过程
类加载 执行子系统案例
tomcat类加载,OSGI热插拔,字节码生成技术,动态代理,Retrotranslator
程序编译与代码优化
早期编译(编译期)
重载要求方法具有不一样的特征签名(不包括返回值),可是class文件中,只要描述不是彻底一致的方法就能够共存,如:
public String foo(List<String> arg){ final int var = 0; return ""; } public int foo(List<Integer> arg){ int var = 0; return 0; }
晚期编译(运行期)
解析模式 -Xint
编译模式 -Xcomp
混合模式 Mixed mode
分层编译:解释执行 -> C1(Client Compiler)编译 -> C2编译(Server Compiler)
触发条件:基于采样的热点探测,基于计数器的热点探测
因为JVM涉及内容较深且广,篇幅有限没法深刻分析细节。本文从微观方面分析了做为原材料的CLASS文件的结构,又从宏观方面阐述了JVM是如何消化每个进入的CLASS。JVM自定义了一套逻辑上的指令集,这也呼应了以前咱们介绍的计算机如何运行一文,现代计算机性能有了长足的发展,可是本质上仍是完备的诺依曼体系架构。随着量子计算的日新月异,相信将来的计算模型也会有革命性的突破。
最后,若是有想一块儿学习Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术的
能够来一下个人QQ群架构华山论剑:836442475【点击进入】,好友都会在里面交流,分享一些学习的方法和须要注意的小细节,天天准时讲10年架构师分享经验,Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术