jvm知识体系
2020-05-18 13:40:09 3 举报
AI智能生成
JVM知识体系
作者其他创作
大纲/内容
jvm
内存管理
运行时数据区
线程私有
程序计数器
当前线程所执行的字节码的行号指示器。
java虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)[插图]用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小
本地方法栈
与虚拟机栈类似,为虚拟机使用到的native方法服务
线程共有
java堆
几乎所有对象实例在这里分配
垃圾收集器管理的主要区域
-XX:+HeapDumpOnOutOfMemoryError 内存溢出时dump当前内存堆快照
方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数
运行时常量
存放编译生成的字面量和符号引用
并不一定编译期产生
String.intern()
-XX:PermSize -XX:MaxPermSize 设置方法区大小
stackOverFlow
线程请求的栈深度大于虚拟机允许的深度
outOfMemory
扩展时无法申请到足够的内存
对象内存布局
对象头
mark word
元数据指针
通过这个指针确定这个对象属于哪个类
非必须存在的,查找元数据并非一定要通过对象本身
实例数据
字段内容
包括父类继承的
对齐填充
非必要存在
保证对象大小为8字节整数倍
垃圾回收
垃圾判断算法
引用计数法
给对象添加一个引用计数器,被引用时加1,失效时减1
难以解决循环引用问题
可达性分析算法
gc roots对象作为起点,判断一个对象是否gc roots可达
可以作为gc roots的对象
虚拟机栈中引用对象
方法区静态属性引用对象
方法区常量引用对象
本地方法栈JNI(native方法)引用对象
finalize()
finalize不执行的情况
当前对象没有覆盖finalize
finalize已经被调用过
若finalize有必要执行
该对象加入F-Queue队列,后续由低优先级的Finalizer线程去执行
finalize是对象逃脱回收的最后一次机会,只要将this与引用链上任何一个对象建立关联即可
垃圾回收算法
标记清除(mark-sweep)
过程
标记出所有要回收的对象
统一回收被标记对象
缺点
标记、清除效率都不高
产生大量不连续碎片
分配较大对象时无法找到足够的连续内存提前触发另一次垃圾回收
复制算法(copying)
内存按容量划分为相等的两块
当一块用完,就将活着的对象复制到另一块
然后把使用过的内存一次清理
优点
实现简单,运行高效,没有碎片产生
商业虚拟机用来回收新生代
缺点
可用内存缩小一半
标记整理(mark-compact)
标记过程与标记清除算法一样
所有存活对象向一端移动,然后清理掉边界以外的内存
分代收集
新生代
复制算法
年老代
标记清除
标记整理
hotspot算法实现
gc roots 可达性分析
枚举根节点(必须gc停顿)
分析过程中对象引用关系不能发生变化
Oop-Map:类加载完成时候保存内存偏移量上的数据类型,辅助完成gc roots枚举
程序只有执行到安全点才暂停
垃圾收集器
Serial
虚拟机client模式下默认新生代收集器
简单高效
没有线程交互开销
停顿时间短
必须暂停所有工作线程
单线程
ParNew
serial多线程版本,虚拟机server模式下首选新生代收集器
除了serial只有他能与CMS配合
默认线程数与cpu数相同
单cpu不会比serial效果好
调优
-XX:+UserParNewGC 指定使用
-XX:ParallelGC-Threads指定线程数
Parallel Scavenge
新生代收集器,多线程
目标控制吞吐量
吞吐量:即吞吐量 = 运行用户代码时间 /(运行用户代码时间 +垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
-XX:MaxGCPauseMills 最大垃圾收集停顿时间
GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的
-XX:GCTimeRatio 设置吞吐量大小
Serial Old
serial 老年代版本,单线程,client模式下虚拟机使用
标记整理算法
Parallel Old
Parallel Scavenge 老年代版本,多线程
CMS (concurrent mark sweep)
最短回收停顿时间,适用于重视响应速度的服务端
初始标记
GC 停顿
标记gc roots 能够直接关联的对象,速度快
并发标记
gc roots tracing 过程
重新标记
修正并发标记期间程序运行导致标记变动的对象,速度慢
并发清除
并发收集
低停顿
对cpu敏感,占用cpu,导致应用程序变慢,中吞吐量降低
无法处理浮动垃圾(floating garbage)
浮动垃圾:出现在标记过程后的垃圾,只能下次gc处理
不能等年老代几乎满了在回收,需要预留一部分空间给应用程序使用
默认占用92%后触发
产生Concurrent mode failure :CMS运行期预留内存无法满足程序需要
启动SerialOld重新收集年老代,停顿时间拉长
产生碎片
标记清除算法
G1(Garbage-First)
希望替换掉CMS
并行并发,缩短stop the world的时间
分代收集,不需要其他收集器配合,独立管理gc堆
空间整合:不产生空间碎片
可预测停顿:可以指定长度为M毫秒的时间片内,消耗在gc上的时间不超过N毫秒
维护优先列表,每次根据允许手机的时间,回收价值最大的region
避免全堆回收
堆布局
堆划分为大小相等的独立区域(region),保留新生代年老代概念
标记与gc roots直接关联的对象
从gc roots 开始对堆中对象做可达性分析
最终标记
修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
保存在remembered set logs
gc停顿
筛选回收
对各个region的回收价值和成本排序
根据用户期望gc停顿时间制定回收计划
gc 日志
Full GC/Major GC
说明发生了gc停顿(STW:stop the wrold)
发生在老年代
调优参数
内存分配与回收策略
新生代eden区
大多数情况新对象在eden区
当eden区没有足够空间是,发起minor gc(新生代垃圾收集)
采用复制算法收集
老年代
大对象(需要大量连续空间的对象,比如长字符串或者数组)直接进入老年代
长期存活对象进入老年代
每个对象有个年龄计数器,如果对象在eden出生并且经过minor gc后仍然存活,并且能被survivor容纳,对象年龄设为1
对象在survivor中每经过一次minor gc 年龄加1
年龄增加到阀值会晋升到年老代
-XX:MaxTenuringThreshold
不一定要对象年龄必须达到MaxTenuringThreshold才进入老年代
如果survivor空间相同年龄所有对象总和大于survivor空间一半
年龄大于等于改年龄的对象直接进入老年代
jdk 工具
jps(jvm process status)
列出正运行的虚拟机进程
jps [options] [hostid]
jstat(jvm statistics monitoring tool)
监视本地或远程虚拟机运行状态信息
类装载
内存
垃圾收集
jit编译
jstat [option vmid [interval[s|ms] [count]]]
interval:查询间隔
count:查询总次数
jinfo(configuration info for java)
实时查看和调整虚拟机参数
-sysprops 打印System.getProperties()
-flag name=value 设置虚拟机参数值
jmap(memory map for java)
生成heapdump(堆转储快照)
jmap[option] vmid
jhat(jvm heap analysis tool)
与jmap搭配使用,内置http/html服务器,可以将dump分析结果在浏览器查看
实际工作中不会在生产服务器分析dump
jstack(stack trace for java)
生产虚拟机当前线程快照(theaddump/javacore)
用来定位线程出现长时间停顿原因,线程死锁、死循环、请求外部资源长时间等待等
jstack[option] vmid
Class文件结构
简介
任何一个Class文件都对应唯一一个类或接口定义
8字节为基础单位的二进制流
组成
魔数(magic number)
前4字节
唯一作用:确定能否被虚拟机接受(图片格式也有魔数,入gif、jpeg)
不用扩展名来识别,因为扩展名可以随意改动
版本号
魔数后4个字节
5、6字节为次版本号(Minor Version)
7、8字节为主版本号(Major Version)
虚拟机拒绝执行超过其版本号的Class
常量池
紧接着版本号后,长度不固定
class文件中资源仓库
类型
字面常量(literal)
文本字符串
final 常量值
符号引用(symbolic references)
类和接口的全限定名(fully qualified name)
字段的名称和描述符(descroptor)
方法名和描述符
访问标志
紧接着常量池两个字节,access_flags
类还是接口、是否public、是否abstract、是否final
类索引、父类索引、接口索引集合
按顺序排列在访问标志后
类索引:确定类的全限定名
父索引:确定父类全限定名
接口索引集合:实现接口列表
字段表集合
描述接口或者类中声明的变量
类级变量
static修饰
实例级变量
access_flag
字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)
name_index(名称索引)
对常量池引用
descriptor_index(描述符索引)
描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值
方法表集合
描述接口或类声明的方法
name_index
descriptor_index
attributes
Class文件中方法的特征签名包括返回值,即两个方法出了返回值其他都一样也可以共存于Class文件中
属性表(attribute_info)集合
属性类型
code
储存字节码指令
max_stack
操作数栈深度最大值
LineNumberTable
描述java源码行号与字节码行号对应关系
LocalVariableTable
描述栈帧局部变量表变量与java源码变量的关系
SourceFIle
记录生成该Class的源码文件名
ConstantValue
通知虚拟机自动为静态变量赋值
只有static修饰的才使用这里个属性
InnerClasses
记录内部类与宿主类之间的关联
Signature
保证运行期反射获取泛型信息
BootstrapMethods
用于保存invockedynamic指令引用的引导方法限定符
invockedynamic运行时动态解析调用点限定符所引用的方法
用于实现Lambda表达式
类加载机制
加载过程
加载
根据全限定名获取该Class的二进制字节流
从zip包读取,如jar、war
网络中读取,applet
运行时生成,动态代理,reflect
将字节流代表的静态储存结构转化为方法区运行时数据结构
在内存中生成该Class对象,作为方法区该类的数据访问入口
唯一可以由用户自定义类加载器参与的阶段
验证
确保Class文件字节流包含的信息符合当前虚机要求不会危害虚机自身安全
文件格式验证
基于二进制字节,只有通过该阶段,才会进入内存方法区,后续验证阶段都在方法区进行不操作字节流
魔数
主次版本号
常量池是否有不支持的常量
元数据验证
语义校验,保证符合java语言规范
是否有父类
是否继承了final修饰的类
是否实现了父类要求实现的所有方法(interface)
类中字段、方法是否与父类产生矛盾,是否覆盖final字段,方法重载是否合法
字节码验证
确保语义合法、符合逻辑,保证方法在运行时不会危害虚机安全
符号引用验证
对常量池中各种符号引用进行匹配性校验
符号引用中的全限定名能否找到对应的类
NoSuchFieldError
NoSuchMethodError
符号引用的类、字段、方法是否有访问权限
IllegalAccessError
准备
为类变量(static修饰)分配内存并设置变量初始值
只在方法区
public static int value = 123
准备阶段过后,value的值为0,因为此时未执行任何java方法
public static final int value = 123
存在字段属性表constantValue中,准备阶段就会赋值为123
解析
将常量池的符号引用替换为直接引用
符号引用
使用时能无歧义的定位到目标的一组符号
与虚拟机的内存布局无关
引用目标不一定已经加载到内存
直接引用
直接指向目标的指针
与虚拟机内存布局相关
引用目标一定已经加载到内存
invokedynamic特例
动态调用点限定符
程序执行到这条指令时解析动作才进行
用于动态语音支持
Lambda表达式
初始化
真正开始执行java字节码
初始化阶段就是执行<clinit>()过程
<clinit>()(class init)
由编译器自动收集所有类变量的赋值动作和静态语句块合并产生
编译器收集的顺序是有语句在源文件中出现的顺序决定的
静态语句块只能访问定义在静态语句块之前的变量,定义在之后的变量,可以赋值,不可访问
父类clinit先执行
接口例外
接口或者实现类的clinit执行时不用执行父接口的clinit
只有当父接口中定义的变量使用时,父接口clinit会执行
若类中没有静态语句块,也没有类变量赋值,编译器可以不生成clinit()
多线程
多线程同时初始化同一个类,只有一个线程会执行clinit方法,其他线程等待,直到该线程退出
如果clinit耗时很长会导致多个线程阻塞
类加载器
判断两个类是否“相等”,必须在同一个类加载器加载的情况下
equals
isAssignableFrom
isInstance
instanceof
启动类加载器(bootstrap classloader)
加载<java_home>/lib下的,按照文件名shibie
-Xbootclasspath指定加载路径
扩展类加载器(extesion classloader)
加载<java_home>/lib/ext目录
应用程序类加载器(application classloader)
ClassLoader.getSystemClassLoader()返回
加载classpath指定类库
双亲委派
检查是否加载过
调用父类加载器loadClass()
若父加载器抛出classNotFoundException,再调用自己的findClass()
非强制性约束
解决基础类调用用户代码
JNDI(Java Naming and Directory Interface)
JDBC
线程上下文类加载器(ThreadContext Classloader)
Thread.setContextClassLoader
完成父类加载器请求子类加载器完成类加载动作
代码热替换(hotSwap)模块热部署(hot deployment)
OSGI(Open Service Gateway Initiative)
JMM(java memory model)
屏蔽硬件和操作系统内存访问差异,实现java在各种平台都能一致性内存访问效果
所有变量(指线程共有的对象,如实例字段、静态字段)存储在主内存(main memory)
每条线程有自己的工作内存(working memory)
保存该线程使用到的变量的主内存拷贝
线程对变量所有操作在工作内存
线程间变量值的传递通过主内存
主内存与工作内存交互
lock(锁定)
作用于主内存变量,把一个变量标识为一条线程独占状态
unlock(解锁)
作用于主内存变量,释放lock状态的变量
unlock前必须将此变量同步回主内存 即store 、write
read(读取)
作用于工作内存,将主内存的变量值传输到工作内存
load(载入)
作用于工作内存,将read的变量值放到工作内存的变量副本中
use(使用)
作用于工作内存,将工作内存的变量值传递给执行引擎
assign(赋值)
作用于工作内存,将执行引擎接收到的值赋值给工作内存变量
store(存储)
作用于工作内存,将工作内存变量传送到主内存
write(写入)
作用于主内存,将store操作的值放入主内存变量
特性
原子性
上面8个指令都是原子操作
synchronized
monitorenter monitorexit
可见性
volatile
final
修饰字段在构造器中初始化完成,并且没有发生this引用逃逸,其他线程都可见
有序性
线程内串行
禁止指令优化重排
monitorenter 保证同步块串行访问
happens-before
即a操作先发生于b,那么a的影响能被b观察到
线程
线程安全
互斥同步
ReentrantLock
可中断
可实现公平锁
可绑定多个条件
非阻塞同步
CAS(compare and swap)
三个参数
变量内存地址
旧预期词
新值
ABA问题
AtomicStampedReference解决
版本号原理
无同步方案
可重入代码(reentrant code)
也叫纯代码(pure code)
如果输入相同参数都能返回相同结果,即满足可重入要求,即线程安全
线程本地存储
Threadlocal
Thread对象中有个ThreadLocalMap,以ThreadLocal的弱引用作为key,本地线程变量为值。每个threadLocal包含一个ThreadLocal.threadLocalHashCode
内存泄露
threadLocal 被回收后,ThreadLocalMap键为null,值永远访问不到
解决方案
get、set、remove方法本身会删除key为null的entry
使用完threadLocal后手动调用remove
锁优化
自旋锁
线程忙循环
若锁被占用时间段,效果好
若被长时间占用,白白浪费cpu资源
自适应
参考前一次自旋时间决定本次自旋时间或者是否自旋
锁消除
编译器检测到不可能存在竞争的锁进行消除
轻量级锁
CAS
失败就膨胀成为重量级锁
偏向锁
前一个获取锁的线程进入互斥区不需要获得锁,直接进入
若失败则膨胀为轻量级锁
0 条评论
回复 删除
下一页