JVM内存回收机制

目录

1.JVM运行时数据区

2.JVM类加载过程

3.双清委派模型

4.垃圾回收机制(GC)

找出谁是垃圾方案一:引用计数

找出谁是垃圾:方案二,可达性分析

释放垃圾的内存空间

[判断垃圾:jvm依据对象的年龄对 对象进行区域划分。](#判断垃圾:jvm依据对象的年龄对 对象进行区域划分。)

回收垃圾方式:分代回收


1.JVM运行时数据区

JVM运行时数据区域 也叫做:JVM内存布局。

JVM内存布局 和 java内存模型完全不同,JVM内存布局由5大部分组成。

1.堆区(线程共享) :程序中所有创建的对象都保存在堆中。JVM最大空间。

2.栈(线程私有)

1>.java虚拟机栈作用(线程私有):Java虚拟机栈的生命周期 和 线程相同Java虚拟机栈 描述了java方法执行的内存模型每个方法在执行时 都会创建一个栈帧 ,用来储存局部变量方法之间的调用关系动态链接,方法出口等等。

2>.本地方法栈:与虚拟机栈类似,只不过是给本地方法使用的,本地方法由C++代码编写。

3.程序计数器(线程私有)保存下一条要执行的指令的地址。 这里不是CPU寄存器储存的,而是内存空间,指令是java的字节码。不是二进制机器语言。

4.元数据区:(以前叫方法区) 保存java代码中涉及类的相关信息。类的static属性。

在一个java进程中,元数据区 和 堆 是只有一份的 。即同一个进程中的所有线程共用一份数据

一个java进程中有多个线程**,多个线程都有自己的 程序计数器 和 栈。**所以每个线程都需要保存自己的"程序计数器" 每个线程都需要记录自己的 调用关系。

检测一下是掌握的怎么样,下面代码中的变量都储存在哪些区域?

java 复制代码
public class test4 {
    static class  Test{
        private int a;
        private Test b = new Test();
        private static int c;
        private static Test d = new Test();
    }

    public static void main(String[] args) {
        int e = 10;
        Test f = new Test();
    }
}

一般地:

局部变量:栈

成员变量:堆

静态成员变量:元数据区(方法区)


2.JVM类加载过程

一个类的生命周期大致为下几个过程。

编写一个java程序 会得到一个 .java文件,在经过javac 编译 就会得到一个 .class文件。

想要运行java 进程,jvm就需要 读取 .class 文件并执行里面的指令。

jvm读取到 .class里面的内容 这个就是类加载。把类 涉及的字节码,从硬盘读取到 内存里。

加载一个 .claas文件,就会对应创建一个类对象。类对象包含了.class

文件里的各种信息。

类名字,类的属性,类有哪些方法,继承的父类有哪些,实现的接口有哪些....


具体步骤:

1.加载:把 .class 文件找到,然后打开并读取文件的内容。

代码中先见到 类的名字,然后进一步找到对应的 .class 文件(涉及一系列的目录查找过程

2.验证验证读到的 .class 文件的数据是否正确,是否合法。

java标准文档中明确规定 .class 文件的格式是怎么样的。

3.准备:分配内存空间。

根据读取到的内容大小,确定出类对象需要的内存空间,申请这样大小的空间,并把这个空间全部初始化为0.

4.解析:主要针对类中的字符串常量进行处理

java虚拟机 将字符串常量池中的 符号引用 替换为直接引用的过程,也就是初始化常量。

符号引用 其实就是指(字符串常量已经在 .class文件中)文件中 符号的位置,就是偏移量。

直接引用 就是直接保存变量的地址。

5.初始化:针对 类对象 做最终的初始化操作,执行静态成员赋值语句。


3.双清委派模型

操作: 输入一个 类的全限定名(类似于java.lang.String),的到对应的 .class 文件。

属于 jvm加载类中的第一个机制:加载

Bootstrap ClassLoader(加载 标准库中的类)

ExtensionClassLoader(加载 扩展库的类)

ApplicationClassLoader(负责加载第三方库的类)

为什么会是这个流程?

核心目的是 方式用户自己写的类把 标准库 或者 扩展库给覆盖掉。

保证 标准库的类是第一位,扩展库的类的是第二位。最后是第三方库的类。

防止程序员 不小心创建一个 和 系统中已有的类重名的类。导致加载的时候覆盖掉了系统的类。


4.垃圾回收机制(GC)

GC 主要存在哪里呢?

栈 和 程序计数器 主要都是跟随线程的结束而结束。

元数据区:类对象涉及到的类加载,一个程序里面吗要加载得类都是有上限的,不会出现无限增长的情况。

所以 堆是GC得主要战区。

垃圾回收,回收内存 都是一对象为维度进行回收的。

GC回收的流程:1.找出谁是垃圾 2.释放垃圾的内存空间

找出谁是垃圾方案一:引用计数

给每个对象分配一个计数器,衡量有多少个引用指向。

每增加一个引用,计数器+1

每减少一个引用,计数器-1,如果计数器为0,此对象就垃圾了需要回收。

此时 对象中的计数器为0,就视为垃圾,需要回收。

上述方案存在两个问题:

1.消耗额外空间去 储存计数器

假设Tets类,只有一个int(4字节)成员,那么就要花2个字节储存计数器,内存多用了 50%

2.引用计数可能会导致"循环引用",使得上述判定出错。

这种情况就是最后 这俩对象计数器都不是0,都不能被释放。


找出谁是垃圾:方案二,可达性分析

用时间换取空间。

JVM中专门搞了一波周期性的线程,扫面代码中的所有对象,判定某个对象是否"可达"。

对应的,不可达的就是垃圾。

JVM中有所有对象的总名单,按照名单点名,如果没有到的 ,就是垃圾。

JVM中有很多的root根,从这个root开始可以把所有的对象都遍历到。遍历不到但是名单上存在就是垃圾。


释放垃圾的内存空间

1.标记-清除法

如果直接对内存空间进行标记清楚,就有可能导致碎片化的空间无法充分利用。

这样剩下的三个空间就不容易利用了,碎片化的空间不能进行申请连续的空间。

2.复制算法

将被回收后的空间的一分为二,把不是垃圾的对象拷贝到另一侧。确保回收后得到连续的空间

这个算法缺点很明显:

1.内存空间利用率低

2.如果存活下来的对象比较多,复制成本也很大。

3.标记-整理

与标记-清楚类似,但是不一样的是后续并不是直接回收对象,而是让所有的存活对象都向前移动,最后直接清理掉边界标记的就可以了。


4.jvm中真正的解决方案。

判断垃圾:jvm依据对象的年龄对 对象进行区域划分。

如果获得年龄,使用可达性分析 对对象进行扫描,每次描扫后对象的年龄就+1

1.伊甸区比较大,让新创建的对象存放,大多数新创建的对象都活不过第一轮GC,留下来的对象拷贝到幸存区。

2.幸存区,是两个相按照复制算法将存活久对象复制到幸存区,反复多次。幸存区里也会为了保留完整空间进行左右多次复制。

3.一个对象在幸存区多次被拷贝,年龄不断增长,就要拷贝到老年代了。

4.根据经验规律,老年代的对象生命周期都比较长,即便如此也是要进行可达性分析的。

但是老年代的GC频率较低。老年代也需要通过标记整理。


回收垃圾方式:分代回收

分代回收时JVM的GC基本思想方法。

jvm还提供许多"垃圾回收器"对上述的分代回收 作进一步的扩充和具体实现。

CMS涉及理念,把整个GC过程拆分成多个阶段,能和业务线程并发运行的。就尽量并发减少STW时间。

G1把整个内存分成很多快,不同的颜色表示这个一块区域是哪一块(新生代,老年代,幸存区...)

进行GC时不要求一周期就把多个内存都回收,只要回收一部分就好。限制一轮GC的工作量,目的是使STW控制在一定范围,降低STW的影响。

相关推荐
吴冰_hogan8 小时前
JVM(Java虚拟机)的组成部分详解
java·开发语言·jvm
东阳马生架构16 小时前
JVM实战—1.Java代码的运行原理
jvm
ThisIsClark18 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
王佑辉19 小时前
【jvm】内存泄漏与内存溢出的区别
jvm
大G哥21 小时前
深入理解.NET内存回收机制
jvm·.net
泰勒今天不想展开21 小时前
jvm接入prometheus监控
jvm·windows·prometheus
东阳马生架构2 天前
JVM简介—3.JVM的执行子系统
jvm
程序员志哥2 天前
JVM系列(十三) -常用调优工具介绍
jvm
后台技术汇2 天前
JavaAgent技术应用和原理:JVM持久化监控
jvm