JVM 详细知识

发布于:2022-12-26 ⋅ 阅读:(700) ⋅ 点赞:(0)

JVM

一.概念

jvm:java虚拟机,用来执行class文件,保证java语言的跨平台性

jvm就是一个字节码翻译器 ,将字节码翻译成各个系统对应的机器码,保证这些字节码可以在不同平台中正确运行

java虚拟机:可以当做有个虚拟的计算机,拥有自己的指令集和各种运行内存

二.jvm内存结构图

结构图

1.类加载器子系统

程序首次运行:加载————>连接--------->初始化

1.加载:加载类文件,通过加载器(引导类加载器,扩展类加载器,应用程序加载器)加载

2.连接:连接操作,验证(验证字节码是否正确),准备(为静态变量分配内存空间和默认值),解析

3.初始化:初始化静态变量和静态代码块

2.运行时数据区

1.方法区:储存类级别的数据,方法区在jvm中只有一个是多线程所共享

2.堆区:储存所有的对象,变量,数组,堆区在jvm中只有一个是多线程所共享

3.虚拟机栈:线程私有的,每个线程都会创建单独的运行时栈,每个方法调用时会在运行时栈中创建一个栈 帧,局部变量储存在占内存中

4.程序计数器:线程安全的,每个线程都会创建单独的程序计数器,用于保存当前所要执行的指令号,一旦指 令执行,计数器会更新到下一条指令

5.本地方法区:线程安全的,每个线程都会创建独立的方法区,用于保存本地方法信息

3.执行引擎

1.解释器:读取字节码,并逐一执行

2.即时编译器:如果一段代码被多次重复调用,都需要解释执行,即时编译器会将这样的字节码编译为本地代 码,用于多次重复调用提高性能

3.本地方法接口:与本地方法库进行交互,提供执行引擎所需要的本地库

4.本地方法库:执行引擎所需要的本地库的集合

三.程序计数器

作用:保存当前要执行的指令地址(指令号),一旦指令执行,计数器会更新到下一条指令的地址(指 令号)

程序计数器是线程安全的(运行数据区中唯一一个不会发生内存泄露的区域

jvm指令通过 javap -v test.class反解析获得反解析工具

作用:根据class字节码文件,反解析当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏 移量映射表、常量池等信息

执行过程:

1.计数器读入 要执行的jvm指令号

2.CPU获得计数器中的指令号

3.CPU根据计数器中的指令号执行相对应的指令

4.计算器更新要执行的jvm指令

四.虚拟机栈(栈)

虚拟机栈就是java方法执行的内存模型

每个执行的线程都有自己的虚拟机栈(系统为当前线程分配的内存空间),是通过方法调用来实现的

在虚拟机栈中,每个方法都对应了一个栈帧

每个线程只能有一个活动栈帧,对应着当前正在执行的方法,该栈帧处于栈顶

虚拟机栈会发生内存溢出

每个栈帧中包含:局部变量表、操作数栈、动态链接、方法返回值等信息

五.栈帧

1.结构

每个栈帧都有独立的储存空间

2.组成

1.局部变量表:

储存方法中的局部变量(包括在方法中声明的非静态变量及函数形参)

储存:对于基本类型的变量,直接存储它的值(long和double占用两个局部变量,其他类型占一个)

对于引用类型的变量,储存的是指向对象的引用

局部变量表的大小在编译期就可以知道大小,因此在执行期间局部变量表的大小不会改变

2.操作数栈:

当一个方法开始执行时,操作数栈为

随着方法的执行,会从局部变量表或对象实例的字段中复制所需要的数据写入到操作数栈,在随着计 算的进行 将栈中的元素出栈 到局部变量表 或者返回给调用者

3.动态链表:

指向运行时常量池的引用

在 class 文件中,描述一个方法调用或访问其成员变量时通过符号引用来表示的,动态

链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用

4.方法返回地址:

方法调用的返回,包含正常返回(有返回值)和异常返回(无返回值),不 同的返回值类型有不同的指令

无论方法采用何种方式退出,在方法退出后都需要返回到方法调用的位置,程序才能继

-续执行,方法返回时可能需要在当前栈帧中保存一些信息,用来帮助它恢复上层方法的执行 状态

六.占内存溢出

栈内存不足时就会发生占内存溢出-------------->可设置栈的大小来改变栈所占空间的大小

内存溢出的两种原因

1.栈帧过多导致内存溢出

2.栈帧过大导致内存溢出

栈溢出错误:

java.lang.StackOverflow

设置栈内存的大小:

-Xss size;

栈内存大小的默认值:

Windows(依赖虚拟机大小):1024k

七.本地方法栈

使用native修饰的方法为本地方法,它不是由java实现的,而是由本地动态库提供,可以是任意语言的实现

线程私有的

本地方法栈服务的是native方法

八.堆

储存所有的对象,变量,数组,堆区在jvm中只有一个是多线程所共享的

java堆内存的划分

 

1.堆是jvm中占用内存最大的一部分

2.堆是被所有线程共享的区域,在虚拟机启动时创建

3.堆是垃圾收集器管理的主要区域

4.堆在逻辑上分为新生代和老年代,新生代又分为 Eden 和 Survivor 区。Survivor 区由 FromSpace 和 ToSpace 组 成。Eden 区占大容量,Survivor 两个区占小容量,默认比例是 8:1:1。

对象的存放过程:

新创建的对象会放在新生代的Eden区,当Eden区的对象放满之后,会触发Minor Gc清理Eden区的对象,存活下来的对象会放在Survivor区的from区,当from区存放满之后会触发Minor Gc清理from区的对象,再次存活的对象会放到To区,这样保证在一段时间内总有一个Survivor区是空的,经过多次Minnor Gc后仍然存活的对象会被放到老年代中。

6.老年代储存长期存活的对象,沾满是会触发Major Gc或者Full Gc对整个对空间进行清理。

GC状态会停止所有的线程 等待GC完成,所以对响应要求高的应用 尽量减少发生 Major GC,避免响应超时

7.Minor GC : 清理年轻代

Major GC : 清理老年代

Full GC : 清理整个堆空间,包括年轻代和永久代

所有 GC 都会停止应用所有线程

8.堆所占用的内存是可扩展的,可通过“-Xms"或者”Xmx"来控制堆的最小内存和最大内存

JVM 堆内存常用参数

堆内存溢出

垃圾收集器会自动收集堆内存中不在使用的对象并回收,但如果在堆内存中不断产生新

对象,并且都被长期使用,这样就有可能发生堆内存溢出

九.方法区

方法区在逻辑上也属于堆的一部分,但是在具体实现上不强制要求方法区的位置

不同的虚拟机厂 商可以有不同的实现,如 JDK1.8 之前使用永久代实现,1.8 后使用元空间实现

方法区在虚拟机启动时创建,是所有线程所共享的

存储结构

方法区用于存储类的结构:

运行时常量池(含字符串常量)、静态变量、类的信息、常量。

类信息:

魔数,版本号,常量池,类(字段和方法),父类和接口数组,字段,方法等 信息

方法区是 JVM 中的一个规范、永久带元空间方法区的两个不同实现

JVM 垃圾回收机制

一、判断对象已死的算法

在堆内存中存放着大量的 Java 对象,垃圾收集器在对堆进行垃圾回收时,首先要判断

哪些对象还活着,哪些对象已经死去(即不在被引用的对象)

一个对象只有被引用的时候才能使用

1.引用计数器算法

引用计算器算法思想:为对象添加一个计数器,当某个地方引用该对象时 , 计数器加1,当引用失效时,计数器减1,当计数器为0是表示该对象已经不在被引用

引用计数器算法实现简单、效率高,适用于大多数适用场景,但是主流的 JVM 均没 有采用了这种算法,原因在于它很难解决对象之间的相互循环引用情况

2.可达性分析算法

可达性分析算法思想:设置一个“GC Roots”对象作为起始点,从起始点开始 向下搜索, 搜索所走的路径叫引用链,当一个对象到“GC Roots”没有任何引用链相连,则证明该对象不再 被引用。

如图:Object5、Object6、Object7 虽然相互引用,但到 GC Roots 没有任何引用,所以这三

个对象就被标记为要回收的对象。

3.可作为“GC Roots”的对象

1.虚拟栈(栈帧中的局部变量表)所引用的对象========>引用栈帧中局部变量表的所有对象

在程序中正常创建一个对象,对象会在 堆上开辟一块空间,同时会将这块空间的地址作为引用保存到 虚拟机栈中,如果对象生命 周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用, 就说明这 个对象还是有用的,这种情况是最常见的

2.方法区中静态属性引用的对象========>引用方法区中的静态属性的所有对象

在类中定义了全局的静态的对象,也就是使用了 static 关键字,由于虚 拟机栈是线程私有的,所以这 种对象的引用会保存在共有的方法区中,显然将方法区中的 静态引用作为 GC Roots 是必须的。

3.方法区中常量引用的对象========>引用方法区中的常量 的所有对象

常量引用,就是使用了 static final 关键字,由于这种引用初始化之后不 会修改,所以方法区常量池里 的引用的对象也应该作为 GC Roots。

4.本地方法栈(JNI)中引用的对象=-=======>引用Native方法的所有对象

在使用 JNI 技术时,有时候单纯的 Java 代码并不能满足我们的需求,我们 可能需要在 Java 中调用 C 或 C++的代码,因此会使用 native 方法,JVM 内存中专门有一块 本地方法栈,用来保存这些对象的引用, 所以本地方法栈中引用的对象也会被作为 GC Roots。

二.java中的四种引用

无论是通过引用计数器还是通过可达性分析判断对象是否存活都跟引用有关,在 JDK1.2 以前,Java 中引用的定义很传统: 如果引用类型的数据中存储的数值代表的是另一 块内存的起始地址,就称这块内存代表着一个引用。这种定义有些狭隘,一个对象在这种定 义下只有被引用或者没有被引用两种状态。

我们希望能描述这一类对象: 当内存空间还足够时,则能保存在内存中;如果内存空间 在进行垃圾回收后还是非常紧张,则可以回收这些对象

1.强引用(Reference)

强引用是java中默认的引用,类似于:A a = new A();

当一个对象具有强引用时,垃圾回收器不会回收他,当内存不足时,虚拟机会抛出OutofMemoryError错误,使程序停止运行。

2.软引用(SoftReference

软引用是用来描述一些还有用但是非必要的对象

如果一个对象具有软引用,内存足够的情况下,垃圾回收器不会回收他,如果内存空间不足,会回收对象。

只要垃圾回收器没有回收该对象,就可以被程序使用。

软引用可用来实 现内存敏感的高速缓存。

3.弱引用(WeakReference

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期,它仅仅 能够生存到垃圾回收之前,当垃圾收集时,无论内存是否足够,弱引用的对象都要 被回收。

4.虚引用

虚引用顾名思义就是“形同虚设”,虚引用并不会决定 对象的生命周期

当一个对象具有虚引用,在任何时候都可能被垃圾回收器回收,也无法通过迅游获得该对象的实例

目的当这个对象被垃圾收集器回收时,收到一个系统的通知 或 后续添加一个其他操作

注意:

当一个虚引用的对象重写了finalize()方法表示在对象被垃圾回收器清理前要做清理工作,*此时虚引用的对象直接被清理不进入引用队列如果持有虚引用的对象没有重写 finalize()方法,则表示在对象被垃圾回收器清理后要做清理工作,此时虚引用的对象被添加到引用队列中

三.垃圾回收算法

1.标记-清除算法(最基础的收集算法)

分为标记和清除两个算法

首先标记要回收的对象,在标记完成后统一回 收所有被标记的对象(标记过程通过可达性分析实现) 。

“标记-清除”算法的不足主要有两个:

效率问题:标记和清除这两个过程的效率都不高

空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在

程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾

收集。

2.复制算法

“复制”算法是为了解决“标记-清除”的内存碎片问题

实现:*它将可用内存按容量划分为 大小相等的两块,每次只使用其中一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。

此算法实现简单,运行高效。但 由于每次都要预留出 50%的空间不能存放对象,内存利用率低,内存空间严重浪费。

3.标记整理算法

标记过程仍与“标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清 理,而是让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。

四、垃圾回收机制

jvm堆内存是进行分代管理的,jvm垃圾收集器针对不同的分代使用不同的垃圾回收算法。

1.新生代垃圾回收

使用复制算法

由于新生代中 98%的对象都是"朝生夕死"的, 所以并不需要按照 1 : 1 的比例来划分内存空间,而是将内存(新生代内存)分为一块 较大的 Eden(伊甸园)空间和两块较小的 Survivor(幸存者)空间,每次使用 Eden 和其 中一块 Survivor(两个 Survivor 区域一个称为 From 区,另一个称为 To 区域)。当回 收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 空间上,最 后清理掉 Eden 和刚才用过的 Survivor 空间。

当 Survivor 空间不够用时,JVM 使用内存担保的机制将 Survivor 空间存活的对象 转移到老年代。 HotSport 默认 Eden 与 Survivor 的大小比例是 8 : 1,也就是说 Eden:Survivor From : Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的 90%, 而剩下的 10%用来存放回收后存活的对象

实现流程

当 Eden 区满的时候,会触发第一次 Minor gc,把还活着的对象拷贝到 Survivor From 区;当 Eden 区再次出发 Minor gc 的时候,会扫描 Eden 区和 From 区,对两个区 域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到 To 区域,并将 Eden 区 和 From 区清空。当后续 Eden 区又发生 Minor gc 的时候,会对 Eden 区和 To 区进行垃 圾回收,存活的对象复制到 From 区,并将 Eden 区和 To 区清空。 部分对象会在 From 区域和 To 区域中复制来复制去,如此交换 15 次(由 JVM 参数 MaxTenuringThreshold 决定,这个参数默认是 15),最终如果还存活,就存入老年代。

2. 老年代垃圾回收

老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-整理"算法。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到