- 结构
- 对象
- GC
- 常量池与String
1.结构
JVM整体结构:
运行时数据区
方法区 -> 线程共享
包括:类信息、常量、静态变量、即时编译期编译后的代码、引用
堆 -> 线程共享
包括:
几乎所有对象实例、数组
虚拟机栈 -> 线程私有
可以存在很多栈帧。每个栈帧:局部变量表、动态连接、完成出口、操作数栈
本地方法栈 -> 线程私有
程序计数器 -> 线程私有
2.对象
(1)对象创建的过程
1.先检查在JVM的Java类加载器中是否还存有这个Class的信息
2.在堆中分配内存
3.内存空间初始化(零值)
4.设置(将常量池中的类、接口、字段、方法等符号引用转为直接引用)
5.对象初始化(构造函数)
(2)对象的内存布局
锁状态标识:
无锁、偏向锁、轻量锁、重量锁
类型指针:
虚拟机通过这个指针来确定这个对象是哪个类的实例
对齐填充:
每一个对象都是32字节的倍数
偏向线程id:
如果线程a获得了锁,则偏向线程id 为a。
如果一个线程来获取锁,会先检查当前偏向线程id是否指向这个线程
如果是则可以获取
如果不是则去验证当前对象头是否标记为偏向锁
如果不是就cas竞争
如果是就使用cas将偏向锁id指向当前线程
标记不适合作为偏向锁,此时会触发偏向锁释放,锁升级为轻量级锁。
偏向时间戳:
如果类对象发生了一次大规模的撤销偏向行为
类的 偏向时间戳 值将加 1(以后创建的对象也会采用新的 epoch 值)
如果类的 epoch 值超过某个阈值,则证明该类不适合使用偏向锁
以后的对象也将不会再使用偏向锁,直接使用轻量级锁
(3)对象的访问定位
使用句柄池
会在堆中开辟一块内存作为句柄池句柄中储存了对象实例数据的内存地址
优点:reference存储的是稳定的句柄地址
在对象被移动只会改变句柄中的实例数据指针,而reference本身不需要改变
缺点:增加了一次指针定位的时间开销
使用直接指针:
优点:节省了一次指针定位的开销
缺点:在对象被移动时(如进行GC后的内存重新排列),reference本身需要被修改
(4)判断对象的存活
引用计数法 -> 投票算法
但是不能解决孤岛问题
可达性分析 -> 从根开始像链一样查找
可以解决孤岛问题
(5)对象的引用
强引用 -> 不会被gc
软引用 -> 快要触发OOM时会被gc
软引用 -> 保留至下一次gc前
虚引用 -> 随时随地可能就没了
(6)对象分配策略
3.GC
(1)分代收集理论
新生代和老年代采用不同的垃圾回收机制
(2)复制算法
特点:
实现简单、运行高效
内存复制、没有内存碎片
利用率只用一半
(3)标记-清除算法
特点
执行效率不稳定
内存碎片导致提前GC
(4)标记-整理算法
特点:
对象移动
引用更新
用户线程暂停
没有内存碎片
(5)JVM中常见的垃圾收集器
连线为共同工作
其中CMS与Serial Old共同协作
1.CMS(ConCurrent Mark Sweep)
CMS是以获取最短停顿时间为目的的GC,注重用户体验,暂停所有用户线程的时间很短
1.初始标记 -> 只标记GC ROOT直接关联的对象
会暂停用户线程,时间很短
2.并发标记 -> 由GC ROOT直接关联的对象出发,进行深层标记
不暂停用户线程,时间很长
3.重新标记 ->
标记从新生代晋升的对象、新分配到老年代的对象以及在并发阶段被修改了的对象
会暂停用户线程,时间短
4.并发清理
不暂停用户线程,时间长
5.重置
CMS清除内部状态,为下次回收做准备
缺点:
- CPU敏感 -> 并发标记和并发清理阶段需要占用CPU
- 浮动垃圾 -> 并发清理阶段可能会产生新的垃圾
- 内存碎片
因为内存碎片的原因,CMS会配合Serial Old共同协作,用Serial Old来解决内存碎片的问题,不过耗时很长,效率很低
2.G1
基本不牺牲吞吐量的前提下完成低停顿的内存回收;可预测的停顿是其最大的优势
1.初始标记 -> 只标记GC ROOT直接关联的对象
会暂停用户线程,时间很短
2.并发标记 -> 由GC ROOT直接关联的对象出发,进行深层标记
不暂停用户线程,时间很长
3.最终标记 ->
标记从新生代晋升的对象、新分配到老年代的对象以及在并发阶段被修改了的对象
会暂停用户线程,时间短
4.筛选回收 ->
根据每个 Region 的回收价值和成本进行排序,然后根据用户期望停顿的时间内来指定回收计划
会暂停用户线程,用户期望停顿的时间
结构
1.region 分区 ->
不再坚持固定大小、固定数量的分代区域划分
而是将整个内存区域划分为若干个大小相等的独立小区域(Region)
每个 Region 都能扮演 Eden、Survivor、Old 区
2.新增Humongous区 ->
用来存放大对象
4.常量池与String
参考
结构
常量池是运行时运行时数据区中的方法区的一部分
作用:
常量池避免了频繁的创建和销毁对象而影响系统性能,其实现了对象的共享
String类是final修饰的,不可以被继承和修改
1.先在栈上创建一个 String 类的对象引用变量 str
2.通过符号引用去字符串常量池中找有没有 “abc”
3.如果没有,则将“abc”存放到字符串常量池中,并将栈上的 str 变量引用指向常量池中的“abc”。如果常量池中已经有“abc”了,则不会再常量池中创建“abc”,而是直接将 str 引用指向常量池中的“abc”。
使用 new 创建的对象,存放在堆中。每次调用都会创建一个新的对象