一个class类的运行流程以及垃圾回收的详细讲解

一个class类的运行流程以及垃圾回收的详细讲解

在这里插入图片描述

1.编译阶段:

源码编写,程序员使用文本编辑器或集成开发环境 编写java源代码(.java),编译器编译(javac)将.java文件编译成字节码文件.class文件

在这里插入图片描述

接下里开始根据双亲委派机制进行,加载器的选择

双亲委派模型:

如果一个类加载器收到了类加载请求,并不会自己先去加载,而是把这个请求委托给父亲的加载器区去执行,如果父亲加载器还存在父亲加载器就往上传递,依次传递,请求最终到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父亲加载器无法完成此加载任务,子加载器才会尝试自己去加载

在这里插入图片描述

加载过程:

1.加载

  • 通过包名 + 类名,获取这个类,准备用流进行传输
  • 在这个类加载到内存中
  • 加载完毕创建一个class对象

2.验证

确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全

(文件中的信息是否符合虚拟机规范有没有安全隐患)

3.准备

负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值

(初始化静态变量)

4.解析

将类的二进制数据流中的符号引用替换为直接引用

(本类中如果用到了其他类,此时就需要找到对应的类)

5.初始化

根据程序员通过程序制定的主观计划去初始化类变量和其他资源

(静态变量赋值以及初始化其他资源)

在这里插入图片描述

接下来到jvm(java虚拟机)

  1. 程序计数器(Program Counter Register)

    定义:程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

    作用:在 Java 虚拟机的多线程环境下,程序计数器用于记录每个线程执行的字节码指令的地址。当线程被切换回来时,能够知道该线程上次执行到哪儿了。例如,线程 A 正在执行一个方法的字节码,执行到第 10 行后被暂停,此时程序计数器就记录了第 10 行这个位置。当线程 A 再次获得 CPU 时间片时,就可以从程序计数器记录的位置(第 10 行)继续执行。

    特点:它是线程私有的,因为每个线程都有自己独立的执行路径,需要单独的程序计数器来跟踪执行位置。并且此区域不会出现内存溢出(OutOfMemoryError)的情况。

  2. Java 虚拟机栈(Java Virtual Machine Stack)

    定义:Java 虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。

    栈帧组成部分

    局部变量表(Local Variables Table):用于存放方法参数和方法内部定义的局部变量。例如,在一个简单的 Java 方法public int add(int a, int b) { return a + b; }中,参数ab就存放在局部变量表中。局部变量表的大小在编译期就确定了。

    操作数栈(Operand Stack):是一个后入先出(LIFO)栈,用于在方法执行过程中进行算术运算、赋值等操作。比如在执行表达式int c = a + b;时,ab的值会被压入操作数栈,然后执行加法运算。

    动态连接(Dynamic Linking):每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,用于支持方法调用过程中的动态连接。

    方法出口(Return Address):当一个方法执行完毕后,需要知道返回的位置,方法出口就记录了这个信息。

    异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。

  3. 本地方法栈(Native Method Stack)

    定义:本地方法栈与 Java 虚拟机栈类似,它们的区别在于 Java 虚拟机栈为 Java 方法服务,而本地方法栈是为本地(Native)方法服务的。本地方法是指用非 Java 语言(如 C 或 C++)编写的方法,这些方法通过 Java 本地接口(JNI)来调用。

    作用和特点:在执行本地方法时,本地方法栈会为每个本地方法创建一个栈帧,用于存储本地方法的局部变量、返回值等信息。它也是线程私有的,同样可能会出现 StackOverflowError 和 OutOfMemoryError 异常。

  4. 堆(Heap)

    定义:堆是 Java 虚拟机所管理的内存中最大的一块,被所有线程共享。它用于存放对象实例和数组。例如,当使用new关键字创建一个对象(如Object obj = new Object();)时,这个对象就存储在堆中。

    垃圾回收(Garbage Collection):堆中的对象由于是动态分配的,所以需要有垃圾回收机制来回收不再使用的对象所占用的空间。Java 虚拟机中有不同的垃圾回收算法,如标记 - 清除算法、复制算法、标记 - 整理算法等,用于自动管理堆内存。

    内存划分(分代):为了更好地进行垃圾回收,堆通常会被分为不同的代(Generation),一般包括新生代(Young Generation)和老年代(Old Generation)。新生代又可细分为 Eden 区和两个 Survivor 区。对象首先在新生代的 Eden 区中分配内存,经过多次垃圾回收后,如果对象仍然存活,就会被移到老年代。

    异常情况:如果堆中没有足够的内存来分配新的对象,并且垃圾回收器也无法回收足够的空间,就会抛出 OutOfMemoryError 异常。

  5. 方法区(Method Area)

    定义:方法区也是所有线程共享的内存区域,它用于存储已被虚拟机加载的类信息(包括类的版本、字段、方法、接口等)、常量、静态变量、即时编译器编译后的代码等。例如,一个 Java 类中的静态变量public static int count;就存储在方法区中。

    运行时常量池(Runtime Constant Pool):它是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。字面量包括字符串常量(如"Hello World")、基本数据类型的值(如103.14)等;符号引用包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符等。在类加载后,运行时常量池中的符号引用会被解析成直接引用。

    异常情况:当方法区无法满足内存分配需求时,也会抛出 OutOfMemoryError 异常。

在这里插入图片描述

当执行完,所要创建的类或者方法。使用结束后开始进行垃圾回收

首先需要进行垃圾的判断和分类(两种方法)

1.引用计数法:

每个对象有一个引用计数。当对象被引用一次,计数加 1;引用失效(如引用变量超出作用域或被重新赋值),计数减 1。当引用计数为 0 时,对象可以被回收。

2.可达性分析:
从根对象的对象开始,通过引用关系向下搜索。如果一个对象从根对象出发无法通过引用链到达,那么这个对象就被判断为可以回收

栈中的被调用的方法,方法中的局部变量

本地栈中的方法中的局部变量

类的静态变量

字符串常量池的引用

都是根对象,就是说这个对象和根对象没关系,就会被进行回收

对垃圾进行分类 强引用,软引用、弱引用和虚引用

强引用是最常见的引用类型。在代码中通过Object obj = new Object();这种方式创建的对象引用就是强引用。只要强引用存在,垃圾回收器就不会回收被引用的对象。

软引用是一种相对强引用较弱的引用类型。软引用所关联的对象在内存充足的情况下不会被回收,只有当系统将要发生内存溢出(OutOfMemory)之前,才会对软引用对象进行回收

弱引用的强度比软引用更弱。一旦垃圾回收器发现了只具有弱引用的对象,无论当前内存是否足够,都会回收该对象。

虚引用是最弱的一种引用类型。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。虚引用主要用于跟踪对象被垃圾回收的状态。

把垃圾进行分类之后就开始回收

在这里插入图片描述

1.标记消除法

标记 - 清除(Mark - Sweep)算法是一种经典的垃圾回收算法。它主要包括两个阶段,即标记阶段和清除阶段。

在标记阶段,垃圾回收器会从根对象(如全局变量、栈中的变量等)开始,通过遍历对象引用图,将所有可达的对象标记为 “存活” 状态

清除阶段则是遍历堆内存中的所有对象,将那些没有被标记为 “存活” 的对象回收。这些对象所占用的内存空间会被释放,以便后续可以重新分配给其他对象使用。

第一遍对堆里面的引用进行标记,计数为0 的就被回收

在这里插入图片描述


第二遍遍历堆,进行清除

在这里插入图片描述

2.复制算法

(左边是a区域,右边是b区域)

复制算法,就是把堆内存分为两个区域,在回收时,把a区域中存活的对象复制到b区域中,然后清除a区域(内存利用率低,因为要将内存分为两个区域),之后在b区域进行创建对象进行之前的操作

在这里插入图片描述

在这里插入图片描述

3.标记整理法

标记 - 整理(Mark - Compact)算法是一种用于垃圾回收(Garbage Collection,GC)的算法。在内存管理中,当程序运行时会不断地分配和释放内存,垃圾回收器的作用是自动回收那些不再被程序使用的内存空间。标记 - 整理算法结合了标记和整理两个主要步骤。

首先是标记阶段,垃圾回收器从根对象(如全局变量、栈上的活动记录等)开始,遍历所有可达的对象,并对这些对象进行标记。这个过程类似于沿着一个树形结构或者图结构进行深度优先搜索或者广度优先搜索,被标记的对象表示它们仍然被程序所引用,是 “存活” 的对象。

然后是整理阶段,在标记完成后,垃圾回收器会将所有存活的对象向内存的一端移动,使得存活对象在内存中是连续的。这样,在存活对象的一端,所有的空闲内存就会集中在另一端,方便后续的新对象分配。

1.先进行标记

在这里插入图片描述

2.删除并整理

在这里插入图片描述

与标记 - 清除算法的对比

标记 - 清除(Mark - Sweep)算法也是一种常见的垃圾回收算法。它同样有标记阶段,但是在清除阶段,只是简单地将未标记的对象(即垃圾对象)所占的内存空间标记为空闲,而不进行内存整理。标记 - 整理算法的优势在于它可以避免内存碎片的产生。在标记 - 清除算法中,由于只是简单地清除垃圾对象,随着时间的推移,内存中会出现很多小块的空闲空间,这些小块的空闲空间可能无法满足较大对象的分配需求,导致虽然总的空闲内存足够,但是无法分配新对象的情况,这就是内存碎片问题。而标记 - 整理算法通过整理存活对象,使得内存空间更加规整,能够有效利用内存。

4.分代收集(Generational Collection)算法是一种基于对象生命周期假设的垃圾回收算法。它的核心思想是根据对象的存活时间将内存划分为不同的代(Generation),不同代采用不同的垃圾回收策略。

一般来说,对象被分为年轻代(Young Generation)和老年代(Old Generation)。年轻代中的对象通常是新创建的对象,它们的生命周期较短,大概率会很快变成垃圾。而老年代中的对象存活时间较长,相对比较稳定。

年轻代中,由于对象生命周期短,所以垃圾回收比较频繁。常见的年轻代垃圾回收算法是复制(Copying)算法。当进行年轻代垃圾回收时,把存活的对象从一个区域复制到另一个区域,然后直接清空原来的区域。例如,年轻代可以分为 Eden 区和两个 Survivor 区(Survivor0 和 Survivor1),新创建的对象首先放在 Eden 区,当 Eden 区满了,就会触发一次年轻代垃圾回收,存活的对象会被复制到其中一个 Survivor 区,然后 Eden 区被清空。在下一次年轻代垃圾回收时,存活的对象会在两个 Survivor 区之间来回移动,经过多次垃圾回收后仍然存活的对象会被晋升(Promotion)到老年代。

老年代中的对象因为存活时间长,数量相对稳定,所以垃圾回收频率较低。老年代通常采用标记 - 整理(Mark - Compact)或者标记 - 清除(Mark - Sweep)算法进行垃圾回收

最后还有永久代就是为了去存储很不容易变化的对象

新生代占8份 老年代占 1份 永久代占 1份

移动,经过多次垃圾回收后仍然存活的对象会被晋升(Promotion)到老年代。

老年代中的对象因为存活时间长,数量相对稳定,所以垃圾回收频率较低。老年代通常采用标记 - 整理(Mark - Compact)或者标记 - 清除(Mark - Sweep)算法进行垃圾回收

最后还有永久代就是为了去存储很不容易变化的对象

新生代占8份 老年代占 1份 永久代占 1份

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值