导图社区 JVM概况图
JVM是Java虚拟机的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。下图带你深入理解Java虚拟机与JVM高级特性与最佳实践,内容涵盖了走进java、自动内存管理机制、虚拟机执行子系统、程序编译与代码优化、高效并发。
编辑于2019-01-23 02:04:36深入理解Java虚拟机 JVM高级特性与最佳实践
第一部分 走进java
第1章 走进Java
1.1 概述
面向对象的编程语言
一次编译到处运行
1.2 Java技术体系
java Card
java ME
java SE
java EE
1.3 Java发展史
1991年Janes Gosling Oak(橡树)
1995年java1.0发布
......
1.4 Java 虚拟机发展史
1.4.1 Sun Classic/Exact Vm
Sun Classic VM 世界上第一款商用Java虚拟机
Exact VM 即使编译器、编译器与解释器缓和工作
1.4.2 Sun HotSpot VM
OpenJDK
Sun JDK
1.4.3 Sun Mobile-Embedded VM /Meta-Circular VM
KVM
手机平台
CDC/CLDC HotSpot Implementation
Java ME支柱
Squawk VM
java Card
JavaInJava
不能即时编译
用Java实现的
Maxine VM
有先进的JIT编译器
有垃圾回收器
效率高接近HotSpot Client VM水平
1.4.4 BEA JRockit/IBM J9 VM
BEA JRockit VM
即使编译器编译后执行
JRockit收集器和MissionControl服务套件处于领先水平
IBM J9 VM
更名为K8
IBM AIX和z/OS 部署java应用
1.4.5 Azul VM/BEA Liquid VM
Azul VM
HotSpot 基础上大量改进
BEA Liquid VM
不需要操作系统
自身实现操作系统的必要功能
最大限度地发挥硬件的能力
1.4.6 Apache Harmoney/Goole Andriod Dalvik VM
Apache Harmoney
Apcahe 基金会旗下的
兼容与JDK1.5、JDK1.6
Goole Andriod Dalvik VM
即时编译性能很高
1.4.7 Mircrosotf JVM 以及其他
微软有windows版本的JVM
其他JVM
Jam VM
cacaovm
SableVm
Kaffe
Jelatine JVM
NanoVm
MRP
Moxie JVM
Jikes RVM
1.5 展望Java技术的未来
1.5.1模块化
java模块化系统
java模块化已经成为一项无法阻挡的变革潮流
java SE 动态组件支持
1.5.2 混合语言
Clojure
JVM支持多语言
JRuby
Groovy
等编程语言
多语言虚拟机的方向发展
1.5.3 多核并行
JDK1.5加入java.util.concurrent包实现粗粒度并行框架
JDK1.7加入java.util.concurrent,forkjoin是对框架的一个扩充
1.5.4 进一步丰富语法
JDK5 加入自动装箱、泛型、动态注释、可变长参数、遍历循环等语法
JDK8 添加Lambda表达式
函数方式编程可能会成为主流
1.5.5 64位虚拟机
支持64位虚拟机
1.6 实战:自己编译JDK
此章节是实战章节,一定要手动去编译一把
自动内存管理机制
第2章 Java内存区域和内存溢出异常
2.1 概述
垃圾回收机制交给了java
2.2 运行时数据区域
2.2.1 程序计数器
较小内存空间
行号指示器
唯一一个不会报OutOfMemoryError错误的区
线程隔离的数据区
2.2.2 虚拟机栈
栈先进后出
会抛出OOM和Stack OverflowError
每个方法执行都会创建帧
执行的是Java方法
2.2.3 本地方法栈
会抛出OOM和Stack OverflowError
执行Native方法
HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一
2.2.4 Java堆
内存中最大的一块区域
存放对象实例
垃圾收集主要工作区域
分为新生代和老年代
会抛出OOM和Stack OverflowError
new 的都是放在堆中
所有线程共享的数据区
2.2.5 方法区
会抛出OOM和Stack OverflowError
类信息、常量、静态常量、即使编译器编译后的代码等数据
垃圾回收在方法区的行为
很少但是也会执行
所有线程共享的数据区
异常定义
方法区和永久代并不等价
2.2.6 运行时常量池
会抛出OOM和Stack OverflowError
动态性
运行时进入方法运常量池
任何字符串的创建都会放在常量池中
用的比较多的是String类的intern()方法
运行时是字节码常量
2.2.7 直接内存
会抛出OOM和Stack OverflowError
可以分配对外内存
2.3 HostSpot虚拟机对象探秘
2.3.1对象创建
给对象分配内存
指针碰撞
有规则
取决于垃圾回收机制
空闲列表
无规则
线程安全问题
线程同步处理
本币线程分配缓冲TLAB
初始化对象
对基础类型字段进行初始化
执行构造方法
每个类都会执行构造方法
2.3.2 对象内存布局
对象头(Header)
运行时数据
如哈希码(HashCode )、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等。
类型指针
指向它的类元数据的指针
实例数据(Instance Data)
对象真正存在的有效信息 虚拟机分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Points)
对齐填充Padding
不是必然存在的
2.3.3 对象的访问定位
使用句柄
使用指针
第3章 垃圾收集器与内存分配策略
3.1 概述
说了下垃圾回收的目的
3.2 对象已死吗
3.2.1 引用计数算法
给对象添加一个引用计数器
实现简单,判定效率高,但JVM基本都没有使用
3.2.2 可达性分析算法
虚拟机栈(栈帧中的本地变量表)中引用对象
方法区中静态属性引用的对象
方法区中常量引用 的对象
本地方法栈中JNI(即一般说的Native方法)引用对象
3.2.3 再谈引用
3.2.4 生存还是死亡
3.2.5 回收方法区
3.3 垃圾收集算法
3.3.1 标记-清除算法
标记步骤
清除步骤
不足:效率不高,清理的内存是碎片的
3.3.2 复制算法
分为3个区一个Eden区两个Survivor区,其中两个Survivor区大小相等
Eden和Survivor 的比例大概是8:1
回收率在98%
将存活对象放在一个区中,清除Eden和Survivor区的对象
针对新生代内存比较有效
3.3.3 标记整理算法
和标记清除算法一样
将存活对象放在一端,然后清除边界外的内存
适用于老年代回收
3.3.4 分代收集算法
采用复制算法和标记整理算法一块使用
根据新生代和老年代决定用哪个算法
3.4 HotSpot的算法实现
3.4.1 枚举根节点
3.4.2 安全点
3.4.3 安全区域
3.5 垃圾收集器
3.5.1 Serial 收集器
单线程垃圾收集器
桌面应用
Stop The Word,执行此收集器的时候,其他应用都要停止
3.5.2 ParNew 收集器
多线程收集器
使用复制算法
3.5.3 Parallel Scavenge 收集器
复制算法
多线程收集器
新生代收集器
达到可控制的吞吐量
-XX:MaxGCPauseMillis停顿时间
-XX:GCTimeRatio吞吐量大小
3.5.4 Serial Old 收集器
3.5.5 Parallel Old 收集器
3.5.6 CMS收集器
CMS(Concurrent Mark Sweep)
优缺点
优点
并发收集
标记清除算法实现
低停顿
缺点
对CPU资源非常敏感
标记清除算法实现
无法处理浮动垃圾,可能出现Concurrent Mode Failure
大量空间碎片产生
初始标记
Stop The World
引用计数算法
可达性分析算法
并发标记
重新标记
Stop The World
并发清除
标记清除算法实现
3.5.7 G1收集器
G1(Garbage-First)
初始标记
并发标记
最终标记
筛选回收
优缺点
优点
并行与并发
使用多个CPU缩短Stop The World的时间
分代收集
空间整合
标记整理算法实现,不会有碎片空间
可预测的停顿
3.5.8 理解GC日志
3.5.9 垃圾收集器参数总结
3.6 内存分配与回收策略
3.6.1 对象优先在Eden分配
对象优先分配到Eden区域,当Eden区域没有内存时会发生一次Minor GC
Eden 和Survior区域的比例是8:1
Minor GC
新生代GC
非常频繁
速度快
Major GC/Full GC
一版在执行Major GC的时候会执行一次Minor GC
Major GC的速度一般会比Minor GC慢10倍以上
3.6.2 大对象直接进入老年代
大部分都是字符串及数组
提供-XX:PretenureSizeThreshold=3M,大于3M的时候直接进入老年代
避免Eden和Survivor区之间发生大量的内存复制
3.6.3 长期存活的对象将进入老年代
定义年龄计数器,Minor GC一次年龄增加1岁
默认是15岁
配置参数-XX:MaxTenuringThreshold=1
3.6.4 动态对象年龄判断
如果在survivor空间中相同年龄所有大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代
不是所有的进入老年代都要等到MaxTenUringTheshold中要求的值
3.6.5 内存分配担保
老年代空间大于新生代所有对象空间
设置参数:-XX:HandlePromotionFailure设置是否允许担保
第4章 虚拟机性能监控与故障处理工具
4.1 概述
知识经验是基础
数据是依据
运行日志
异常堆栈
GC日志
线程快照
堆转储快照
4.2 JDK命令行工具
4.2.1 jps 虚拟机进程状况工具
JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程
得到的id是本地虚拟机唯一id
-q 只输出LVMID,省略主类名称
-m 输出虚拟机进程启动时传递给main()函数的参数
-l 输出主类的全名,如果进程执行的是jar包,输出jar路径
-v 输出虚拟机进程启动时JVM参数
4.2.2 jstat
JVM Statictics Monitoring Tool 用于收集HotSpot 虚拟机各方面运行的数据
显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
4.2.3 jinfo:java配置信息工具
Configuration Info for Java 显示虚拟机配置信息
查看参数
# jinfo -flag GCLogFileSize 8807 -XX:GCLogFileSize=10485760
打开gc
jinfo -flag+PrintGCDetails 12278
关闭gc
jinfo -flag -PrintGCDetails 12278
jinfo -flag -PrintGC 12278
4.2.4 jmap
Memory Map for Java 生成虚拟机的内存转储快照(heapdump 文件)
使用方法:jmap -dump:format=b,file=test.bin 16184 这种方式可以用 jvisualvm.exe 进行内存分析, 或者采用 Eclipse Memory Analysis Tools (MAT)这个工具
获取dump的三种方法
jmap -histo:live pid这种方式会先出发fullgc,所有如果 不希望触发fullgc 可以使用jmap -histo pid
jdk启动加参数: -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=/httx/logs/dum 这种方式会产生dump日志,再通过jvisualvm.exe 或者Eclipse Memory Analysis Tools 工具进行分析
4.2.5 jhat
JVM Heap Dump Browser 用于分析heapdump文件, 它会建立一个HTTP/HTML 服务器,让用户可以在浏览器查上查看分析结果
使用方法:jhat test.bin
自启动一个内置的tomcat在网页分析
支持OQL语句
4.2.6 jstack
Stack Trace for java 显示虚拟机的线程快照
线程出现停顿、死锁、循环、请求外部资源导致过长等原因
4.2.7 HSDIS:JIT生成源代码反汇编
4.3 JDK的可视化工具
4.3.1 Jconsole:Java监视与管理控制台
4.3.2 VisualVM :多合一处理工具
第5章 调优案例分析与实践
5.1 概述
知识
工具
数据
经验
必须要自己多看多练
5.2 案例分析
5.2.1 高性能硬件上的程序部署策略
子主题
虚拟机执行子系统
第6章 类文件结构
6.1 概述
计算机只认识0和1
6.2 无关性的基石
一次编译到处运行
Groovy JRuny Jython 都可以JVM中运行
虚拟机不关心class是谁产生的
6.3 CLass 类文件的结构
6.3.1 魔数与Class文件的版本
class头的4个字节成为魔数
魔数后四个字节是版本号信息,存储是16进制,换算为10进制看对应的版本号
6.3.2 常量池
常量池的数字在版本号后面,且是占有两个字节
这一块没有看清原理
使用javap -verbose 类名 输出信息,方便查询
cp_info
6.3.3 访问标志
是类还是接口
可以看flags
定义是不是public
是否定义abstract
是否是final
access_flags有16个标志位能用,当前使用8个,没有用到的的标志位一律为0
6.3.4 类索引、父类索引与接口索引集合
6.3.5 字段表集合
6.3.6 方法表集合
6.3.7 属性表集合
1 code属性
11 BootstrapMethods属性
3 lineNumberTable属性
4 LocalVariableTable属性
5 SourceFile 属性
2 Exceptions 属性
7 InnerClasses属性
8 Deprecated及Synthetic属性
9 StackMapTable属性
10 Singnature属性
6 Con斯坦Value属性
6.4 字节码指令简介
6.4.1 字节码与数据类型
6.4.2 加载和存储指令
6.4.3 运行指令
就是将基础运算符进行转化为指令,方便计算机计算
6.4.4 类型转换指令
注意越界
6.4.5 对象创建与访问指令
a代表引用类型
6.4.6 操作数栈管理指令
6.4.7 控制转移指令
6.4.8 方法的调用和返回指令
6.4.9 异常处理指令
6.4.10 同步指令
monitorenter
monitorexit
第7章 虚拟机类加载机制
7.1概述
java虚拟机加载完字节码才能返回结果
7.2类加载时机
有7个小阶段 加载,连接(验证,准备,解析)、卸载、使用、初始化
有切仅有5种对类进行初始化
遇到new、getstatic、putstatic或者invokestatic这四个指令时
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类 没有进行过初始化,则需要先触发其初始化
当初始化一个类的时候,如果发现其父类还没有初始化,则需要先 触发其父类的初始化
当虚拟机启动时,用户需要制定一个执行的主类(包括main()方 法的那个类)虚拟机会优先初始化这个类
在初次调用java.lang.invoke.MethodHandle实例时,通过java虚 拟机解析出类型是REF_getStatic,REF_puStatic,REF_invokeStatic 的方法句柄时
7.3类加载过程
7.3.1加载
通过一个类的全限定名来获取定义此类 的二进制流
从ZIP中获取
从网络中获取
运行时计算生成
其他文件生成
数据库读取
将这个字节流所代表的静态存储结构转 化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang .Class对象,作为方法区这个类的各种 数据访问接口
7.3.2验证
文件格式验证
魔数是否是以0xCAFEBABE开头
主次版本号是否在虚拟机处理范围
常量池是否有不支持的常量,和是 否有不存在的常量
UTF8编码
Class文件中信息
元数据验证
这个类是否有父类
这个类是否继承了不允许的继承的类
类不是抽象类,是否实现了其他类 或者接口之中要实现的所有方法
类中字段方法是否与父类产生矛盾
字节码验证
最复杂的校验
符号引用验证
7.3.3准备
分配内存空间
变量的初始值
记一下各个类型初始化值
7.3.4解析
类或接口的解析
字段解析
类方法解析
接口方法解析
7.3.5初始化
类加载的最后一步
执行类构造器<clinit>()方法的过程
7.4类加载器
7.4.1 类与类加载器
通过一个类的全限定名来获取描述此类的二进制字节流
启动类加载器
扩展类加载器
应用程序类加载器
自定义类加载器
高度灵活性
代码加密
类加载器可以实现热部署
7.4.2 双亲委派模型
首先会检查请求加载的类是否已经被加载过
若没有被加载过
递归调用父类加载器的loadClass()
父类加载器为空后就使用启动类加载器加载
如果父类加载器和启动类加载器均无法加载请求,则调用自身的加载功能
JDK1.2以后才有双亲委派模型
7.5本章小结
第8章 虚拟机字节码执行引擎
8.1 概述
8.2 运行时栈帧结构
8.2.1 局部变量表
最小值是变量槽slot
8.2.2 操作数栈
后入先出(LIFO)
8.2.3 动态链接
8.2.4 方法返回地址
8.2.5 附件信息
8.3 方法调用
8.3.1 解析
invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法、私有方法和父方法
invevirtual:调用所有虚拟方法
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
invokedynamic
8.3.2 分派
静态分派调用
动态分派调用
8.3.3 动态类型语言支持
第9章 类加载及执行子系统的案例与实践
程序编译与代码优化
第10章 早期(编译期)优化
第11章 晚期(运行期)优化
高效并发
第12章 Java内存模型与线程
12.1 概述
衡量服务性能高低好坏
每秒处理事务数 TPS(Transsactions Per Second)
12.2硬件效率与一致性
计算机存储和处理器之间有缓存
计算机访问缓存协议有
MSI
MESI
MOSI
Synapse
Dragon
Protocol