虚拟机要来干啥?C和C+不用
代码托管,字节码编译机器码,代码托管阔以干很多事,如内存管理,代码检阅(数组安全,代码语法安全检阅,虚代码关联),去泛型等
代码怎么运行,过程
程序员写好代码,javac工具编译成二进制代码文件,二进制文件开始是魔数,标识是java文件,后面字节是编译器版本,工具在哪个版本编译的。
接着是常量池,有一部分计算好每个方法栈的深度,方便方法栈的开启。
又称字节码文件,字节码称呼缘由,每个指令由八个位表示。又称字节码指令文件。怎么查看指令?阔以由javap工具转成字节指令显示,其实还是二进制文件,只是对应成字节码指令显示。
字节码,二进制结构,魔术,占4字节(16进制也就8个字符,这里不算字符,按进制算,根据ASCII算转字母),版本高低,占4字节,常量池,先常量个数占一字节,再逐一分配常量对应的二进制数,这里有几种常量类型,索引表示类,类名,方法,方法名。字符串,字符串名,到值。等一直索引下去。特别关注final终极常量,谁引用谁的常量池就拥有它。助记符。其中类的形参第一个是this,要不然你哪里在类里面调。
可利用asmtools.jar包调用里面的入口方法,把二进制文件传入进行匹配输出对应指令文件。阔以实现二进制文件和指令文件的互转。
java语言看待代码,跟java虚拟机指令看待代码不一致。比如基本类型boolean编译后是整数1或0。阔以试着改成其他数字。
加载进虚拟机,可有两种方式执行代码,即时编译先转机器码,解释执行,不编译成机器码,直接运行。热代码编译,冷代码解释即可,虚拟机有多个编译器。真实运行有五个部分,寄存器,cpu处理指令,本地方法区非iava语言代码,方法区,二进制文件存储区域,类常量也在这里,共享区域。方法栈,执行方法区域,把方法代码编译成指令压栈,每个执行还没执行前,已经压栈,先进后出,正是利用这特性。私有,多线程独自开启自己的方法栈。随机就来了。堆,开启对象存储,给引用对象地址。基本变量不入堆,堆单个对象分配都是连续。每个二进制类都有自己的class对象,反射可用。堆详细介绍可看后续垃圾回收。
基本类型
自动拆装和自动装箱,存储堆浪费很多空间,一个对象的存储默认开启2048字节,不够存储了再开启,对象头16字节,加整形4字节。
八大基本类型,虚拟机预先定义好的对象,布尔值单字节八位,其实字节码就是零和一,char和shortint都占两字节,char只有正没有负,所以阔以表示中文,但是中文有负数,中文两个字节。byte单字节。八位。int整型四字节。long长整型字节。float和double单精度和双精度,四位和八位字节,超出范围的就不是数字,NAN。除了boolean和char,其他默认值都是0,类型匹配交给虚拟机。
类加载进虚拟机过程
加载,启动类加载器,祖师爷,谁也找不到。c++实现。其他加载类都为classloader子类,双亲委派模型(类加载器收到加载类的请求先请求父类加载器,它接不,不接才轮到它接)。核心类都归祖师爷加载,扩展类加载器归前面祖师爷加载,应用类加载器归扩展类加载,后面两个加载扩展库类和应用类你写的。同一个类不同类加载器加载就是不同类。
链接,加载的类合并进虚拟机。三个步骤,验证,准备,解析。验证,语法常规检验,是否有异常故意嵌入,魔数,版本等检验。准备,静态字段分配内存,非初始化,生成符号引用,独一无二。方法表生成。解析,符号引用解析为真实引用。触发其他类的加载。
初始化,静态变量赋值。加锁初始化。常规New即可触发。
命令查看类加载顺序
java -verbose:class 类名
数组类只会加载类,不会链接和初始化。
虚拟机怎么加载方法
静态绑定和动态绑定,静态绑定,编译好的二进制文件即可直接识别。动态绑定体现在多态,重写和重载。动态绑定阔以由方法表和接口方法表关联。索引值的动态关联。重载不体现在虚拟机范围,重载可以根据类名,方法名,描述符唯一定位,描述符在虚拟机范围更加严格,比语法多了一个返回类型的限定。那就有意思了,你在编译工具语法过不去,通过修改二进制文件阔以修改通过。重载游上面解释通过,但是重写就没办法了。重写体现在父子。父子唯有类名指定。这就是动态识别,靠运行时识别。识别子类到父类到超类的过程。重写的,编译器也会编译成多个,弄成重写桥接模式。空间换时间的方法表。类的方法索引。实际是内联缓存。编译后二进制不出相关的内联助记符。这应该是虚拟机的机制。
jvm处理异常
抛下面有错误和异常,异常分运行和非运行。运行不用抓起来,语法不要求,无药可救,人工介入。finally块各个出口都有它,编译瞅瞅。二进制代码,有个异常表记录起始终结标记,跳转至哪行标记,跳转哪行助记符。这都没说到重点,异常栈轨迹。捕获里面再出异常,前面就没了,记录后者。那跟问题的记录就记录后面的,难。新版本jdk有这功能。
虚拟机与反射
反射,慢,性能差,调用15次阀值以内,委派本地方法调用,超过自己生成机器码执行,动态执行。省略了到c+的过程。动态快但是第一次生成机器码耗时。getMethod性能开销大,classforname,class.getMethod(),Method.invoke常规操作性能,验证方法的可调检查可以在虚拟机设置。
基本变量超出范围阔以设置范围,防止自动装箱。设置虚拟机参数即可。
逃逸
虚拟机内存结构
父子结构,64位,内存指针8字节,对象头(哈希,锁信息)8字节,一个整形占4字节。那一个整形就变大了4被。才有了基本类型。
内存对齐,cpu读取缓存,整行读取
压缩指针
字段重排列
jol工具查看内存
垃圾回收
应用计数法(死循环调用)
gc root可达性分析(多线程)
安全点(堆不产生对象的点),啥情况?本地方法调用,
回收方法
清除
压缩
复制
新生区,樱桃园。新村区,新村区(空)。动态比例。堆共享,按线程分配2048字节连续,不够再申请,指针累计利用到尾部。tlab技术
minor gc,樱桃园,满了,触发前面gc,把樱桃园和新村区存进下一曲,计数加1。指针对调。新村区计数到15进老年区。标记复制。不进行全表扫描,即全堆扫描。那老年代要是引用了新生代的对象呢?引入卡表,记录老年代引用新生代的记录。老年代有引用的也当做gc root.
老年区,采用标记压缩算法。都是结合使用。
full gc
内存模型
as if series 编译器代码重排序(前后无依赖)
happen before 偏序关系(强制,传递性)
内存屏障(final写写,楼上写读)
violate关键字
CPU读取数据从寄存器读,共享数据存寄存器。重排序扰乱执行顺序会读取错误数据。
即时编译器会产生重排序,解释执行不会。