深入理解JVM
2021-03-30 16:38:32 1 举报
AI智能生成
周志明 《深入理解JVM》
作者其他创作
大纲/内容
制造内存溢出
栈异常
线程请求栈深度大于虚拟机允许深度,默认参数下1000-2000 SOF
定义大量本地变量,增大方法帧中本地变量表长度 SOF
堆异常
不断创建对象,导致堆溢出
方法区异常
常量池导致
借助CDLIB等不断动态生成类
直接内存
计算得知内存无法分配,于是抛出异常,没有向操作系统申请分配内存
程序计数器
不会有异常hhhh
垃圾回收
判断是否是垃圾
堆的对象回收判断
引用计数法
不能解决循环引用
可达性分析算法
可以作为GC Roots的
栈帧本地变量表中引用的对象
方法区静态属性引用对象
方法区中常量引用对象
本地方法帧中引用对象
引用类型
强引用strong reference
不会回收
软引用soft reference
溢出才回收
弱引用weak reference
下一次被回收
虚引用phantom reference
finalize()——最后一次机会
重写finalize()且未被调用过的finalize()方法将被判定为有必要执行
方法区的回收判断
废弃常量
没有被引用的产量
无用类
该类所有实例被回收
加载该类的classloader已被回收
class对象没有被引用
垃圾收集算法
标记-清除算法
效率低
产生碎片多
复制算法
简单高效
浪费一半内存
标记-整理算法
分代收集
新生代
老年代
hotspot的算法实现
准确式GC
垃圾收集器
新生代(都是复制算法)
Serial
收集时暂停其他所有线程
简单高效
单线程
ParNew
多线程
唯一能与CMS配合工作
Parallel Scavenge
吞吐量优先
自适应调节+XX:UserAdaptiveSizePolicy
老年代(除了CMS都是标记整理算法)
Serial Old
适合client
子主题
CMS顶不住的时候,它来顶
Parallel Old
只能与Parallel Scavenge配合使用
CMS
优点
并发收集-低停顿
三大缺点
对CPU资源敏感,CPU个数少时,可能导致用户程序执行速度降低50%
无法处理浮动垃圾,导致Concurrent Mode Failure
标记清除算法导致的大对象没有空间分配,不得不触发FUll GC
G1
并发和并行
分代收集
标记-整理算法
将堆分为多个大小相等的region
内存分配
对象优先在Eden分配
大对象直接进入老年代
-XX:PretenureSizeThreshold,大于这个设置值得对象直接在老年代分配(只对serial和ParNew有效)
长期存活对象进入老年代
出生时位于Eden,一次MinorGC存活后进入Survivor,每经历一次GC,年龄加一,年龄达到15进入老年代
当Survivor中相同年龄的对象大小总和大于Survivor的一半,则大于等于这个年龄的对象进入老年代
空间分配担保(保得住,Minor GC ,保不住,Full GC)
老年代连续空间大于新生代对象总大小
老年代连续空间大小大于历次晋升的平均大小
流程
子主题
大对象直接进入老年代
长期存活对象进入老年代
空间分配担保(保得住Minor GC)
长期存活对象进入老年代
空间分配担保(保得住Minor GC)
常用工具
jps:JVM Process Tool:显示指定系统内所有的虚拟机进程
-v 输出启动时JVM参数
-l 输出主类全名
jstat:JVM Statistics Monitoring Tool 收集HotSpot虚拟机各方面的运行数据
jstat -gc 2764 1000 10
jinfo:Configuration Info for Java 显示虚拟机配置信息
jinfo -flag +PrintGCDetails 12760
jmap:Memory Map for java 内存转储快照 (堆的相关信息和操作)
jmap -heap 12760
jmap -dump:format=b,file=文件名 [pid]
jhat:JVM Heap Dump Browser 用于分析heapdump文件(基本不会用到)
jstack:Stack Trace for Java 显示虚拟机的线程快照
jstack -l 12768
方法调用
含义:确定被调用的方法的版本
分类
解析
编译期可知,运行期不变:在类加载时的解析阶段,从常量池把符号引用转化为直接引用
非虚方法
静态方法
私有方法
实例构造器
父类方法
分派 dispatch
静态类型与实际类型
静态分派:依赖静态类型来定位方法执行版本的分派
发生在编译阶段
典型是方法重载
方法根据参数,自动选择最符合的版本,可能发生自动转换
(char->int->long->float->double)
可能发生自动装箱、转换为实现的接口
继承按从下到上的顺序
动态分派:依赖实际类型来确定方法执行版本的分派
典型是重写
发生在运行期间
内存区域
运行时数据区
线程共享
堆Heap
占内存最大的一块
存放对象实例
垃圾回收的主要区域
方法区Method Area
储存已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
常量池
编译器生成的各种字面量和符号引用
字符串常量池
运行时常量池
运行期间也可以将常量放入池中,常见应用String intern()方法
线程私有
程序计数器PCR
当前线程执行字节码的行号指示器
Q:为什么需要程序计数器
A:CPU线程切换回来能定位下一条执行语句
虚拟机栈VM stack
生命周期与线程相同
每个方法执行会创建一个栈帧
局部变量表:编译期可知的各种基本数据结构、对象引用、返回地址
存放方法参数及方法内的局部变量
操作数栈
动态链接:指向运行时常量池中该栈帧所属方法的引用
方法出口:储存返回地址
退出方法的方式
正常完成出口
异常完成出口,不会返回值,返回地址通过异常处理器表来确定
退出过程
1)恢复上层方法的局部变量表和操作数栈
2)把返回值压入调用者的栈帧的操作数栈中
3)调整PC计数器指向下一条指令
附加信息
本地方法栈 Native Method Stack
与虚拟机栈一样,只是服务的是native方法
直接内存
对象的创建
步骤
1)是否能在常量池中找到类符号引用,并检查是否已被加载、解析、初始化,如果没有则进行类加载
2)分配内存
内存规整:指针碰撞
内存不规整:空闲列表
线程安全方案1:本地线程分配缓冲TLAB,每个线程一个空间,不干涉 通过-XX:+/- UseTLAB开关
线程安全方案2:CAS加失败重试保证
3)对象设置
属于哪个类
元数据
哈希码
GC分代年龄
对象内存布局
对象头
运行时数据(Mark Word)
哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
类型指针
确定是哪个类的实例
实例数据
代码中定义的各种类型字段内容(包括从父类继承的)
对齐填充
占位符
对象的访问定位
通过栈中局部变量表中对象引用
方式
使用句柄
包含:1、实例数据的引用 2、对象类型数据的引用
优点:对象移动(由于垃圾收集,挺频繁)时,只需要改变句柄池中实例对象的指针,不需要改变栈中引用
直接指针(Hotspot采用)
指向堆中的对象实例,对象实例又有指针指向类型数据
优点:句柄有三次指针寻址,直接指针只有两次
调优案例与实战
问题
高性能硬件上的部署策略
问题:大内存使得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)
分类(从上到小,组合)
启动类加载器
扩展类加载器
应用程序类加载器
自定义类加载器
只有上层无法加载,子才会加载
面试题
背景:一个实时分析引擎系统,每分钟可处理100个请求,每个请求需处理1w条数据,每个请求耗时10s,部署在4核8g的服务器上,采用parnew+cms垃圾回收器。其中堆内存3G,young 1.5g,old 1.5g,eden 1.2g,s1,s2 100mb,分析该系统存在的问题,并给出调优方案。
子主题
0 条评论
下一页