【JavaEE初阶】了解JVM

news2024/11/17 1:35:30

文章目录

  • 一. 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扫描,但是频率更低了
如果老年代的对象是垃圾了,使用标记整理的方式进行释放.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CMake:检测python解释器和python库

CMake:检测python解释器和python库 导言检测python解释器CMakeLists.txt输出附录 检测python库项目结构CMakeLists.txt相关源码附录 导言 python是一种非常流行的语言。许多项目用python编写的工具,从而将主程序和库打包在一起,或者在配置或构建过程中使…

企业集团员工内部食堂餐厅食材预定订餐统计系统开发

内部食堂是针对员工食堂设计的预定系统 可以提前进行点餐,统计餐食人数 定量制作,避免浪费食材。 首页预定菜单提前显示一周菜单 用户可以提前预定想要购买的餐品 在用餐时取餐核销 食堂平台方用餐时对用户的菜品进行核销 通过后台预订信息 根据报餐统计做出对应数量…

Example: Beam Allocation in Multiuser Massive MIMO阅读笔记一

文章目录 A Machine Learning FrameworkApplication of Supervised Learning to Resource AllocationResearch Challenges and Open IssuesLow-Complexity ClassifierMulti-BS CooperationFast Evolution of Scenarios Conclusion A Machine Learning Framework 对于现有的云计…

python在函数中更改外部变量值

目录 前言 列表、字典(可变对象) 元组(不可变对象) 全局变量 前言 今天在写LeetCode题时,发现一个问题我并没有掌握,那就是如何在Python的函数中更改变量值(包括列表,字典&…

C++ - 模板分离编译

模板分离编译 我们先来看一个问题&#xff0c;我们用 stack 容器的声明定义分离的例子来引出这个问题&#xff1a; // stack.h // stack.h #pragma once #include<deque>namespace My_stack {template<class T, class Container std::deque<T>>class stack…

Python 开发工具 Pycharm —— 使用技巧Lv.3

单步执行调试 1&#xff1a; 鼠标左键单击红点是断点行 2&#xff1a;甲虫样式是进行调试方式运行&#xff0c;鼠标左键单击点击 3&#xff1a; 单步运行图标&#xff0c;点击让程序运行一行 4&#xff1a; 步入步出&#xff0c;可以进入当前代码行函数内 5&#xff1a;重新运行…

JSON.stringify()与JSON.parse()没有你想的那样简单

重新学习这两个API的起因 在本周五有线上的项目&#xff0c;16:30开始验证线上环境。 开始都是顺顺利利&#xff0c;一帆风顺。 大概17:50左右&#xff0c;我正在收拾东西。 准备下班去王者峡谷骑着我的船溜达一圈。 可是天降意外&#xff0c;给我派了一个bug。 测试给我说&am…

山西电力市场日前价格预测【2023-08-06】

日前价格预测 预测明日&#xff08;2023-08-06&#xff09;山西电力市场全天平均日前电价为411.77元/MWh。其中&#xff0c;最高日前电价为457.52元/MWh&#xff0c;预计出现在19: 30。最低日前电价为370.37元/MWh&#xff0c;预计出现在13: 15。 价差方向预测 1&#xff1a; 实…

海外应用商店优化实用指南之关键词

和SEO一样&#xff0c;关键词是ASO中的一个重要因素。就像应用程序标题一样&#xff0c;在Apple App Store和Google Play中处理应用程序关键字的方式也有所不同。 关键词研究。 对于Apple&#xff0c;我们的所有关键词只能获得100个字符&#xff0c;Google Play没有特定的关键…

数据结构10 -查找_树表查找

创建二叉搜索树 二叉搜索树 二叉搜索树是有数值的了&#xff0c;二叉搜索树是一个有序树。 若它的左子树不空&#xff0c;则左子树上所有结点的值均小于它的根结点的值&#xff1b; 若它的右子树不空&#xff0c;则右子树上所有结点的值均大于它的根结点的值&#xff1b; 它…

47.Linux学习day01 基础命令详解1(很全面)

目录 一、Linux和Windows的区别 二、Linux系统目录结构 常见目录说明 三、Linux常见的基础命令 1.pwd 2.cd 3.ls 4.man 5. touch 6.mkdir 7. rmdir 今天正式学习了linux的一些基础操作和基础知识&#xff0c;以及linux和windows的区别。 一、Linux和Windows的区…

SpringMVC基于SpringBoot的最基础框架搭建——包含数据库连接

SpringMVC基于SpringBoot的最基础框架搭建——包含数据库连接 背景目标依赖配置文件如下项目结构如下相关配置如下启动代码如下Controller如下启动成功接口调用成功 背景 工作做了一段时间&#xff0c;回忆起之前有个公司有线下笔试&#xff0c;要求考生做一个什么功能&#x…

建设全球研发中心城市,长沙与人才何以双向奔赴?

跨越山涧峡谷&#xff0c;需要搭建钢铁桥梁&#xff0c;跨越文化沟通&#xff0c;需要搭建互相理解的桥梁&#xff0c;那么&#xff0c;一座城市与人才之间的联系&#xff0c;应该搭建怎样的桥梁&#xff1f; 近日&#xff0c;长沙出台了重磅文件《中共长沙市委长沙市人民政府…

XSS漏洞原理及利用跨站请求伪造CSRF

XSS漏洞原理及利用&跨站请求伪造CSRF XSS一、案例二、什么是XSS三、XSS危害四、XSS的分类4.1、反射型XSS4.1.1、介绍4.1.2、利用过程 4.2、存储型XSS4.2.1、介绍4.2.2、利用过程4.2.3、案例 4.3、DOM型XSS4.3.1、介绍4.3.2、常用的DOM方法4.3.3、案例4.3.3.1、代码分析4.3.…

Spring源码之XML文件中Bean标签的解析1

读取XML文件&#xff0c;创建对象 xml文件里包含Bean的信息&#xff0c;为了避免多次IO&#xff0c;需要一次性读取xml文件中所有bean信息&#xff0c;加入到Spring工厂。 读取配置文件 new ClassPathResource("applicationContext.xml")ClassPathResource是Sprin…

vue动态生成行

vue代码 <el-table :data"form.lineInfos" :bordertrue style"width: 99.99%;"> <el-table-column type"index" label"序号" width"50"></el-table-column> <el-table-column prop"unitPrice&qu…

【Linux 网络】 传输层协议之TCP协议 TCP的三次握手和四次挥手

TCP协议 TCP协议段格式谈谈什么是 “可靠” 和 “不可靠”TCP协议段——序号与确认序号TCP协议段——窗口大小TCP协议段 —— 六个标志位确认应答机制&#xff08;ACK&#xff09;超时重传机制连接管理机制TCP 的三次握手四次挥手TCP三次握手四次挥手总结图 滑动窗口流量控制拥…

18. SpringBoot 如何在 POM 中引入本地 JAR 包

❤️ 个人主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;成功解决 BUG 合集 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; Spring Boot 是一种基于 Spring 框架的轻量级应用程序开发框架&#xff0c;它提供了快速开发应用程…

第126天:内网安全-隧道技术SSHDNSICMPSMB上线通讯LinuxMac

知识点 #知识点&#xff1a; 1、入站规则不出网上线方案 2、出站规则不出网上线方案 3、隧道技术-SMB&ICMP&DNS&SSH 4、控制上线-Linux&Mac&IOS&Android-连接方向&#xff1a;正向&反向&#xff08;基础课程有讲过&#xff09; -内网穿透&#xf…

vue2-vue项目中你是如何解决跨域的?

1、跨域是什么&#xff1f; 跨域本质是浏览器基于同源策略的一种安全手段。 同源策略&#xff08;sameoriginpolicy&#xff09;&#xff0c;是一种约定&#xff0c;它是浏览器最核心也是最基本的安全功能。 所谓同源&#xff08;即指在同一个域&#xff09;具有以下三个相同点…