15022 字
40 分钟
JVM虚拟机核心知识体系
组成
类加载子系统(Class Loader SubSystem)
类的生命周期
- 类加载
- 过程
- 通过类的全限定名,获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
- 类加载器
- 启动类加载器(Bootstrap ClassLoader):负责加载JRE核心类库,像JRE中的rt.jar等(C/C++)
- 扩展类加载器(Extension ClassLoader):负责加载JRE扩展目录ext中的jar包,由Java语言实现,父类加载器为null
- 系统类加载器(Application ClassLoader):负责加载ClassPath路径下的类包,由Java语言实现,父类加载器为ExtClassLoader
- 自定义类加载器(UserDefined ClassLoader)
- 步骤
- 继承ClassLoader
- 重写findClass方法
- 重写loadClass方法(如果要打破双亲委派模型)
- 重写defineClass(如果需要加载外部类)
- 应用
- 对于非.class的文件,需要转为Java类
- 加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译
- 从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端
- Tomcat的自定义类加载器:详见Tomcat类加载器图
- 步骤
- 类加载机制
- 全盘负责委托机制:由一个ClassLoader加载一个类和与其相关的其他类
- 双亲委派机制
- 概念:向上委托,向下加载
- 异常示例
- 自定义java.lang.String,并运行main
- 异常:在类java.lang.String中找不到main方法
- 原因:main所在的类会触发初始化,且该类由根加载器加载,String不存在main方法
- 自定义java.lang.**,并运行main
- 异常:java.lang.SecurityException:Prohibited package name:java.lang
- 原因:无权访问,main所在的类会触发初始化,且该类由根加载器加载,获取不到类,禁止使用该包名
- 自定义java.lang.String,并运行main
- 优点
- 避免类的重复加载
- Java核心API中定义类型不会被随意替换
- 沙箱安全机制:对java源代码的保护,如防止上述异常示例
- 缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
- 类加载方式
- 使用Class静态方法Class.forName
- 使用ClassLoader:this.getClass().getClassLoader()
- 使用new
- 过程
- 连接
- 验证
- 目的:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机的自身安全
- 分类
- 文件格式验证
- 是否以魔数0xCAFEBABE开头
- 主、次版本号是否在当前虚拟机处理范围之内
- 常量池的常量中是否有不被支持的常量类型(检查常量tag标志)
- 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
- CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据
- Class文件中各个部分及文件本身是否有被删除的或附加的其他信息
- 元数据验证
- 这个类是否有父类(除了java.lang.Object之外,所有类都应当有父类)
- 这个类是否继承了不允许被继承的类(被final修饰的类)
- 如果这个类不是抽象类,是否实现了其父类或接口之中所要求实现的所有方法
- 类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等等)
- 字节码验证
- 目的:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会产生危害虚拟机安全的事件
- 分类
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作数栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中
- 保证跳转指令不会跳转到方法体以外的字节码指令上
- 保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险不合法的
- 符号引用验证
- 目的:类对自身以外(常量池中的各种符号引用)的信息进行匹配性校验
- 分类
- 符号引用中通过字符串描述的全限定名是否能够找到对应的类
- 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
- 符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问
- 文件格式验证
- 准备
- 概念:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值
- 基本类型(int、long、short、char、byte、boolean、float、double、reference)的默认值为(0、0L、(short)0、‘\u0000’、(byte)0、false、0.0f、0.0d、null)
- 常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100
- 解析
- 作用:把常量池中的符号引用转换为直接引用,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址
- 分类
- 前置条件:假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接口C的引用
- 类或接口的解析
- 如果C不是一个数组类型,那虚拟机将会把代表N的全限定名传递给D的类加载器去加载这个类C
- 如果C是一个数组类型,并且数组的元素类型为对象,那将会按照第1点的规则加载数组元素类型
- 访问权限,不具备则抛出java.lang.IllegalAccessError异常
- 字段解析
- 解析字段表内class_index项中索引的CONSTANT_Class_info符号引用
- 如果C 本身就包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束
- 否则,如果C中实现了接口,将会按照继承关系从下往上递归搜索各个接口和它的父接口如果接口中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束
- 否则,如果C 不是java.lang.Object的话,将会按照继承关系从下往上递归搜索其父类,如果在父类中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束
- 否则,查找失败,抛出java.lang.NoSuchFieldError异常
- 访问权限,不具备则抛出java.lang.IllegalAccessError异常
- 注:如果有一个同名字段同时出现在C的接口和父类中,或者同时在自己的父类或多个接口中出现,那编译器可能拒绝编译,并提示”The field xxx is ambiguous”
- 类方法解析
- 解析类方法表内class_index项中索引的CONSTANT_Class_info符号引用
- 类方法和接口方法符号引用的常量类型定义是分开的,如果在类方法表中发现class_index中索引的C 是个接口,那就直接抛出java.lang.IncompatibleClassChangeError异常
- 如果通过了第一步,在类C 中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
- 否则,在类C的父类中递归查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
- 否则,在类C实现的接口列表以及他们的父接口中递归查找是否有简单名称和描述符都与目标相匹配的方法,如果存在相匹配的方法,说明类C是一个抽象类这时查找结束,抛出java.lang.AbstractMethodError异常
- 否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError
- 访问权限,不具备则抛出java.lang.IllegalAccessError异常
- 接口方法解析
- 解析接口方法表内class_index项中索引的CONSTANT_Class_info符号引用
- 与类解析方法不同,如果在接口方法表中发现class_index中的索引C是个类而不是个接口,那就直接抛出java.lang.IncompatibleClassChangeError异常
- 否则,在接口C中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
- 否则,在接口C的父接口中递归查找,直到java.lang.Object类(查找范围包括Object类)为止,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束
- 否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError
- 访问权限,接口中所有的方法默认都是public的,所以不存在访问权限的问题
- 符号引用
- 概念:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。
- 示例
- 例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现,在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。
- org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
- 特性:各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
- 直接引用
- 分类
- 直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
- 相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
- 一个能间接定位到目标的句柄
- 特性:直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了
- 分类
- 验证
- 初始化
- 概念:为静态变量赋予正确的初始值,此阶段才是程序员编写的程序变量赋予真正的初始值,执行静态代码块
-
- 概念:初始化阶段就是执行类构造器方法 - 来源:javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来 - :构造器 - 注:子类的 需在父类的 后运行,虚拟机必须保证 在多线程下被同步加锁 - 类初始化时机
- 主动引用
- 虚拟机启动时被标为启动类的类(包含 main() 方法的类)
- 在类的字节码中遇到new、getstatic、putstatic、invokestatic这四条指令时,会触发类的初始化
- 这四条字节码指令分别对应创建类的实例、访问一个类的静态字段、设置一个类的静态字段、调用一个类的静态方法
- 这里的静态字段不包括在编译期就能确定的静态常量,因为静态常量会存放到调用方的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
- 当初始化类的时候,如果父类还没有进行过初始化,则需要先触发其父类的初始化
- 使用反射 API 对类进行反射调用时,会初始化这个类
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类
- 被动引用
- 静态字段:对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化
- 数组:通过数组定义来引用类,不会触发此类的初始化,因为数组由 Java 虚拟机直接生成的
- 原始类型的数组变量由newarray创建
- 引用类型的数组变量由anewarray创建
- 常量字段编译期不确定:当一个常量字段的值在编译期不确定时(如UUID.random().toString()),那么它不会被放到调用类的常量池中。因此即便这个静态字段是一个常量(被final关键字修饰),但由于它在编译期是不确定的,所以在程序运行时还是会主动使用这个常量所在的类,从而触发常量所在类的初始化
- 父类接口:一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中运行期才确定的常量)才会初始化。
- 注:由于接口是无法定义静态代码块的,所以无法像类那样通过类静态代码块的执行与否判断是否发生初始化。但是在接口的初始化过程中,编译器同样会为接口生成
()构造器,用于初始化接口中所定义的成员变量。
- 注:由于接口是无法定义静态代码块的,所以无法像类那样通过类静态代码块的执行与否判断是否发生初始化。但是在接口的初始化过程中,编译器同样会为接口生成
- 主动引用
在初始化阶段,虚拟机会为类的静态变量赋予正确的初始值,当然接口类也会遵从这条规定。所以我们可以通过初始化阶段对静态字段的赋值来观察接口类是否进行了初始化
- 使用:通过执行引擎执行相应类的方法
- 卸载
- 程序正常执行结束
- 执行中出现异常
- 操作系统出现异常
- 通过执行 system.exit()
运行时数据区(Runtime Data Areas即JVM内存模型)

组成
- 线程私有
- 虚拟机栈
- Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同
- 组成:栈帧
- 局部变量表
- 存储对象
- 方法参数
- 局部变量
- 容量
- slot复用:变量脱离作用域后
- 单位:变量槽(Variable Slot)
- 大小:只能存放32位以内的数据类型,64位的double及long需使用两个slot,且不允许单独访问其中一个
- 32位数据类型
- boolean
- byte
- char
- short
- int
- float
- reference:引用类型
- returnAddress
- 最大值:方法的code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量
- slot分配(通过索引定位)
- 0处存放this(实例方法和构造方法
) - 其余按方法体内部定义的变量顺序和作用域分配
- 0处存放this(实例方法和构造方法
- 注:变量使用前需确定初始值
- 存储对象
- 操作数栈
- 数据结构:栈FILO
- 容量
- 32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2
- 最大值:方法的code属性的max_stack数据项中确定了该方法所需要分配的局部变量表的最大容量
- 作用
- 算术运算
- 调用其他方法时参数传递
- 动态连接
- 概念:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)
- 静态连接:类加载解析阶段符号引用转化为直接引用
- 过程:当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中
- 方法出口
- 概念:在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态
- 退出方式
- 正常退出:有返回值,则一并返回,调用者的PC计数器的值可以作为返回地址
- 异常退出:无返回值,返回地址是要通过异常处理器表来确定的
- 一些附加信息
- 局部变量表
- 栈顶缓存技术:将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率
- 异常
- StackoverflowError:固定大小,线程请求分配的栈容量超过Java虚拟机栈允许的最大容量
- 栈帧过多
- 局部变量表数据过多
- 操作数栈过深
- OutOfMemoryError:动态扩展,没有足够的内存去创建对应的虚拟机栈
- StackoverflowError:固定大小,线程请求分配的栈容量超过Java虚拟机栈允许的最大容量
- 命令
- -Xss:设置栈的大小
- 本地方法栈
- 概念:本地方法栈则是为虚拟机使用到的 Native 方法服务,结合JNI使用
- 数据结构:栈FILO
- 异常
- StackoverflowError:固定大小(用参数-Xss设置),程请求分配的栈容量超过Java虚拟机栈允许的最大容量,
- OutOfMemoryError:动态扩展,没有足够的内存去创建对应的虚拟机栈
- 程序计数器:一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
- 虚拟机栈
- 线程共享
- 堆
- 存储:对象数据
- 组成
- 新生代
- Eden
- S0
- S1
- 老年代
- 永久代:JDK1.8废除
- 新生代
- 引用
- 强引用(StrongReference)
- 概念:当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题
- 应用场景:对象的一般状态
- 软引用(SoftReference)
- 概念:内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存
- 应用场景:对象缓存
- 弱引用(WeakReference)
- 概念:弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
- 应用场景:对象缓存
- 虚引用(PhantomReference)
- 概念:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收
- 应用场景:跟踪对象的垃圾回收
- 强引用(StrongReference)
- 内存分配
- 实现方式
- 指针碰撞
- 原理:假设Java堆中内存时完整的,已分配的内存和空闲内存分别在不同的一侧,通过一个指针作为分界点,需要分配内存时,仅仅需要把指针往空闲的一端移动与对象大小相等的距离
- 应用场景:Serial、ParNew,适用堆内存规整(即没有内存碎片)的情况下
- 空闲列表
- 原理:事实上,Java堆的内存并不是完整的,已分配的内存和空闲内存相互交错,JVM通过维护一个列表,记录可用的内存块信息,当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录
- CMS,适用堆内存不规整的情况下
- 指针碰撞
- 并发解决
- CAS: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性
- TLAB: 为每一个线程预先分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配
- 实现方式
- GC
- 算法
- 存活性判断
- 引用计数
- 原理:给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1;任何时刻计数值为0的对象就是不可能再被使用的
- 优点
- 及时回收无效内存,实时性高
- 垃圾回收过程中无需挂起
- 没有全局扫描,性能高
- 缺点
- 对象的生成、赋值和引用都涉及引用计数的变化,从而导致引用计数的增减处理频繁
- 为引用计数分配固定的空间来存放计数,这是固有损耗
- 无法处理循环引用
- 可达性分析
- 原理:通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的
- 两次标记过程
- 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,则将会被第一次标记并且进行一次筛选
- 若这个对象被判定有必要执行finalize()方法,则该对象放置在一个叫F-Queue队列中,并之后由VM自动建立的、低优先级的Finalizer线程去执行它
- finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue队列中的对象进行第二次小规模的标记,若finalize()能够救活自己,则第二次标记时,将被移除出“即将回收”的集合;若对象没有完成自救,则回收
- GC Roots
- 局部变量表中引用的对象
- 类变量引用的对象
- 字符串常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
- 异常类、基本类型的Class、系统类加载器
- synchronize持有的对象
- 本地缓存
- 优点
- 解决了循环引用问题
- 三色标记
- 原理
- 基于GC Roots获取存活对象
- 白色:还没有搜索过的对象
- 灰色:正在搜索的对象
- 黑色:搜索完成的对象
- 并发标记问题
- 多标-浮动垃圾
- 正在扫描的灰色对象,引用被断开
- 并发标记开始后的新对象,直接染成黑色
- 漏标
- 条件
- 灰色对象断开了白色对象的引用
- 黑色对象重新引用了该白色对象
- 解决方法
- 读写屏障
- 写屏障用于拦截第二和第三步
- 读屏障则是拦截第一步
- 在读写前后,将对象G给记录下来
- 增量更新(CMS):当一个白色对象被一个黑色对象引用,将黑色对象重新标记为灰色,让垃圾回收器重新扫描,但效率较低。(破坏条件二)
- 将黑色染灰
- 将白色染灰
- 原始快照:SATB(G1及ZGC):当引用发生变化之前,记录下原来的引用对象,既原始快照,当灰色对象和白色对象之间的引用马上被断掉时,将这个引用记录下来,使GC依旧能够访问到,那样白色就不会漏标。(破坏条件一)
- 在开始标记的时候生成一个快照图,标记存活对象
- 在并发标记的时候所有被改变的对象入队(在write barrier【写屏障】里把所有旧的引用所指向的对象都变成非白的)
- 可能存在浮动垃圾,将在下次被收集
- 读写屏障
- 条件
- 多标-浮动垃圾
- 原理
- 引用计数
- 垃圾回收
- 标记-清理
- 原理:标记后直接清理
- 特点:清除后会产生大量不连续的内存碎片空间,碎片空间太多可能会导致当程序后续需要创建较大对象时,无法找到足够连续的内存空间而不得不再次触发垃圾回收
- 标记-压缩
- 原理:在清理的同时整理内存
- 特点:避免内存碎片化
- 应用场景:生命周期长、回收频率低,但注重回收一次内存空间得到足够释放的场景
- 复制
- 原理:将内存划分为两块区域,每次使用其中一块,当其中一块用满,触发垃圾回收的时候,将存活的对象复制到另一块上去,然后把之前使用的那一块进行格式化,一次性清除干净
- 特点:内存空间利用率低
- 应用场景:那些生命周期短、回收频率高的内存对象
- 增量更新
- 原理:每次收集部分
- 特点:延迟低,线程切换开销较大
- 分区收集
- 原理:内存分区,回收价值最高的区域
- 特点:在吞吐量和低延迟间取得较好的平衡
- 标记-清理
- 存活性判断
- 回收过程
- Minor GC与Full GC
- 在初始阶段,新创建的对象被分配到Eden区,survivor的两块空间都为空
- 当Eden区满了的时候,Minor GC被触发
- 经过扫描与标记,存活的对象被复制到S0,不存活的对象被回收, 并且存活的对象年龄都增大一岁
- 在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区。当Eden 和 s0区空间满了,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的两个对象在复制到S1后其年龄要加1。此时Eden区S0区被清空,所有存活的数据都复制到了S1区
- 再下一次MinorGC则重复这个过程,这一次survivor的两个区对换,存活的对象被复制到S0,存活的对象年龄加1,Eden区和另一个survivor区被清空
- 再经过几次Minor GC之后,当存活对象的年龄达到一个阈值之后(-XX:MaxTenuringThreshold默认是15),就会被从年轻代Promotion到老年代
- 随着MinorGC一次又一次的进行,不断会有新的对象被promote到老年代
- 老年代空间不足发生Major/Full GC
- 分类
- Minor GC
- 回收区间
- 新生代
- 触发条件
- Eden区满时
- 注:Survivor区满时不会触发
- 回收区间
- Major GC
- 回收区间
- 老年代
- 触发条件
- 老年代满时
- 回收区间
- Full GC
- 回收区间
- 新生代
- 老年代
- 元空间
- 触发条件
- 调用System.gc时,系统建议执行Full GC,但是不必然执行
- 老年代空间不足
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
- 回收区间
- Minor GC
- 对象进入老年代的情况
- 假如进行Minor GC时发现,存活的对象在ToSpace区中存不下,那么把存活的对象存入老年代
- 大对象直接进入老年代,假设新创建的对象很大,比如为5M(这个值可以通过PretenureSizeThreshold这个参数进行设置,默认3M),那么即使Eden区有足够的空间来存放,也不会存放在Eden区,而是直接存入老年代
- 长期存活的对象将进入老年代
- 动态对象年龄判定,如果在From空间中,相同年龄所有对象的大小总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象就会被移动到老年代,而不用等到15岁(默认)
- 空间分配担保
- 在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间
- 成立:Minor GC可以确保是安全的
- 不成立:虚拟机会查看HandlerPromotionFailure(JDK1.7失效)这个参数设置的值(true或flase)是否允许担保失败
- true:那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的
- false:这次Minor GC将升级为Full GC
- 在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间
- Minor GC与Full GC
- 垃圾回收器
- 前置
- 吞吐量
- 概念:CPU用于运行用户代码时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
- 特性:吞吐量越大,暂停时间越长,响应时间越长,反之吞吐量越小,暂停时间越短,响应时间越短
- 参数设置
- -XX
:最大垃圾收集停顿时间 - -XX
:吞吐量大小 - -XX<+useadaptivesizepolicy>+useadaptivesizepolicy>:动态调整,以提供最合适的停顿时间或者最大的吞吐量
- -XX
- 读写屏障
- 写前屏障(pre-write barrier):所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根重新扫描一遍,例:satb
- 写后屏障(post-write barrier):记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍,例:增量更新
- 跨代引用
- Card Table
- Rset
- 吞吐量
- 分类
- Serial
- 特点
- 单线程
- 采用标记复制算法
- 新生代收集器
- 虚拟机运行在Client模式下的默认新生代收集器
- 优点
- 简单高效,对于单个CPU环境而言,Serial收集器由于没有线程交互开销,可以获取最高的单线程收集效率
- 缺点
- 需要STW(Stop The World),停顿时间长
- 配套垃圾回收器
- Serial Old
- CMS
- 特点
- ParNew
- 特点
- Serial收集器的多线程版本,并行执行
- 采用标记复制算法
- 新生代收集器
- 虚拟机运行在Server模式下的首选新生代收集器
- 优点
- CPU数量较多时,效率高于Serial
- 缺点
- 单/双CPU的环境中,由于线程交互开销,效率低于Serial
- 需要STW(Stop The World),停顿时间长
- 配套垃圾回收器
- Serial Old
- CMS
- 特点
- Parallel Scavenge
- 特点
- 多线程,并行执行
- 采用标记复制算法
- 新生代收集器
- 虚拟机运行在Server模式下的默认新生代收集器
- 吞吐量优先
- 优点:吞吐量大,程序运行效率高
- 缺点:响应时间较长
- 配套垃圾回收器
- Serial Old
- Parallel Old
- 特点
- Serial Old
- 特点
- Serial收集器的老年代版本
- 采用标记整理算法
- 老年代收集器
- 给Client模式下的虚拟机使用
- 优点
- 简单高效,对于单个CPU环境而言,Serial收集器由于没有线程交互开销,可以获取最高的单线程收集效率
- 缺点
- 需要STW(Stop The World),停顿时间长
- 配套垃圾回收器
- Serial
- ParNew
- Parallel Scavenge
- 特点
- Parallel Old
- 特点
- Parallel Scavenge的老年代版本,并行执行
- 采用标记整理算法
- 老年代收集器
- 吞吐量优先
- 优点:吞吐量大,程序运行效率高
- 缺点:响应时间较长
- 配套垃圾回收器
- Parallel Scavenge
- 特点
- CMS
- 特点
- 并发与并行执行
- 采用标记清除算法
- 老年代收集器
- 优点:响应时间短,用户体验好
- 缺点
- 吞吐量较低
- 无法处理浮动垃圾
- 会产生大量的内存碎片,不利于大对象的分配,可能会提前触发一次Full GC
- -XX<+usecmscompactatfullcollection>+usecmscompactatfullcollection>:进行碎片的合并整理过程,这样会使得停顿时间变长
- -XX<+cmsfullgcsbeforecompaction>+cmsfullgcsbeforecompaction>:设置执行多少次不压缩的Full GC后,接着来一次带压缩的GC
- 收集过程
- 初始标记
- 标记和GCroot直接关联的对象
- 标记新生代指向的老年代对象
- 需要STW
- 并发标记
- 增量更新
- 三色标记
- Dirty Card:引用变更区域
- Write Barrie:记录Dirty Card
- 并发预清理:重新标记Dirty Card
- 可被终止的并发预清理
- 触发时机:Eden区的内存占用大于CMSScheduleRemarkEdenSizeThreshold(默认2M)时
- 结束时机:Eden的内存使用率达到CMSScheduleRemarkEdenPenetration(默认50%)时
- 作用:协调minor gc与重新标记阶段
- 重新标记
- 重新标记和GCroot直接关联的对象
- 重新标记新生代指向的老年代对象
- 需要STW
- 并发清除,回收内存空间,时间很长
- 并发重置
- 初始标记
- 配套垃圾回收器
- Serial
- ParNew
- Serial Old:CMS会在特殊情况(jvm认为内存不够了concurrent mode failure,promotion fail) 转而停下所有线程 去做full gc,也就是MSC(单线程)就是Serial Old(也算一次full gc)作为替补
- 特点
- G1
- 特点
- 并发与并行执行
- 采用复制算法
- 分代收集
- 可预测的停顿
- 优点
- 基于标记 - 整理算法,无内存碎片产生
- 可预测的停顿时间模型
- Region
- 大小:为1到32M,根据总的内存大小而定
- 数量:不超过2048个
- 指针
- bottom:开始指针
- previous TAMS:并发标记开始指针
- next TAMS:并发标记结束指针
- top:分界指针
- end:结束指针
- 注:next与top之间的为并发标记阶段的新生对象
- 分区
- Eden(新生代)
- Survivor(幸存区)
- Old(老年代)
- Humongous(巨大对象)
- 大于等于Region一半的对象
- 直接分配到老年代,防止频繁拷贝
- 永远不会移动,要么直接被回收,要么一直存在
- 全局缓冲区日志来源
- Writer Barrier
- 跨区引用
- 索引变更
- Writer Barrier
- 对象分配
- TLAB:提升Eden分配效率
- PLAB:提升到Survivor及Old的效率
- Thread
- App Thread(用户线程)
- Concurrence Refinement Thread(同步优化线程,写后屏障)
- 过程:扫描全局缓冲区的日志,根据日志更新各个区(Region)的RSet
- 参数设置
- -XX
- -XX
- -XX
- -XX
- 注:如果发现全局缓冲区日志积累较多,G1会调用更多的线程来出来缓冲区日志,甚至会调用App Thread 来处理,造成应用任务堵塞
- GC Thread(GC线程)
- Set
- 记忆集合(Remembered Sets)
- 概念:储存记录其他Region对当前持有Rset Region中Card的引用
- 储存方状态
- 哈希表:到对象
- 细粒度:到Region
- 粗粒度:到分代区域
- 收集集合(Collection Set)
- 概念:通过扫描RSet来分析垃圾比例最高的Region区,放入CSet中,进行回收
- 记忆集合(Remembered Sets)
- GC
- Yong GC
- 回收年轻代的内存
- 收集过程
- 根扫描,静态和本地对象被扫描
- 更新RS,处理dirty card队列更新RS
- 处理RS,检测从年轻代指向年老代的对象
- 对象拷贝,拷贝存活的对象到survivor/old区域
- 处理引用队列,软引用,弱引用,虚引用处理
- 需要STW
- Mixed GC
- 回收所有年轻代内存和部分老年代内存
- 收集过程
- 初始标记,标记GCRoots能直接关联到的对象,时间很短,会穿插yong gc,需要STW
- 根区域扫描
- 并发标记,进行GCRoots Tracing(可达性分析)过程,并通过写前屏障将灰色对白色引用,将白色置灰,放入satb mark queue中,并为bottom与pre指针间的引用进行快照Bitmap
- 重新标记,读取satb mark queue中的应用,进行漏标修复,同时这个过程也会处理弱引用,需要STW
- 清理阶段,处理并回收Cset中的Region
- 注:当mixed gc回收速度赶不上内存分配的速度,G1会使用单线程(使用Serial Old的代替)进行Full GC
- Yong GC
- 参数
- XX<+useg1gc>+useg1gc>:开启G1 GC
- -XX:MaxGCPauseMillis = 200 :设置最大暂停目标,G1只会仅最大努力达到这个目标
- -XX:InitiatingHeapOccupancyPercent = 45 : 表示并发开始GC周期的堆使用阈值,当整个堆的使用量达到阈值时,就会开始并发周期
- -XX
: 每次Mixed GC回收的老年代内存数量,默认为8 - -XX
: 每次GC使用的CPU数量, 值不是固定 - -XX
%: 假天花板数量,作用是预留10%的空间不使用,
- 特点
- ZGC
- Region
- 小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象
- 中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象
- 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,最小容量可低至4MB,所有大型Region可能小于中型Region。大型Region在ZGC的实现中是不会被重分配的,因为复制一个大对象的代价非常高昂
- 染色指针
- HotSpot虚拟机的标记实现方案
- 把标记直接记录在对象头上(如Serial收集器)
- 把标记记录在与对象相互独立的数据结构上(如G1、Shenandoah使用了一种相当于堆内存的1/64大小的,称为BitMap的结构来记录标记信息)
- 直接把标记信息记在引用对象的指针上(如ZGC)
- 存储位置(64位操作系统)
- 高18位不能用来寻址,所有不能使用
- 剩余的46为却可以支持64T的空间,到目前为止我们几乎还用不到这么多内存。于是ZGC将46位中的高4位取出,用来存储4个标志位
- Finalizable:表示是否只能通过finalize()方法才能被访问到,其他途径不行
- Remapped:表示是否进入了重分配集(即被移动过)
- Marked1、Marked0:表示对象的三色标记状态
- 剩余的42位可以支持4T的内存
- 优势
- 一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉
- 染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,ZGC只使用了读屏障
- 染色指针具备强大的扩展性,它可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据
- HotSpot虚拟机的标记实现方案
- 内存多重映射:将多个不同的虚拟内存地址映射到同一个物理内存地址上
- NUMA支持
- UMA:表示内存只有一块,所有CPU都去访问这一块内存,那么就会存在竞争问题(争夺内存总线访问权),有竞争就会有锁,有锁效率就会受到影响,而且CPU核心数越多,竞争就越激烈
- NUMA:每个CPU对应有一块内存,且这块内存在主板上离这个CPU是最近的,每个CPU优先访问这块内存,那效率自然就提高了
- 收集过程
- 并发标记:会出现短暂的停顿,更新染色指针中的Marked 0、Marked 1标志位
- 初始标记
- 并发标记
- 重新标记
- 并发预备重分配:根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)
- 并发重分配:ZGC执行过程中的核心阶段,这个把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系
- 并发重映射:重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,但是ZGC中对象引用存在“自愈”功能,所以这个重映射操作并不是很迫切。ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象图的开销
- 注:指针的“自愈”(Self-Healing),ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象
- 并发标记:会出现短暂的停顿,更新染色指针中的Marked 0、Marked 1标志位
- 优点
- 低停顿
- 高吞吐量
- ZGC收集过程中额外耗费的内存小
- 缺点
- 浮动垃圾
- Region
- Serial
- JDK版本迭代
- JDK1.3:发布Serial GC及ParNew
- JDK1.4:发布Parallel GC及CMS
- JDK1.6:Parallel GC成为默认
- JDK1.7:发布G1
- JDK1.9:G1成为默认
- JDK11:引入Epsilon及ZGC
- JDK12:增强G1,并引入Shenandoah GC
- JDK13:增强ZGC
- JDK13:删除CMS
- 前置
- 算法
- 逃逸分析
- 概念:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
- 代码优化
- 栈上分配
- 同步省略
- 标量替换
- 命令
- -Xms:设置堆的起始大小
- -Xmx:设置堆的最大大小
- -XX
:老年代与新生代比例,默认为2 - -XX
:Eden与Survivor比例,默认为8 - -Xmn:设置新生代的最大内存大小,优先级高于NewRatio、SurvivorRatio
- -XX
:新生代转移到老年代的年龄阈值 - -XX
:是否开启TLAB,默认开启 - -XX
:空间分配担保,JDK1.7失效 - -XX
:逃逸分析JDK1.6后默认开启 - -XX
:标量替换默认开启
- 元空间
- 永久代(Permanet Generation,也称PermGen)
- 概念:方法区的实现
- 垃圾回收:永久代和堆是相互隔离的,但它们使用的物理内存是连续的,和老年代捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集
- 存储
- 类信息
- 运行时常量池
- 字符串常量
- 类静态变量
- 即时编译器编译后的代码
- JDK版本变动
- 1.7
- 符号引用(Symbols)转移到了Native Memory
- 字符串常量池(interned strings)转移到了Java Heap
- 类的静态变量(class statics)转移到了Java Heap
- 1.8:取消了永久代,改为元空间
- 替换原因
- 字符串存在永久代中,容易出现性能问题和内存溢出
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
- 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
- 替换原因
- 1.7
- 概念:元空间不再与堆连续,而且是存在于本地内存(Native memory)
- 存储内容
- 类信息
- 运行时常量池
- 即时编译器编译后的代码
- 参数
- -XX
,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值 - -XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的
- -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集
- -XX
,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集
- -XX
- 永久代(Permanet Generation,也称PermGen)
- 堆
内存溢出
- 栈溢出:StackOverflowError
- 堆溢出:OutOfMemoryError
heap space - 永久代溢出:OutOfMemoryError: PermGen space,已废除
- 线程创建失败:OutOfMemoryError
to create native thread
执行引擎(Execution Engine)
作用:将字节码指令解释/编译为对应平台上的本地机器指令
- 输入:字节码文件
- 处理过程:字节码解析的等效过程
- 执行结果
- 注:每个字节码指令都由一个1字节的操作码和附加的操作数组成
执行方式
- 解释器执行(通过解释器执行)
- 概念:将编译好的字节码一行一行地翻译为机器码执行
- 优势:当程序需要迅速启动的时候,解释器可以首先发挥作用,省去了编译的时间,立即执行。解释执行占用更小的内存空间。同时,当编译器进行的激进优化失败的时候,还可以进行逆优化来恢复到解释执行的状态
- 编译器执行(JIT通过即时编译器产生本地代码执行)
- 概念:以方法为单位,将字节码一次性翻译为机器码后执行
- 优势:在程序运行时,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获得更高的执行效率
- 即时编译
- 用处:处理热点数据
- 热点数据分类
- 被多次调用的方法
- 被多次执行的循环体
- 热点探测
- 概念:判断一段代码是否是热点代码,是不是需要触发即时编译
- 探测算法
- 基于采样的热点探测
- 概念:虚拟机会周期的对各个线程栈顶进行检查,如果某些方法经常出现在栈顶,这个方法就是“热点方法”
- 优点:实现简单、高效,很容易获取方法调用关系
- 缺点:很难确认方法的reduce,容易受到线程阻塞或其他外因扰乱
- 基于计数器的热点探测(HotSpot使用)
- 概念:为每个方法(甚至是代码块)建立计数器,执行次数超过阈值就认为是“热点方法”
- 优点:统计结果精确严谨
- 缺点:实现麻烦,不能直接获取方法的调用关系
- 分类
- 方法调用计数器
- 概念:如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内的方法被调用的次数
- 热度的衰减及半衰周期:当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那么这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就成为此方法的统计的半衰周期( Counter Half Life Time)
- 注:进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的
- 阈值
- Client 模式:1500
- Server 模式:10000
- 回边计数器
- 概念:统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”( Back Edge )。显然,建立回边计数器统计的目的就是为了触发 OSR 编译
- 阈值
- Client 模式:公式为 方法调用计数器阈值(CompileThreshold)X OSR 比率(OnStackReplacePercentage)/ 100 。其中 OSR 比率默认为 933,那么,回边计数器的阈值为 13995
- Server 模式:公式为 方法调用计数器阈值(Compile Threashold)X (OSR (OnStackReplacePercentage)- 解释器监控比率 (InterpreterProfilePercent))/100 其中 onStackReplacePercentage 默认值为 140,InterpreterProfilePercentage 默认值为 33,如果都取默认值,那么 Server 模式虚拟机回边计数器阈值为 10700
- 方法调用计数器
- 基于采样的热点探测
- 热点数据分类
- 即时编译器
- Client Compiler/C1:即Client编译器,面向对启动性能有要求的客户端GUI程序,采用的优化手段比较简单,因此编译的时间较短
- Server Compiler/C2:即Server编译器,面向对性能峰值有要求的服务端程序,采用的优化手段复杂,因此编译时间长,但是在运行过程中性能更好
- 目前的 HotSpot 编译器默认的是解释器和其中一个即时编译器配合的方式工作,具体是哪一个编译器,取决于虚拟机运行的模式,HotSpot 虚拟机会根据自身版本与计算机的硬件性能自动选择运行模式,这种配合使用的方式称为“混合模式”(Mixed Mode)
- 使用 -client 和 -server 参数强制指定虚拟机运行在 Client 模式或者 Server 模式
- 使用参数 -Xint 强制虚拟机运行于 “解释模式”(Interpreted Mode),这时候编译器完全不介入工作
- 使用 -Xcomp 强制虚拟机运行于 “编译模式”(Compiled Mode),这时候将优先采用编译方式执行,但是解释器仍然要在编译无法进行的情况下接入执行过程
- 用处:处理热点数据
方法调用
- 概念:确定被调用方法的版本,即调用哪一个方法,不涉及方法内部的具体运行过程
- 分类
- 解析调用:静态过程,在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,而不会延迟到运行期再去完成,即编译期可知、运行期不变
- 分派调用
- 静态类型与实际类型
- 静态类型:父类/接口
- 实际类型:子类/实现类
- 静态分派与动态分派
- 静态分派
- 依赖静态类型来定位方法的执行版本
- 典型应用是方法重载
- 发生在编译阶段
- 动态分派
- 依赖动态类型来定位方法的执行版本
- 典型应用是方法重写
- 发生在运行阶段
- 单分派:根据一个宗量对目标方法进行选择(方法的接受者与方法的参数统称为方法的宗量)
- 多分派:根据多于一个宗量对目标方法进行选择
- 静态分派
- 静态类型与实际类型
- invokevirtual 指令
- 弹出栈顶元素,判断其实际类型,记为C
- 能否在C中查找到需要调用的简单名称和描述相符的方法
- 能:返回引用
- 否:向C的父类再做如上的搜索,能否找到相符方法
- 能:返回引用
- 否:抛出异常
本地库接口(Native Method Interface -> JNI)
本地方法库(Native Method Library)
对象
实例化方式
new
- 类的静态方法
- xxxBuilder/xxxFactory的静态方法
Class的newInstance():反射,只能调用空参的构造器,权限必须是public
Constructor的newInstance():反射,对参数和访问权限无要求
clone:不调用任何构造器,当前类实现Cloneable接口,实现clone()
反序列化:从文件中,从网络中获取一个对象的二进制流
第三方库Objenesis
创建步骤
判断对象是否加载、链接、初始化
为对象分配内存
- 内存规整
- 指针碰撞
- 内存不规整
- 空闲列表
处理并发安全问题
- 采用CAS失败重试,区域加锁保证更新的原子性
- 每个线程预先分配一块TLAB,通过-XX: +/-UseTLAB来设置
初始化分配到的空间
- 所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
设置对象的对象头
执行进行初始化
内存布局
对象头
- 运行时元数据
- 哈希地址值
- GC分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程id
- 偏向时间戳
- 类型指针
- 指向类元数据InstanceKlass,确定对象所属的类型
- 注:数组,还需记录长度
实例数据
- 概念:它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
- 规则
- 相同长度的字段总是被分配到一起
- 父类中定义的变量会出现在子类前
- 如果CompactFields参数设置为true(默认为true),子类的窄变量可能插入到父类的空隙
对象填充
- 非必须,类似占位符
访问定位
句柄访问
- 栈 :reference
- 到对象实例数据的指针
- 到对象类型数据的指针
- 优点
- 稳定,不受对象移动影响
- 缺点
- 占用空间
- 效率较低
直接指针
- 栈 :reference
- 到对象类型数据的指针
- 实例池:对象实例数据
- 方法区:对象类型数据
- 到对象类型数据的指针
- Hotspot的默认实现
- 优点
- 空间占用少
- 效率高
- 缺点
- 不稳定,受对象移动影响
字节码文件
魔数
0xCAFEBABE
版本号
主版本号
次版本号
常量池
存储
- 字面量
- 符号引用
- 类和接口的全局限定名
- 字段的名称和描述符
- 方法的名称和描述符
组成
- 常量池计数器
- 常量池数据区:14种类型的cp_info
访问标志
描述该Class是类还是接口
是否被Public、Abstract、Final等修饰符修饰
注意:JVM并没有穷举所有的访问标志,而是使用按位或操作来进行描述的,比如某个类的修饰符为Public Final,则对应的访问修饰符的值为ACC_PUBLIC | ACC_FINAL,即0x0001 | 0x0010=0x0011
当前类名
访问标志后的两个字节,描述的是当前类的全限定名。这两个字节保存的值为常量池中的索引值,根据索引值就能在常量池中找到这个类的全限定名
父类名称
当前类名后的两个字节,描述父类的全限定名,同上,保存的也是常量池中的索引值
接口信息
父类名称后为两字节的接口计数器,描述了该类或父类实现的接口数量。紧接着的n个字节是所有接口名称的字符串常量的索引值
字段表
描述
- 类和接口中声明的变量
- 类级别的变量以及实例变量
组成
- 两个字节,描述字段个数
- 每个字段的详细信息fields_info
方法表
组成
- 两个字节描述方法的个数
- 每个方法的详细信息
- 方法的访问标志
- 方法名
- 方法的描述符
- 方法的属性
- Code区:源代码对应的JVM指令操作码,在进行字节码增强时重点操作的就是“Code区”这一部分
- LineNumberTable:行号表,将Code区的操作码和源代码中的行号对应
- LocalVariableTable:局部变量表,包含This和局部变量
JVM调优
子主题 1
子主题 2
子主题 3
子主题 4
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
JVM虚拟机核心知识体系
https://onecodemaker.cn/posts/java核心-05-jvm/ 部分信息可能已经过时
相关文章 智能推荐
1
JVM与JUC并发编程完全指南
Java核心 系统讲解JUC并发工具包与JVM核心体系:Lock接口、线程通信、Condition、线程池、BlockingQueue、ReentrantReadWriteLock、类加载、堆结构、GC垃圾回收、参数调优。
2
Java基础核心知识:OOP原则、访问修饰符、泛型与内部类
Java核心 深入理解Java面向对象编程六大原则、四种访问修饰符、泛型的类/接口/方法/通配符用法,以及四种内部类的特点与示例。
3
Java集合核心问题:哈希冲突与并发修改异常
Java核心 解析HashMap哈希冲突的解决机制、equals与hashCode的契约关系,以及ConcurrentModificationException的成因与迭代器安全删除方案。
4
Java多线程与并发编程深度解析
Java核心 全面覆盖Java多线程核心知识:线程基础、CompletableFuture异步编程、锁机制、CAS与原子类、ThreadLocal、JMM、volatile、对象内存布局与锁升级。
5
Spring AOP原理与自定义注解详解
Java核心 从静态代理、JDK动态代理到CGLIB代理,深入理解Spring AOP的实现原理与通知执行顺序;结合面试题分析Spring4/Boot2的AOP差异;系统讲解Java注解机制与自定义注解实践。