深入理解JVM
2018-10-15 10:42:00 114 举报
AI智能生成
JVM(Java虚拟机)是Java技术的核心,它为Java程序提供了一个运行环境。JVM是一个虚拟的计算机,它有自己的硬件架构和操作系统,可以执行Java字节码。JVM的主要任务是加载Java类文件,验证字节码,管理内存和垃圾回收,以及执行字节码。JVM的优点是跨平台性,即同一段Java代码可以在任何支持JVM的平台上运行。此外,JVM还提供了一些高级特性,如即时编译、动态类加载和多线程等。深入理解JVM有助于我们更好地编写高效的Java程序。
作者其他创作
大纲/内容
异常
StackOverflowError
线程请求栈深度大于虚拟机允许深度
定义大量本地变量,增大方法帧中本地变量表长度
OutOfMemoryError
虚拟机栈动态扩展超过极限
不断创建对象,导致堆溢出
常量池
直接内存溢出
子主题
垃圾回收
回收内容
堆、方法区
对象回收判断
引用计数法
不能解决循环引用
可达性分析算法
可以作为GC Roots的
栈帧本地变量表中引用的对象
方法区静态属性引用对象
方法区中常量引用对象
本地方法帧中引用对象
引用类型
强引用strong reference
不会回收
软引用soft reference
溢出才回收
弱引用weak reference
下一次被回收
虚引用phantom reference
方法区的回收
回收废弃常量、无用类
没有被引用的常量
无用类
该类所有实例被回收
加载该类的classloader已被回收
class对象没有被引用
垃圾收集算法
标记-清除算法
效率低
产生碎片多
复制算法
简单高效
浪费一半内存
标记-整理算法
分代收集
垃圾收集器
Serial
收集时暂停其他所有线程
简单高效
单线程
新生代复制算法、老年代标记-整理算法
ParNew
多线程
Parallel Scavenge
新生代复制算法
吞吐量优先
自适应调节
Serial Old
老年代-标记整理算法
CMS
标记-清除算法
并发收集-低停顿
G1
并发和并行
分代收集
标记-整理算法
将堆分为多个大小相等的region
内存分配
对象优先在Eden分配
大对象直接进入老年代
大对象:需要大量连续空间的对象
长期存活对象进入老年代
出生时位于Eden,一次MinorGC存活后进入Survivor,每经历一次GC,年龄加一,年龄达到15进入老年代
当Survivor中相同年龄的对象大小总和大于Survivor的一半,则这些对象进入老年代
常用工具
jps:JVM Process Tool:显示指定系统内所有的虚拟机进程
jstat:JVM Statistics Monitoring Tool 收集HotSpot虚拟机各方面的运行数据
jinfo:Configuration Info for Java 显示虚拟机配置信息
jmap:Memory Map for java 内存转储快照
jhat:JVM Heap Dump Browser 用于分析heapdump文件,建立一个HTTP/HTML服务器让用户浏览
jstack:Stack Trace for Java 显示虚拟机的线程快照
方法调用
含义:确定被调用的方法的版本
分类
解析
编译期可知,运行期不变:在类加载时的解析阶段,从常量池把符号引用转化为直接引用
非虚方法
静态方法
私有方法
实例构造器
父类方法
分派 dispatch
静态类型与实际类型
静态分派:依赖静态类型来定位方法执行版本的分派
发生在编译阶段
典型是方法重载
方法根据参数,自动选择最符合的版本,可能发生自动转换
(char->int->long->float->double)
可能发生自动装箱、转换为实现的接口
继承按从下到上的顺序
动态分派:依赖实际类型来确定方法执行版本的分派
典型是重写
发生在运行期间
编译期
编译器
前端编译器:把java转成class
解析和填充符号表
词法、语法分析:字符流转换成标记集合;根据标记序列构造抽象语法树
填充符号表:符号表是一组符号地址和符号信息构成的表格
注解处理
语义分析和字节码生成
标注检查
检查声明、变量、赋值
常量折叠
数据及控制流分析
局部变量是否赋初值
方法的每条路径是否有返回值
受查异常是否被处理
解语法糖
语法糖:提升编码效率
字节码生成
把实例构造器方法、类构造器方法添加到语法树
默认构造器
语句块、变量初始化、调用父类构造方法收敛到构造器方法
优化方法:String的append
把前面生成的信息(语法树、符号表)转换为字节码
后端运行期编译器:字节码转换成机器码
即时编译器 JIT
把热点代码翻译成平台相关的机器码
热点代码
被多次调用的方法
被多次执行的循环体
热点探测
基于采样:周期性检查各个线程的栈顶,如果一个方法经常出现在栈顶,则为热点方法
基于计数器:为每个方法建立计数器,执行则加1,超过阈值,则为热点方法
解释执行:节约内存
编译执行:提升效率
平台无关的前端将字节码构造成高级中间代码HIR
平台相关的后端从HIR产生低级中间代码LIR
平台相关后端使用线性回归扫描算法在LIR上分配寄存器、peephole优化,产生机器码
静态提前编译器:java转换成本地机器码
语法糖
泛型与类型擦除
泛型类型会被擦除,不能重载
自动装箱、拆箱与遍历循环
条件编译
使用条件为常量的if
编译器把不成立的代码块消除
编译时对代码优化(解释执行不会)
方法内联
目的
去除方法调用的成本(建立栈帧)
方法内联膨胀后便于后续优化
把多个方法合在一起
冗余访问消除
去掉中间变量
目的:减少对象访问
复写传播
去掉相同变量
公共子表达式消除
一个表达式已经计算过了,且变量值没有发生变化,直接用前面的结果代替
数组范围检查消除
逃逸分析
方法逃逸:方法里的对象被外部方法引用
如果一个对象不逃逸可以进行以下优化
栈上分配
把对象内存放到栈上,随栈帧的销毁而销毁
同步消除
多线程不会访问到,可以把同步限制消除
标量替换
把对象分解成标量,从而可以被放在寄存器中
分析是否逃逸很耗时
内存模型与线程
运行时数据区
程序计数器PCR
线程私有
虚拟机栈VM stack
线程私有
生命周期与线程相同
每个方式执行会创建一个栈帧
局部变量表:编译期可知的各种基本数据结构、对象引用、返回地址
存放方法参数及方法内的局部变量
操作数栈
动态链接:指向运行时常量池中该栈帧所属方法的引用
方法出口:储存返回地址
退出方法的方式
正常完成出口
异常完成出口,不会返回值,返回地址通过异常处理器表来确定
退出过程
1)恢复上层方法的局部变量表和操作数栈
2)把返回值压入调用者的栈帧的操作数栈中
3)调整PC计数器指向下一条指令
附加信息
本地方法栈 Native Method Stack
线程私有
与虚拟机栈一样,只是服务的方法类型不一样
堆Heap
线程共享
占内存最大的一块
存放对象实例、数组
垃圾回收的主要区域
方法区Method Area
线程共享
储存已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
常量池
编译器生成的各种字面量和符号引用
对象的创建
步骤
1)是否能在常量池中找到类符号引用,并检查是否已被加载、解析、初始化,如果没有则进行类加载
2)分配内存
内存规整:指针碰撞
内存不规整:空闲列表
线程安全:本地线程分配缓冲TLAB,每个线程一个空间,不干涉
3)对象设置
属于哪个类
元数据
哈希码
GC分代年龄
对象内存布局
对象头
运行时数据(Mark Word)
哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
类型指针
确定是哪个类的实例
实例数据
代码中定义的各种类型字段内容(包括从父类继承的)
对齐填充
占位符
对象的访问定位
通过栈中局部变量表中对象引用
方式
使用句柄
直接指针
调优案例与实战
问题
高性能硬件上的部署策略
问题:大内存使得GC停顿时间变长
控制FGC频率关键:大多数对象生存周期不应太长,不能有成批量、长生存时间的大对象产生
集群间同步导致内存溢出
堆外内存溢出
除了堆和方法区,还有这些地方占内存
Direct Memory
线程堆栈
Socket缓存区
虚拟机和GC
内部命令导致系统缓慢
调用外部命令,虚拟机会克隆一个和当前虚拟机一样的进程,耗CPU、MEM
服务器JVM崩溃
异步调用另一个服务器上的服务,响应缓慢,导致请求积压
不恰当数据结构导致内存占用过大
调优
类加载时间
编译
GC时间
扩大新生代-防止YGC
扩大老年代-防止FGC
类文件结构
class文件是一组8位字节为基础单位的二进制流,大端
数据类型
无符号数
u1、u2、u4、u8表示长度为1、2、4、8个字节(8位))
表示数字、索引引用、数量值、字符串值
表
组成
0~3字节:魔数:文件类型
4~7字节:jdk本号
常量池
字面量:常量字符串、final常量值
符号引用
类和接口的fully Qualified Name
字段的方法和描述符
方法的名称和描述符
u2访问标志:类/接口、public、final、abstract
继承关系
u2类索引:类的全限定名
u2父索引:父类的全限定名
nu2+1接口索引:实现接口的全新定名
字段表集合:描述接口、变量
u2访问标志
u2 name_index
u2 descriptor_index
u2 attributes_count
u2 attributes
方法表集合:描述方法
属性表集合
code属性
exception属性
LineNumberTable属性
LocalVariableTable属性
sourceFile属性
constantvalue属性:通知虚拟机自动为静态变量赋值
innerClass属性
Deprecated和Synthetic属性
stackMapTable属性
Signature属性:记录泛型信息
BootstrapMethod属性
类加载机制
类型的加载、连接和初始化过程都是在程序运行期间完成的:增加开销,增加灵活性
运行时再指定接口的实现类
类可以从网络或其它地方加载
过程
加载
过程
通过类的全限定名来获取定义此类的二进制流
从jar、war包中获取
从网络中获取:Applet
运行时计算生成:动态代理技术
其他文件生成:JSP
将字节流代表的静态储存结构转化为方法区的运行时数据结构
在内存中生成一个Class对象,作为方法区该类各种数据的访问入口
加载与连接的部分内容是交叉进行的
验证
目的:确保Class文件包含的信息符合虚拟机要求
防止访问数组边界以外的数据
防止将对象转型为它并未实现的类型
防止跳转到不存在的代码行
检验动作
文件格式验证
是否以0xCAFEBABE开头
主、次版本号是否当前虚拟机支持
常量池中常量是否有不被支持的类型
索引值是否指向不存在的常量
元数据验证
类是否有父类
是否继承了不允许被继承的类
非抽象类是否实现了其父类或接口的方法
是否覆盖了父类的final字段
字节码验证
字节码操作数栈的数据类型与指令代码序列能正常工作,不会用long的命令操作int
跳转指令不会跳转到方法体之外的指令上
类型转换是有效的
符号引用验证(发生在解析阶段)
符号引用中通过全限定名是否能找到对应的类
在指定类中是否符合方法的字段描述符及简单名称所描述的方法和字段
符号引用中的类、字段、方法是否能被当前类访问
准备
正式为类变量分配内存并设置类变量零值的阶段(final的变量会赋实际值)
解析
将常量池内符号引用替换为直接引用的过程
符号引用:符号表示的,目标不一定已加载在内存中,与虚拟机内存布局无关
直接引用:指针、偏移量或句柄。目标一定加载在内存中,与虚拟机内存布局有关
分类
类/接口的解析
不是数组类型:当前类的类加载器得到目标类的全限定名
数组类型:先加载数组元素类型
符号引用验证
字段解析
1、类只包含简单名称和字段描述符与目标相匹配的字段,则返回这个字段的直接引用
2、否则,如果类实现了接口,按照继承关系从下往上递归搜索各个接口重复1
3、否则,如果不是java.lang.Object,按照继承关系从下往上递归搜索各个父类重复1
4、否则,查找失败
类方法解析
接口方法解析
初始化
执行类构造器<clinit>()方法
<clinit>()方法是由编译器自动收集类中所有变量的赋值动作、静态语句块中的语句合并产生的
子类的<clinit>()执行之前,父类<clinit>()一定执行完成
不会执行父接口的<clinit>(),除非要使用父接口中定义的变量
多线程同时初始化一个类,只有一个会去执行<clinit>(),其余挂起
初始化条件
初始化有且仅有:
遇到new、getstatic、putstatic、invokestatic字节码时
使用new实例化对象
调用一个类的静态方法
读取、设置一个类的静态字段
使用java.lang.reflect包时,类没有初始化,则初始化
初始化子类,而其父类没有被初始化时
主类
使用JDK1.7动态语言
被动引用不初始化
通过子类引用父类的静态字段,不会导致子类初始化
通过数组定义来引用类
引用类的常量,不会初始化该类,因为常量在编译阶段存入了常量池
使用
卸载
类加载器:外部实现的“通过类全限定名获取描述此类的二进制字节流”的代码
同一个类,被不同类加载器加载,这两个类不相等(equals、instanceof)
分类(从上到小,组合)
启动类加载器
扩展类加载器
应用程序类加载器
自定义类加载器
只有上层无法加载,子才会加载
0 条评论
下一页