文章目录
一、java虚拟机发展
- Sun Classic VM
(商用,纯解释器,性能差)
- Exact VM
- (准确式内存管理,编译器+解释器,
- 在Solaris平台发布(windows和linux没有发布),
- 初步具备高性能虚拟机的特性)
- HostSport
- 设计之初并非为java开发
- 继承了sun classic vm和exact的优点
- 热点代码探测技术,性能高,代码优化技术
- KVM
- 嵌入式的虚拟机
- 简单,轻量级,高度可移植
- 手机平台运行
- JRockit
- 快
- 专注服务器端应用
- 垃圾收集器+服务套件
- 诊断内存泄露问题
- J9(IBM公司)
- 类似于HotSport
- 多用途的虚拟机
- Azul VM Liquid VM
- 专用虚拟机,性能超强
- Taobao VM
二、内存管理
2.1线程独占区:
每个线程都有自己的虚拟机栈/本地方法栈/程序计数器
2.1.1 程序计数器:
- 当前线程所执行的字节码指令的行号指示器;
- 如果线程执行java方法,计数器记录–正在长记性的jvm字节码指令地址
- 如果线程执行native方法,计数器值为undefined
tips: 为什么要记录正在执行的指令的行号?
因为cpu按时间片执行,若当前指令没有执行完,时间片用完线程需要切换之后,做上下文恢复。
2.1.2 虚拟机栈
描述java方法执行的动态内存模型
存储当前线程运行方法所需的数据,指令,返回地址;
作为一个栈,栈内进出的基本单元是栈帧。
包含:栈帧,局部变量表,大小
栈帧
- 每个方法执行,都会创建一个栈帧,伴随着方法从创建到执行完成。用于存储局部变量表,操作数栈,动态链接,方法出口。
动态链接
what?
编译成.class字节码文件之后,只是生成了jvm所认知的逻辑地址,但是交于操作系统去执行时候,需要找到真实物理地址,所以需要将各个对象逻辑地址绑定到真实的物理地址,在运行时动态绑定的过程即为动态链接;
动态链接作用?
运行时来决定来调用哪个方法;
动态链接为什么在栈帧里面?
因为方法不执行的时候是没有必要解析的,
比如接口有n个实现类,运行时只是个符号引用,真正在调用方法时候才会去常量池找到真正的实例;
出口:
返回地址,方法执行完,需要出栈;
正常就需要return
异常需要throw error。
局部变量表
- 存放编译期可知的各种基本数据类型,引用类型,returnAddress类型
- 局部变量表的内从空间在编译期完成分配,当进入一个方法时,这个方法需要在栈帧中分配多少内存是固定的,运行期间不会改变
嵌套方法实际是多个栈帧
2.1.3 本地方法栈
与虚拟栈很类似(Hotspot不区分这两个栈)
为虚拟机执行native方法内存模型
本地方法:没有实现类,是其他语言实现的;
2.2 线程共享区域:
所有线程共享此区域
2.2.1 堆
存放对象实例
垃圾收集器管理的主要区域
区域:新生代,老年代,Eden空间
-Xmx -Xms 动态指定堆大小
2.2.2 方法区
-
存储虚拟机加载的类信息,常量,静态常量,即时编译器编译后的代码等数据
- 类信息:类的版本,字段,方法,接口
- 运行时常量
- 静态变量
- JIT
- 运行时常量池
- 存放各种字面量和符号引用 (int a =1),1是字面量,a是符号引用;
- 常量池里面不仅仅存储了常量,符号引用包含了类的名称和描述符,接口的名称,接口名,字段名,方法的名称及描述符,权限描述符等
- 常量池内放着类运行所需要的所有的东西;
-
方法区和永久代
- 使用永久代的方式来实现方法区,方法区是规范,永久代是实现
- jdk1.8 之后的元空间与永久代的地位是相等的,也是对方法区的一种实现;
-
垃圾回收在方法区一般不回收
-
异常–outofMemory
2.3 内存模型
关于meta Space
设计meta Space是为了规避永久代会溢出的问题,meta Space类似ArrayList可自动扩容;
但是自动扩容堆外内存不是越大越好;
2.4 栈+堆+方法区的交互关系
三、对象的创建
3.0 创建对象的方式
- new
- 反射
- 通过类(类型)—任何数据类型包括(基本数据类型)都有一个静态的属性.class --ClassLoader
- 通过字符串(类全名 )—能够实现解耦:Class.forName(str)
- 通过对象—对象.getClass()来获取c(一个Class对象)-- Object的方法
- 克隆
- 序列化
3.1 创建过程:
注意:对象的创建之前需要先加载类,
3.2 给对象分配内存
分配内存其实是指针移动的过程;
方式:
- 指针碰撞
- 空间列表
3.2.1 分配方式:
- 指针碰撞
规整的内存,向空闲区方向移动指针,执行临界区
- 空闲表
不规整的内存,维护空闲表记录空闲内存,占用内存时更新表
java堆是否规整是由垃圾回收器决定的:
- 如果垃圾回收器带有回收整理压缩的功能,就会把堆内存整理压缩成规整的区域,
- 不带回收整理功能,堆内存就不规整
3.2.3 线程安全问题
并发情况,同一时刻会有多个对象在创建,指针移动的过程可能会存在线程安全问题,或者更新空间表的过程
如何解决:
- 线程同步,加锁(效率低) CAS
- 栈上分配,本地线程分配缓冲 (效率高) – 给每个线程分配一块独立buffer内存区域(这个容量不是很大,可以通过虚拟机运行参数进行指定)
- TLAB thread local allocation burffer
3.2.4 初始化对象
初始化默认值,对象是哪个类的实例,对象的hash码,初始在对象头中
3.2.5 执行构造方法
3.3 对象的结构
3.3.1 Header(对象头)
- 自身运行时候的数据 (MarkWord )–不同平台不同, 可能是32字节或者64字节,但是一定是8字节的整数倍
- 哈希值
- 源于Object类的native int hashcode
- GC分代年龄(根据不同的代选用不同的垃圾回收算法),
- 锁状态标志(监视器中的锁状态)
- 线程持有的锁
- 偏向线程Id
- 偏向时间戳
- 哈希值
- 类型指针(确定对象是哪个类的实例,指向类的元信息)
- Class clazz = ClassA.class
- 给clazz分配了指向这块区域的指针叫类型指针
- 数组长度 只有对象数组才有
3.3.2 InstanceData(数据实例)
真正存储对象的有效信息,
相同类型的数据被分配到一起
3.3.3 Padding
相当于占位符,起填充的作用;
8字节的整数倍;
问题:在高并发场景下,如何改变锁状态,由无锁态变成轻量级锁??
3.4 对象的访问定位
3.4.1 方式
- 使用句柄 (两步:引用类型指向句柄池,句柄池指向真正的内存地址)
- 优势:栈内存中存放的引用地址始终指向句柄池不需要修改,不管对象的新增和销毁
- 缺点:两次访问,慢
- 句柄池就是一块内存空间,句柄代表一个指针
- 直接指针 (引用类型直接指向对象的内存区域) --Hotspot采用此方式
- 优势:减少一次查找,快
3.4.2 访问定位需要通过两个指针
- 到对象实例数据的指针
- 到对象类型数据的指针
不管是使用直接指针还是使用句柄都需要保存上述两个指针才可以定位到对象
参数格式
-Xms20M starting 堆的其实大小
-Xmx max 堆的最大
-Xmn new 堆的新生代大小