JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread Local Allocation Buffer。 对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。JVM内存模型
1.JVM内存模型与运行时数据区之间的关系?
JVM运行时数据区是一种规范,而JVM内存模式是对该规范的实现
![](https://img.haomeiwen.com/i12841502/8e41f54f00ad8d54.png)
![](https://img.haomeiwen.com/i12841502/00f8247d0b70bf69.png)
对象生命周期
对象引用分类
对象引用分为4种,分别是:强引用、软引用、弱引用以及虚引用。
强引用:日常开发中编写的代码都是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。如果对象一直被引用,容易触发Java内存泄漏。
软引用:软引用需要用SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它 不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。适用于作为缓存对象来使用。
public class SoftReferenceDemo { /** * 软引用Demo * * 适用对象: * 1. 占用内存大 * 2. 生命周期长 * 3. 使用率不高 * * 如:图片相关的对象。 * * @param args */ public static void main(String[] args) throws InterruptedException { byte[] bytes = null; SoftReference<byte[]> softReference = null; //软引用中无数据,进行初始化操作。 if (softReference == null) { //缓存100M的数据 bytes = new byte[100 * 1024 * 1024]; //将缓存添加到软引用中。 softReference = new SoftReference<>(bytes); //移除强引用 bytes = null; } // 从软引用中获取数据 bytes = softReference.get(); } }
弱引用:弱引用需要用 WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
虚引用:虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主 要作用是跟踪对象被垃圾回收的状态。
对象生命周期
对象生命周期包括:创建阶段、应用阶段、不可见阶段、不可达阶段、收集阶段、终结阶段、对象空间重分配阶段,共7部分组成。
-
创建阶段: 类加载进入JVM ,并被初始化阶段。
-
应用阶段: 对象被强引用。
-
不可见阶段: 是指程序本身不再持有该对象的任何强引用,简单说就是程序的执行已经超出了该对象的作用域了。
-
不可达阶段:是指该对象不再被任何强引用所持有,即 对象不会被GC Root所持有。
引用计数法:
可达性分析法: -
收集阶段:当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。
![](https://img.haomeiwen.com/i12841502/e63ff646cbeaff25.png)
-
终结阶段: 当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
-
对象空间重分配阶段:垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象
空间重新分配阶段”。
常见的GC问题
对象创建过程
![](https://img.haomeiwen.com/i12841502/1f1305c9e3407adf.png)
-
GC触发时期
- 当Eden区或者S区不够用了
- 老年代空间不够用了
- 方法区空间不够用了
- System.gc()
-
GC分类?
Partial GC Partial其实也就是部分的意思.那么翻译过来也就是回收部分GC堆的模式,他并不会回收我们整个堆.而我们 的young GC以及我们的Old GC都属于这种模式
young GC: 只回收young区, 标记-复制-清理算法。
old GC:只回收Old区
full GC:实际上就是对于整体回收 -
Suvivor区的作用?
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
-
Suvivor为什么是两个?
最大的好处就是解决了碎片化。
-
新生代中Eden:S1:S2为什么是8:1:1?
新生代中的可用内存:复制算法用来担保的内存为9:1
可用内存中Eden:S1区为8:1
即新生代中Eden:S1:S2 = 8:1:1 -
堆内存中都是线程共享的区域吗?
JVM默认为每个线程在Eden上开辟一个buffer区域,用来加速对象的分配,称之为TLAB,全称:Thread Local Allocation Buffer。 对象优先会在TLAB上分配,但是TLAB空间通常会比较小,如果对象比较大,那么还是在共享区域分配。参考:G1
垃圾收集器介绍
业务线程和GC线程串行执行叫做:stw -- stop the world.
-
标记-清除算法 -- Old区使用
-
标记:找出内存中需要回收的对象,并且把它们标记出来,
此时需要遍历整个堆内存,比较耗时。
-
清除:清除掉被标记需要回收的对象,释放出对应的内存空间
-
缺点:
- 标记和清除两个过程都比较耗时,效率不高
- 会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无 法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
-
-
标记-复制算法 -- Suvivor区使用
将内存划分为两块相等的区域,每次只使用其中一块,当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。
缺点: 空间利用率降低。-- 空间换时间的思想。
-
标记-整理算法--Old区使用
标记-整理算法 = 标记-清除算法+整理算法。
整理算法的顺序包括:任意顺序、线性顺序、
-
任意顺序:仅仅将内存排在一起即可,
任意顺序应用的算法:--双指针算法
优点:高效 缺点:只能处理固定大小的数据排列 遍历次数:2次
- 双指针算法:设置两个指针,一般是一个头部一个尾部-- 对撞指针,或者是都在头部,但是速度不同,如快慢指针
- 快慢指针:是为了解决链表环问题;
- 对撞指针:是为了解决数组或者字符串等非环的问题
此处双指针算法如何使用?
- 1.使用对撞指针,头指针查找空闲位置,尾指针查找存活对象,头指针查找到空闲位置会等待尾指针此时尾指针找到存活对象,这时候存活对象复制到头指针所在的位置,然后继续直到两个指针相撞。--此时只是复制对象位置
双指针算法第一次遍历
- 2.第二次遍历 修改对象的引用关系。
- 清除老位置的对象。
-
线性顺序:根据GCRoot 根节点的引用,将相关联的对象放到一起即可,不关注空间碎片问题。
标记整理线性顺序
-
滑动顺序:将存活对象滑动到内存一侧其他部分不关注。
滑动顺序使用的算法--Lisp2
Lisp2算法
优点:可以处理非固定大小的数据排列 缺点:1. 需要遍历3次;2. 需要额外空间记录对象迁移位置。 遍历次数:3次
过程:使用了三个指针,头指针、尾指针、中间指针,三个指针的作用是:一个判断空闲位置,一个判断可达对象,一个记录结束位置。
第一次遍历:记录对象需要迁移的位置。
标记整理-Lisp2算法第一次遍历.png
第二次遍历:修改引用对象的关系,和双指针算法相同。
第三次遍历:挪动对象到记录位置。
滑动顺序使用的算法--单次遍历算法
单次遍历算法
优点:可以处理非固定大小的数据排列 缺点:需要格外的空间来储存池临时表 遍历次数:1次
过程:需要额外的表来记录对象的迁移位置,具体来说通过设置内存大小相同的Carl Table 来记录对象的标记位向量(对象的开始和结束位置)、偏离位向量(对象移动后的开始位置)和内存索引号
第一次:记录Carl Table。-- 遍历
第二次:完成对象移动和引用关系修改。 -
网友评论