74_深入JVM虚拟机【持续更新】
2020-05-19 10:40:47 63 举报
AI智能生成
深入理解 JVM 虚拟机笔记
作者其他创作
大纲/内容
内存与垃圾回收篇
JVM与 Java 体系结构
Java 及 JVM 简介
Java 发展的历程
虚拟机与 Java 虚拟机
JVM 的整体结构
Java 代码的执行流程
Java 的架构模型
JVM 的生命周期
JVM 的发展历程
Java 类加载系统
类加载子系统(类加载系统图补充)
运行时数据区
运行时数据区概述与线程
程序计数器
程序计数器(Program Counter Register)是一块比较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
每个线程都有一个独立的程序计数器且不相互影响,独立存储为,线程私有内存区域
该区域不会出现 OutOfMemoryError 的情况
Java 虚拟机栈
Java 虚拟机栈是线程私有的, 它的生命周期和线程相同。虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行你的时候 Java 虚拟机都会同步创建一个 栈帧 (Stack Frame)用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程。就对应着一个栈帧从入栈到出栈的一个过程。
局部变量表存放了编译期可知的各种 Java 虚拟机基本数据类型(boolean , byte, char , short, int, float, long, double)、对象引用(reference 类型, 它并不等同于对象本省, 可能时执行对象起始地址的引用指针,也可能时只想一个代表对象的句柄或者此对象的相关位置)和 retrun Address 类型 (指向了一条字节码指令的地址)
局部变量表中的存储空间以局部变量槽 (Slot)来表示, 其中64位长度的 Long 和 double 里欸行的数据被占用两个变量槽, 其余的数据类型会占用1个槽。
异常情况
如果栈的深度大于虚拟机所允许的深度、将抛出 StackOverflowError
如果 Java 虚拟机栈拓容可以动态拓展, 当栈拓展时无法申请到足够的内存会抛出 OutOfMemoryError
本地方法栈
本地方法栈 (Native Method Stacks)与虚拟机所发挥的作用时非常相似,区别在于本地方法栈则是为 Java 虚拟机使用本地 Natvie 方法服务
Java 堆
Java 堆 (Java Heap)时虚拟机管理的最大一块内存。
Java 堆内存被所有线程共享的一块内存区域,在虚拟机启动的时候创建。
Java 几乎所有的对象实例都在这里分配内。
异常情况
Java 堆中没有存储完成实例分配, 并且无法再拓展的时,Java 虚拟机将抛出 OutOfMemoryError 异常
方法区
方法区(Method Area)与 Java 堆一样, 是各个线程共享的内存区域, 它用于存储已被虚拟机加载的类型信息, 常量,静态变量,即时编译后的代码缓存等数据。
方法区与永久代
Java 8 之前, 通过永久代来实现了方法区
Java 8 及其以后, 废弃了永久代的概念,使用元空间来代替
异常情况
如果方法区无法满足新的内存分配需求,将抛出 OutOfMemoryError 异常
运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件除了有类的版本、字段、方法、接口等描述信息外、还有一项常量池表(Constant Pool Table ), 用于存放编译期生成的各种字面量和符号引用,这部分内容再类加载后被放到方法区的运行时常量池中。
异常情况
运行时常量池是方法区的一部分,受到方法区内存的限制,当常量池无法再申请到内存的时候会抛出 OutOfMemoryError 异常
直接内存 (Direct Memory)
直接内存 (Direct Memory)并不是虚拟机运行时的数据区的一部分,也不是《Java 虚拟机规范》中定义的内存区域
异常情况
部分内存频繁被使用,也可能导致 OutOfMemoryError 的异常
例子:
DirectByteBuffer、Netty 中比较常用
HotSpot 虚拟机对象
对象创建
通过 new 关键字 ,创建对象
虚拟机创建对象的过程
1. 接受字节码 new 指令
2. 检查指令参数是否能在常量池中定位到类的符号引用
3. 检查这个符号引用是否被加载,解析初始化。如果没有,需要先执行相应的类加载过程。
4. 在堆中划分适合的内存空间
堆中内存 分配的方式
指针碰撞(Bump The Pointer)
假设 Java 堆中的对象是绝对规整的, 所有被使用过的内存都放在一边,空闲的放在另外一边。中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间防线挪动了一段与对象大小相等的距离,这种分配方式被称为 "指针碰撞 " (Bump The Pointer)
垃圾收集器 Serial 、ParNew 使用
空闲列表(Free List)
如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)
垃圾收集器 CMS 基于标记清除(Sweep)
对象内存布局
对象头(Header/ Mak Word)
存储运行时自身的运行数据:哈希码(HashCode)、GC 分代年龄、锁状态、线程持有的锁、偏向锁ID、偏向时间戳等。
HotSpot 虚拟机对象头
实例数据(Instance Data)
实例数据部分是真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论继承下来,还是子类中定义的字段都必须记录下来。
对齐填充(Padding)
这个不是必然存在的, 也没有特别的意义,只是起着占位符的作用。由于 HotSpot 虚拟机的自动内存管理要求对象起始地址必须时 8 字节的整数倍,换句话就是说,任何对象都必须是8字节的整数倍,对线头进行设计成正好是8字节的倍数。如果实例数据部门没有对齐,就需要对齐填充。
对象访问定位
java 程序会通过栈上的 reference 数据来操作堆上的具体对象。由于 reference 数据来操作堆上的具体对象。 在 《Java 虚拟机规范》 只规定了它是一个指向对象的引用。具体的实现方式是由虚拟机实现
两种访问方式
使用句柄
如果使用句柄访问的话,Java 堆中将可能会划分出一块内存来作为句柄池, reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的地址信息。
通过句柄访问对象
直接指针
采用直接指针,Java 堆对象的内存布局就必须考虑如何防止访问类型数据的相关西悉尼, reference 中存储的直接就是对象的地址,如果只是访问对象本身的话,就不想需要多一次的间接访问开销。
通过直接指针访问对象
两种访问的优势和劣势
采用句柄方式最大的好处就是 reference 中存储的是稳定的句柄地址, 对象被移动(垃圾收集时移动对象时非常普遍的行为)指挥修改句柄中实例数据指针,而reference 本身不需要被修改
采用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本
HotSpot而言,它主要使用第二种方式进行对象访问(有例外情况,如果使用了Shenandoah收集器的话也会有一次额外的转发,后续会讲到)
OutOfMemoryError 异常
Java 堆溢出
异常提示:java.lang.OutOfMemoryError: Java heap space
分析方法
内存泄露 (Memory Leak)
内存溢出(Memory Overflow)
分析工具
Eclipse Memory Analyzer
相关设置参数
-Xmx
-Xms
虚拟机栈和本地方法栈溢出
Java 虚拟机规范场景
1. 如果线程请求的深度大于虚拟机所允许的最大深度, 将抛出 StackOverflowError 异常。
2. 如果虚拟机的栈内存允许动态拓展, 当拓展栈容量无法申请到足够的内存时, 将抛出 OutOfMemoryError 异常
实际场景(补充内存溢出测试场景)
todo
不断创建线程
参数
代码
输出
todo
相关设置参数
本地方法栈
-Xoss
虚拟机栈
-Xss
方法区和运行时常量池溢出
本机直接内存溢出
直接内存 (Direct Memory)的容量大小可以通过 -XX: MaxDirectMemorySize 参数来指定,如果不去指定,则和Java堆最大值(-Xmx)一致.
执行引擎
垃圾回收概述
垃圾收集器(GarBage Collection , GC),是实现内存动态分配和垃圾技术的工具。
垃圾收集器的主要工作
计算需要回收的内存
在合适的始机回收垃圾
实现垃圾的回收
垃圾回收相关算法
引用计数器算法
是指资源(可以是对象, 磁盘, 内存等)的被引用次数保存起来,当引用次数变为0 的时候就将其失败的过程
优/缺点
优点: 1. 可以尽快的回收不使用的对象;2.回收过程不会导致长时间的卡顿问题。
缺点:1.频繁更新计数器降低运行效率;2.原始引用计数器无法解决循环依赖的问题。
Python 早期版本
可达性分析算法
可达性分析算法,通过 “GC Roots”作为根系欸但集从这些节点开始,向下搜索,搜索过程中走过的路径称为 “引用链”(Reference Chain)如果某个对象到 GC Roots 之间没有任何引用链相连,或者用图论来说的话就是说从 GC Roots 到这个对象是不可达时,证明该对象已经不再被使用了。
在主流商用程序语言中广泛使用
利用可达性分析判断对象是否可回收
Java 中, 可作为 GC Roots 的对象包括
1. 在虚拟机栈(栈帧中的本地变量表)中的引用对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
2. 方法区中类静态属性引用的对象,譬如 Java 类的引用类型静态变量。
3. 在方法区中常量引用的对象, 譬如字符串常量池 (String Table)里的引用
4. 本地方法栈中 JNI(通常说的 native 方法)引用的对象
5. java 虚拟机内部的引用,如基本数据对应的 Class 对象, 一些常驻异常对象 (比如:NullPintException、OutOfMemoryError ) 等, 还有系统类加载器。
6. 所有被同步锁(Syschronized 关键字)持有的对象
7. 反映 Java 虚拟机内部情况的 JMX Bean 、 JVM TI 中注册的回调、本地带啊缓存等。
对象引用分类
1. 强引用 (Strong Re-ference)
2. 软引用(Softe Reference)
3. 弱引用 (Weak Reference)
4. 虚引用 (Phantom Reference)
对象存活判断
方法区的垃圾回收
垃圾回收算法
分代收集算法
标记-清除算法
标记-复制算法
标记-整理算法
垃圾回收概念
垃圾回收器
字节码与类的加载篇
性能监控与调优
总结
0 条评论
下一页