【JavaEE初阶】了解JVM

文章目录

  • 一. JVM内存区域划分
  • 二. JVM类加载机制
    • 2.1 类加载整体流程
    • 2.2 类加载的时机
    • 2.3 双亲委派模型(经典)
  • 三. JVM垃圾回收机制(GC)
    • 3.1 GC实际工作过程
      • 3.1.1 找到垃圾/判定垃圾
        • 1. 引用计数(不是java的做法,Python/PHP)
        • 2. 可达性分析(Java的做法)
      • 3.1.2 清理垃圾
        • 1. 标记清除
        • 2. 复制算法
        • 3. 标记整理
        • 4. 分代回收

一. JVM内存区域划分

JVM启动的时候,会申请到一整个很大的内存区域.JVM是一个应用程序,要从操作系统里申请内存.JVM就根据需要,把空间分为几个部分,每个部分各自有不同的功能.具体划分如下:
在这里插入图片描述

  • Native Method Stacks(本地方法栈):native表示是JVM内部的C++代码.就是给调用native方法(JVM内部的方法)准备的栈空间.

  • Program Counter Register(程序计数器):记录当前线程执行到那个命令.(很小的一块存一个地址)每个线程有一份.

  • JVM Stack(虚拟机栈):虚拟机栈是给Java代码使用的栈. 此栈是JVM中一个特定的空间,对于 JVM虚拟机栈
    ,这里存储的是方法之间的调用关系.整个栈空间内部,可以认为是包含很多个元素(每个元素表示一个方法),把这里的每个元素称为**“栈帧”,这一个栈帧里,会包含这个方法的入口地址,方法的参数,返回地址,局部变量等.
    而对于 本地方法栈 , 存储的是
    native方法之间的调用关系**.
    虚拟机栈,不是只有一个,而是有很多个,每个线程都有一个.
    在这里插入图片描述
    由于函数调用,是有先进后出特点的.此处的栈,也是先进后出的.
    栈空间整体一般都不会很大,但是每个栈帧其实占得空间比较小,一般代码无限递归才会出现栈溢出情况.

  • Heap(堆):整个JVM空间最大的区域.new出来的对象,类的成员变量,都在堆上.**堆是一个进程只有一份,栈是每个线程有一份.**一个进程里有多个线程.所以一个进程有多个栈.每个jvm就是一个java进程.
    在这里插入图片描述

    注意这里的常见说法:栈是线程私有的.(此说法不完全对).私有的意思是我的你用不了.实际上,一个线程栈上的内容,可以被另一个线程使用.比如通过变量捕获,一个线程可以访问到另一个线程的局部变量.

  • Metaspace(元数据区):即方法区.一个进程里只有一块,多个线程共用一块.

  • 在这里插入图片描述
    小结:

  • 局部变量 在 栈

  • 普通成员变量 在 堆

  • 静态成员变量 在 方法区/元数据区

二. JVM类加载机制

2.1 类加载整体流程

类加载,准确的说,类加载就是.class文件,从文件(硬盘)被加载到内存中(元数据区)这样的过程.这个过程是非常复杂的.
.class文件可以有多个类对象
.class文件是编译后的Java源代码,它包含了编译后的字节码指令.
类加载主要分为以下几个过程:
在这里插入图片描述

  • 加载:把.class文件找到(找的过程),打开文件,地文件,把文件内容读到内存中.

  • 验证:检查.class文件格式是否正确. .class文件是一个二进制文件,这里的格式是有严格说明的,官方提供了JVM虚拟机规范.规范文档上描述了.class的格式:在这里插入图片描述
    java代码中写的类的所有信息,都会包含在上述.class文件中.使用二进制的方式重新组织.

  • 准备:给类对象分配内存空间(此时内存初始化为全0)

  • 解析:针对字符串常量进行初始化,把符号引用转为直接引用.
    字符串常量,得有一块内存空间,存这个字符的实际内容,还得有一个引用,用来保存这个内存空间的起始地址.
    在类加载之前,字符串常量,此时是处在.class文件中的,此时这个"引用"记录的并非是字符串常量的真正地址,而是它在文件中的"偏移量".(或者是占位符)(符号引用)
    类加载之后,才真正在把这个字符串常量放到内存中,此时才有"内存地址",这个引用才能被真正赋予成指定内存地址.(直接引用)

    举个🌰: 小学的时候,学校组织以班为单位大家看电影,
    但是到电影院之前我不知道自己的真实地址(真实座位),但是我知道我前面是A,后面是B,此时到了电影院,我们仨也是挨着的.(符号引用)
    此时我只知道自己的相对位置. 到了电影院之后,老师组织同学们坐下.我坐下之后才知道我们的真正位置.(直接引用)

  • 初始化:调用构造方法,进行成员初始化,执行代码块,静态代码块,加载父类.

2.2 类加载的时机

一个类,不是java程序已运行,就把所有的类都加载了.而是真正使用的时候才加载(懒汉模式)
在以下场景中会触发类加载:

  1. 构造类的实例
  2. 调用这个类的静态方法/使用静态属性
  3. 加载子类,就会先加载其父类

以上都是用到了再加载,一旦加载过后,后续使用就不用再加载.

2.3 双亲委派模型(经典)

此过程发生在加载阶段.
双亲委派模型:描述的是加载 找.class文件的基本过程.
JVM默认提供了三个类加载器:

  • 启动类加载器(BootStrap ClassLoader):负责加载Java标准库中的类(无论是哪种JVM的实现,都会提供这些一样的类)
  • 扩展类加载器(Extension ClassLoader):负责加载JVM扩展库中的类.(由实现JVM的厂商/组织提供的额外功能)
  • 应用程序类加载器(Application ClassLoader):负责加载项目中自己写的类以及第三方库中的.

在这里插入图片描述
上述三个类,存在"父子关系"(不是父类子类,相当于每个class loader有一个parent属性,指向自己的父 类加载器).
那么上述类加载器如何配合工作呢?
首先加载一个类的时候,实现从ApplicationClassLoader开始.但是ApplicationClassLoader会把家在任务交给父亲,让父亲去进行.于是ExtensionClassLoader要去加载了,但是也不是真的加载,而是再委托给自己的父亲.于是BootstrapClassLoader要去加载了,也是想委托给自己的父亲,结果发现,自己的父亲是null. 没有父亲/父亲加载完了,没找着类,才由自己进行加载,此时BootstrapClassLoader就会搜索自己负责的标准库目录的相关的类,如果找到就加载,如果没找到,就继续由子类加载器进行加载.然后由ExtensionClassLoader真正搜索扩展库相关的目录,如果找到了就加载,如果没找到就由子类加载器进行加载.然后由ApplicationClassLoader真正搜索用户项目相关的目录,如果找到就加载,如果没找到就由子类加载器进行加载(由于当前没有子了,只能抛出 类找不到这样的异常)
在这里插入图片描述
那么为什么要有上述顺序呢?
上述这套顺序其实是出自于JVM实现代码的逻辑,这段代码大概是使用递归的方式写的.但这个顺序,最主要的目的就是为了保证Bootstrap能够先加载,Application能够后加载.这样就可以避免说因为用户创建了一些奇怪的类,引起不必要的bug.
假设用户在自己的代码中写了个java.lang.String按照上述加载流程,此时JVM加载的还是标准库的类,不会加载到用户自己写的这个类. 这样就能保证,即使出现上述问题,也不会让jvm已有代码混乱,最多是用户自己写的类不生效.
另一方面,类加载器,其实是可以用户自定义的.上述三个类加载器是jvm自带的.用户自定义的类加载器,也可以加入到上述流程中,就可以和现有的加载器配合使用,

三. JVM垃圾回收机制(GC)

垃圾:指的是不再使用的内存空间.垃圾回收,就是把不用的内存帮我们自动释放.
在执行程序时,要在堆上申请一块内存空间.在C/C++中,上述内存空间需要手动方式进行释放.如果不手动释放的话,这块内存的空间就会持续存在,一直存在到进程结束(堆上的内存生命周期比较长.不像栈,栈的空间会随着方法执行结束,栈帧销毁而自动释放,堆则默认不能自动释放.)那么这就可能会导致一个严重的问题—内存泄漏.如果内存一直占着不用,又不释放,就会导致剩余空间越来越少.进一步导致后续的内存申请操作失败.因此,大佬们想了一些办法,来解决内存泄漏的问题.
GC是其中最为主流的方式.(Java Go Python PHP JS大部分的主流语言都是使用GC解决内存泄漏的问题的)

但GC中有一个比较关键的问题:STW问题(stop the world)
如果有时候,内存中的垃圾已经非常多了,此时触发一次GC操作,开销可能非常大,大到可能把系统资源吃了很多.另一方面GC回收垃圾的时候可能会涉及到一些锁操作.导致业务代码无法正常执行.这样的卡顿,极端情况下,可能是出现即使毫秒甚至上百毫秒.

JVM中有很多内存区域:

  1. 程序计数器
  2. 元数据区

  3. GC主要是针对进行释放的.
    GC是以"对象"为基本单位,进行回收的.而不是字节!
    在这里插入图片描述
    GC回收的是整个对象都不再使用的情况.而一部分使用一部分不使用的对象,暂且不回收.

3.1 GC实际工作过程

  1. 找到垃圾/判定垃圾
  2. 再进行对象的释放

3.1.1 找到垃圾/判定垃圾

找到垃圾/判定垃圾的关键就是看到底有没有"引用"指向它.如果一个对象,有引用指向它,就可能被使用到;如果一个对象,没有引用指向了,就不会再被使用了.
具体如何知道对象是否有引用指向呢?

1. 引用计数(不是java的做法,Python/PHP)

给每个对象分配了一个计数器(整数).每次创建一个引用指向该对象,计数器就+1,每次该引用被销毁,计数器就-1.

{
	Test t = new Test();//Test对象的引用计数1
	Test t2 = t;//t2也指向了t,引用计数2
	Test t3 = t;//引用计数就是3
}
//大括号结束,上述三个引用超出作用域失效.此时引用计数就是0了,此时new Test()对象就是垃圾了.

这个办法简单有效,但是java没有采用.但是这个办法也有一定的缺点:
1. 内存空间浪费的多(利用率低)
每个对象都要分配一个计数器,如果按四个字节计算.代码中的对象非常少就无所谓.但是如果对象特别多,占用的额外空间就会很多,尤其是每个对象都比较小的情 况.例如:一个对象体积1k,此时多4个字节,无所谓.一个对象体积事4字节,此时多4个字节,相当于体积扩大一倍.
2. 存在循环引用的问题

class Test{
	Test t = null;
}
Test a = new Test();//1号对象,引用计数是1
Test b = new Test();//2号对象,引用计数是1
a.t = b;//a.t也指向2号对象了,2号对象的引用是2
b.t = a;//b.t也指向1号对象了,1号对象的引用是2

接下来,a和b引用销毁,此时a和b计数-1,但引用计数结果还都是1,不能释放资源,但实际这两个对象已经无法访问了。python/php使用引用计数,需要搭配其他机制来避免循环引用.
举个🌰:
在这里插入图片描述

2. 可达性分析(Java的做法)

把对象之间的引用关系理解成了一个树形结构,从一些特殊的起点出发,进行遍历,只要能遍历访问到的对象,就是"可达的",再把"不可达的"当作垃圾即可.
在这里插入图片描述

就像上述的二叉树,root指向根节点a.
如果root.right.right = null ,此时就表示f不可达
如果root.right = null此时就表示c不可达,f也不可达了。
可达性分析的关键要点,进行上述遍历,需要有"起点"被称为gcroots.以下常做为根起点:

  1. 栈上的局部变量(每个栈的每个局部变量,都是起点)
  2. 常量池中引用的对象
  3. 方法区中,静态成员引用的对象

可达性分析,总的来所,就是从所有的gcroots的起点出发,看看该对象里又通过引用能访问那些对象,依次遍历,把所有可以访问的对象都给遍历一遍(遍历的同时把对象标记成"可达"),剩下的遍历不到的对象就是"不可达".

可达性分析的特点:可达性分析克服了引用计数的两个缺点,但是也有自己的缺点:

  1. 消耗的时间更多,因此某个对象成了垃圾,也不一定能第一时间发现,因为扫描的过程,需要消耗时间
  2. 在进行可达性分析的时候,依次遍历,一旦这个过程中,当前代码中的对象引用关系发生了变化,这就会使情况变得更加复杂。比如,当一个对象指向下一个对象,刚遍历完这个对象,这个对象的引用变了。因此,我们为了更准确的遍历,需要让其他的业务线程暂停工作(STW问题)。

3.1.2 清理垃圾

主要有三种基本做法:

1. 标记清除

这种策略,就是直接把垃圾对象的内存释放,但是这个方式的缺点就是会产生内存碎片.
在这里插入图片描述
我们从内存中申请空间的时候,都是整块的连续的空间,现在这里空闲的空间是离散的,独立的空间,总的空间可能很大.假如总的空闲的空间可能超过了1G,但是你想申请500MB可能都不一定申请到。

2. 复制算法

为了解决内存碎片的问题,又引入了复制算法.复制算法,是把整个内存空间,分成两半,一次只用一半.
在这里插入图片描述
现在将2和4标记为垃圾,要释放垃圾,复制算法会将左边不需要释放内存的空间复制到右边的空间中,然后整体释放左边空间的内存.

复制算法,就是把"不是垃圾"的对象复制到另外一半,然后把整个空间删除掉.
每次触发复制算法,都是向另外─侧进行复制,内存中的数据拷贝过去.

缺点:

  1. 空间利用率低
  2. 如果要是垃圾少,有效对象多,复制成本就比较大了~~

3. 标记整理

在这里插入图片描述
这种方法,保证了空间利用率,同时也解决了内存碎片问题
但是这种做法的缺点:

  1. 效率不高如果要搬运的空间比较大,此时开销也很大

4. 分代回收

基于上述这些基本策略,搞了一个复合策略"分代回收"
把垃圾回收,分成不同的场景,有的场景有这个算法,有的场景有那个,各展所长.

分代是怎么分的?
基于一个经验规律:如果一个东西,存在的时间比较长了,那么大概率还会继续的长时间持续存在下去.(要没早就没了,既然存在,肯定有点用)
规律不等于"定律",允许例外,针对大部分情况有效的.

上述规律,对于Java的对象也是有效的.(是有一系列的实验和论证过程)
java的对象要么就是生命周期特别短,要么就是特别长.根据生命周期的长短,分别使用不同的算法.
给对象引入一个概念,年龄.(单位不是年,而是熬过GC的轮次)(经过了这一轮可达性分析的遍历,发现这个对象还不是垃圾.这就是"熬过一轮GC") 年龄越大,这个对象存在的时间就越久.
在这里插入图片描述
刚new 出来的,年龄是0的对象,放到伊甸区.(出自圣经,上帝在伊甸园造小人)
熬过一轮GC,对象就要被放到幸存区了.虽然看起来幸存区很小,伊甸区很大,一般够放.
伊甸区到幸存区,采用的是复制算法.

幸存区之后,也要周期性的接受GC的考验.
如果变成垃圾,就要被释放.如果不是垃圾,拷贝到另外一个幸存区(这俩幸存区同一时刻只用一个),在两者之间来回拷贝(复制算法),由于幸存区体积不大, 此处的空间浪费也能接受.如果这个对象已经再两个幸存区中来回拷贝很多次了这个时候就要进入老年代了·

老年代都是年纪大的对象.生命周期普遍更长.针对老年代,也要周期性GC扫描,但是频率更低了
如果老年代的对象是垃圾了,使用标记整理的方式进行释放.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/60239.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自学(黑客)技术,入门到入狱!

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高; 二、则是发展相对成熟入…

@ControllerAdvice注解使用及原理探究 | 京东物流技术团队

最近在新项目的开发过程中,遇到了个问题,需要将一些异常的业务流程返回给前端,需要提供给前端不同的响应码,前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方,如果每个地方都写一些…

Typescript中的元组与数组的区别

Typescript中的元组与数组的区别 元组可以应用在经纬度这样明确固定长度和类型的场景下 //元组和数组类似,但是类型注解时会不一样//元组赋值的类型、位置、个数需要和定义的类型、位置、个数完全一致,不然会报错。 // 数组 某个位置的值可以是注解中的…

正点原子HAL库入门1~GPIO

探索者F407ZGT6(V3) 理论基础 IO端口基本结构 F4/F7/H7系列的IO端口 F1在输出模式,禁止使用内部上下拉 F4/F7/H7在输出模式,可以使用内部上下拉不同系列IO翻转速度不同 F1系列的IO端口 施密特触发器:将非标准方波,整形为方波 当…

01-序言

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan 简介: 此专栏是学习“线性代数”课程做的笔记,教程来自B站。视频作者是Grant Sanderson, 这套视频里的动画是他通过manim制作的(manim是…

怎么维护好自己的电脑

你的电脑已经成为你工作、学习、娱乐的最佳工具之一,但是如果你不做好电脑维护工作,就可能面临着电脑变慢、蓝屏、崩溃等问题。在这篇文章中,我们将介绍10个电脑维护步骤,让你的电脑更加稳定! 为什么需要电脑维护&…

python数据处理程序代码,如何用python处理数据

大家好,给大家分享一下python数据处理程序代码,很多人还不知道这一点。下面详细解释一下。现在让我们来看看! 要求:分别以james,julie,mikey,sarah四个学生的名字建立文本文件,分别存…

285 · 高楼大厦

链接:LintCode 炼码 - ChatGPT!更高效的学习体验! 题解: 1.从左往右维护一个单调递减,栈的长度就是,可以看到最多的高楼 2.从右往后也是维护一个单调递减的栈 class Solution { public:/*** param arr:…

Java判断文件的系统格式编码格式

使用Java判断一个文件的系统格式(亲测可用),比如我们常见的Windows格式的文件,Unixg格式的文件,Mac格式的文件;常常有这样的场景:我们在Windows系统编写的脚步上传到Linux系统执行,执…

【验证测试】未初始化的全局变量和局部变量的初值

验证目标&#xff1a; 未初始化的全局变量的初值为 0未初始化的局部变量的初值为随机值 测试用例&#xff1a; #include <stdio.h>char gval1; int gval2; static long gval3;int main() {unsigned char uchTmp1;unsigned int uTmp2;printf("%d\n", gval1)…

Centos虚拟机忘记密码-修改密码

1.重启系统 2.在这个选择界面&#xff0c;按e建 3.找到如下位置&#xff0c;插入init/bin/sh 4.填写完成后按Ctrlx引导启动 5.输入mount -o remount, rw / (注意空格) 6.重置密码 出现以下为重置成功 7.执行touch /.autorelabel 8.退出exec /sbin/init 9.输入你的新密…

14-4_Qt 5.9 C++开发指南_QUdpSocket实现 UDP 通信_UDP组播

文章目录 1. UDP组播的特性2. UDP 组播实例程序的功能3. 组播功能的程序实现4. 源码4.1 可视化UI设计4.2 mainwindow.h4.3 mainwindow.cpp 1. UDP组播的特性 下图简单表示了组播的原理。UDP 组播是主机之间“一对一组”的通信模式&#xff0c;当多个客户端加入由一个组播地址定…

STM32——STM32F401x系列标准库的下载+环境搭建+建工程步骤(更完整)

文章目录 标准库的下载环境搭建建工程最后的话 标准库的下载 1.STM32标准库的官网下载网站https://www.st.com/content/st_com/en.html 2. 3. 4. 5. 6. 7.点击之后下滑 8.选择自己需要的版本下载 环境搭建建工程 大致步骤同之前我写的一篇STM32——建工程差不多&#xff0…

快速WordPress个人博客并内网穿透发布到互联网

快速WordPress个人博客并内网穿透发布到互联网 文章目录 快速WordPress个人博客并内网穿透发布到互联网 我们能够通过cpolar完整的搭建起一个属于自己的网站&#xff0c;并且通过cpolar建立的数据隧道&#xff0c;从而让我们存放在本地电脑上的网站&#xff0c;能够为公众互联网…

jenkins准备

回到目录 jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具&#xff0c;主要用于持续、自动的构建/测试软件项目、监控外部任务的运行。Jenkins用Java语言编写&#xff0c;可在Tomcat等流行的servlet容器中运行&#xff0c;也可独立运行。通常与版本管理工具(SCM)、构…

【每日易题】数据结构链表篇——单链表oj题(1),几道典型例题带你快速掌握单链表

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;今天来填一个埋了好久的坑&#xff0c;在暑假之前就预告过这个系列&#xff0c;但由于各种原因&#xff08;主要是有点懒&#xff09;今天才开坑。我们这个系列主要是…

Scratch 之 如何制作简单的碰撞箱

hello&#xff0c;大家好&#xff0c;今天教给大家如何制作粗糙的碰撞箱 本次教学不用拓展、不用自制积木、不用变量、不用列表 首先明确原理 图1 我们想做到第一个 而大家不要做成第二个 错误示范&#xff1a; 然后就变成了 咳咳咳 这就是我们不想看到的 于是你可以这样…

ESP32-C2开发板 ESP8684芯片 兼容ESP32-C3开发

C2是一个芯片采用4毫米x 4毫米封装&#xff0c;与272 kB内存。它运行框架&#xff0c;例如ESP-Jumpstart和ESP造雨者&#xff0c;同时它也运行ESP-IDF。ESP-IDF是Espressif面向嵌入式物联网设备的开源实时操作系统&#xff0c;受到了全球用户的信赖。它由支持Espressif以及所有…

文心一言 VS 讯飞星火 VS chatgpt (71)-- 算法导论7.1 1题

参照图 7-1的方法&#xff0c;说明 PARTITION在数组 A(13&#xff0c;19&#xff0c;9&#xff0c;5&#xff0c;12&#xff0c;8&#xff0c;7&#xff0c;4&#xff0c;21&#xff0c;2&#xff0c;6&#xff0c;11)上的操作过程。 文心一言&#xff1a; PARTITION 是一种常…

【ASP.NET MVC】使用动软(一)(9)

一、解决的问题 前文为解决数据库操作设计的 TestMysql 类&#xff0c;仅简单地封装了一个Query函数&#xff0c;代码如下&#xff1a; public class TestMysql{public static string SqlserverConnectStr "server127.0.0.1;charsetutf8;user idroot;persistsecurityin…