当前位置: 首页 > news >正文

【EE初阶】JVM

文章目录

  • JVM
    • (一) JVM 内存区域划分
    • (二) 类加载机制
    • (三) 垃圾回收机制

JVM

JVM的执行流程
程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader) 把文件加载到内存中运行时数据区(Runtime Data Area) ,而字节码文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器**执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口本地库接口(Native Interface)**来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。

(一) JVM 内存区域划分

  • 为啥要区域划分?

JVM 就是Java虚拟机 仿照真是的机器,真实的操作系统进行设计
真实的操作系统中,对于进程的地址空间是进行了 分区域设计的
JVM也就仿照操作系统的情况,进行了分区域设计~~

在这里插入图片描述

比如有一栋写字楼(操作系统)
有很多层,每一层相当于一个进程
我们的公司在一层上,这一层上租了下来,进行装修,分出若干个办公室
就相当于把这个楼层划分出若干个区域,有着不同的作用(人事,老板,开发人员…)

  • JVM具体是怎么划分的呢?

程序计数器的作用:用来记录当前线程执行的行号的。
程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器值为空。
程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM情况的区域!

四个核心区域:
1.程序计数器 => 是一块很小的区域,只是用来记录当前指令执行到哪个地址了
2.元数据区 => 保存当前类被加载好的数据~~
比如我们刚开始学Java的时候 .java => .class => 加载到内存中
要想运行这个代码,就需要把 .class 加载到内存里面(类加载)
3.
写代码的时候,肯定会有方法调用~
每次调用方法,就会进入方法内部执行,当方法执行完毕,返回到调用位置,继续往后走
注意了,这里的栈和数据结构里的栈并不是一回事在这里插入图片描述
那和函数栈帧有关系吗?
JVM中的栈和栈帧,概念上是差不多的,但东西不是一个东西
JVM这个进程,是由C++代码实现的程序,这个进程本身就是存在一系列方法调用的~~
在这里插入图片描述
4.
保存 new 的对象的
比如 Test t = new Test();
如果 t 是一个局部变量 => t 在栈上
如果 t 是一个成员变量 => t 在堆上
如果 t 是一个静态成员变量 => t 在元数据区

堆就是JVM中最大的空间区域了
往集合类里面添加元素,如果堆上的对象,不再使用了的话,就需要被释放掉(就是后面要讨论的垃圾回收机制)
在这里插入图片描述>元数据区 和 堆,整个Java进程 共用同一份>程序计数器 和 栈 ,一个进程中可能有多份(每个线程有一份)

局部变量: 栈帧结束,也就销毁了
成员变量:在堆上,被释放(垃圾回收)
静态成员变量:不会释放,只谈类加载,不谈类卸载

(二) 类加载机制

类加载本身是一个很复杂的事情
我们主要关心两个方面
1.类加载的步骤
Java的官方文档上,也有对应的描述,感兴趣可以仔细阅读
一共三个大阶段,其中第二个阶段,又分成了三个小步骤 => 一共是5个步骤
a) 加载: 找到.class文件
根据类的 全限定类名(包名 + 类名,形如:java.lang.String这样的)
打开文件,读取文件内容到内存里
b)验证:解析,校验 .class文件读到的内容,是否是合法的,并且把这里的内容转成 结构化数据 (.class 文件是二进制文件,格式都是有明确要求的~~)
c)准备:给类对象 申请内存空间,此处申请的空间相当于"全0"的空间(未初始化的内存空间)
d)解析:针对字符串常量,进行初始化
字符串常量,本身就包含在 .class文件中 =>就需要 .class文件里解析出来的字符串常量放到 内存空间里(元数据区,常量池中)
e)初始化:针对刚才谈到的类对象进行最终的初始化
针对类对象的各种属性进行填充,包括 类中的静态成员
如果这个类还有父类,并且父类还没加载 =>触发父类的类加载
2.类加载的"双亲委派模型"
双亲委派模型 => 描述了类加载中,根据 全限定类名(包名 + 类名 ) 找到 .class文件的过程

  • 类加载触发的时机

Java程序一启动,就会加载用到的所有的类吗 -->不是的!!!
懒汉模式/懒加载
Java代码,用到哪个类,就会触发哪个类的加载
比如
1.构造了这个类的实例 ->加载
2.调用/使用了 类静态属性/静态方法
2.使用某个类的时候,他的父类若没有加载,也会触发父类的加载

  • 双亲委派模型(类加载这里的高频问题)
  • 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

在这里插入图片描述
在这里插入图片描述

双亲委派模型的过程:

进行类加载,通过全限定类名,找 .class 的时候
就会从 ApplicationClassLoader 作为入口开始,
然后把 加载类 这样的任务,委托给 父亲 来进行, ExtensionClassLoader
ExtensionClassLoader 也不会立即进行查找, 而是也委托给 父亲 来进行,BootstrapClassLoader,BootstrapClassLoader 也想委托给父亲, 由于没有父亲, 只能自己进行类加载. 根据类名, 找标准库范围, 是否存在匹配的 .class 文件
BootstrapClassLoader 没有找到, 再把任务还给 孩子 ExtensionClassLoader , 接下来 ExtensionClassLoader 来负责进行找 .class 文件的过程. 找到就加载, 没找到, 也就把任务还给孩子 ApplicationClassLoader. 接下来 ApplicationClassLoader 负责找 .class. 找到就加载, 没找到就抛出异常

在这里插入图片描述
给出的这三个类加载器,是属于 JVM 自带的.

程序员是可以自定义类加载器的~~
当你自定义的时候,就可以把你的类加载器,也放到双亲委派模型中,也可以不放到里面.

Tomcat (Java 中的知名的 HTTP 服务器) 的内部就有自定义的类加载器
(由于现在用的 Spring MVC 内置了 tomcat, 用的时候感知不到了)
从指定的 webapps 目录加载对应的类~~

(三) 垃圾回收机制

垃圾回收 => Java中释放内存的手段
比如C语言中,申请内存使用 malloc
申请之后,一定要手动调用 free 进行释放,否则就会出现 内存泄漏
要是 free 不小心忘记了 或者因为 提前return 了没执行到 free ,就直接出错了

Java引入垃圾回收,进行自动释放,JVM会自动识别出,某个内存,是不是后续不再使用了,自动释放

C++并没有引入GC,就是因为STW问题(卡了),运行效率会有所影响,但是现在Java17及以上,STW问题大部分情况下 < 1ms 时间了

  • GC回收 是回收 JVM中 堆 内存区域

程序计数器 =>线程销毁,自然释放
栈 => 方法执行结束,栈帧就结束,随之释放了
元数据区 => 类对象,一般不会释放
堆 => 创建很多新对象,也会有就对象消亡
说是"回收内存" 本质上 “回收对象”

  • GC工作过程

1.找到垃圾(不再使用的对象)
2.释放垃圾(对应的内存释放掉)

  • 找到垃圾(两个方法)

1.引用计数
2.可达性分析(Java采取这个方案)

1.找到垃圾(不再使用的对象) 1) 引用计数 => 每个对象 new 的时候,都搭配一个小的内存空间保存一个整数

在这里插入图片描述
但这个方法有两个缺点:
1)内存消耗更多
尤其是对象本身比较小,引用次数消耗得空间比例就更大了
假设引用计数是 4 个字节
对象本身是 8 个字节.
引用计数就相当于提高了 50% 的空间占用率.2) 可能出现 “循环引用” 这样的问题
class Test {
Test t = null;
}
Test a = new Test();
Test b = new Test();
a.t = b;
b.t = a;
a = null;
b = null;

可达性分析 => 用时间换空间
1.以代码中的一些特定对象,作为 遍历的"起点" =>GCRoots
1)栈上的局部变量(引用类型)
2)常量池引用指向的对象
3)静态成员
这三个对象,程序运行到任何一个时刻,JVM都是容易获取到的
2.尽可能的进行遍历
判定某个对象是否能访问到
3.每次访问到一个对象,都会把这个对象标记成"可达"
当完成所有的对象的遍历之后
未被标记成 “可达” 的对象就是 “不可达”

JVM 中一共有多少个对象, JVM 自身是知道了.
通过可达性分析, 知道了哪些是 “可达” 的, 剩下的就是 “不可达” =>接下来要回收的垃圾

  • 已经知道哪些对象 是垃圾了,如何进行释放呢?

1.标记 - 清除
2.复制算法
3.标记 - 整理
Java使用 分代回收 ~~ 把3个方法(主要是后2个) 结合起来,扬长避短

1.标记 - 清除
把垃圾对象的内存,直接进行释放.
这样做会产生内存碎片问题
在这里插入图片描述
2.复制算法
在这里插入图片描述
3.标记 - 整理
在这里插入图片描述

Java使用的是 分代回收,把3个方法(主要是后2个) 结合起来,扬长避短

“代” =>指的是 对象的年龄 也就是GC的轮次
某个对象, 经历一轮 GC 可达性分析之后, 不是垃圾;
此时对象的年龄就 + 1 ; 初始情况就是 0
在这里插入图片描述
经验规律:
如果一个对象是小年轻, 这个对象就很可能快速就挂掉
如果一个对象是老油条, 这个对象就可能继续存在

老年代, GC 频次就可以降低了
新生代, GC 频次就会比较高~~

  • 新创建的对象就放到 “伊甸区”,绝大部分的伊甸区的对象, 活不过第一轮 GC 所以幸存区比伊甸区小~~

  • 伊甸区 => 幸存区: 复制算法, 复制的对象规模是很少~~ 复制的开销可控的~

  • 幸存区中的对象, 也要经历 GC 的扫描.每一轮 GC 都会消灭一大部分对象;剩余的对象再次通过 复制算法, 复制到另外一个幸存区

  • 如果这个对象在 幸存区 中经历了多次复制, 都存活下来了, 对象的年龄就大了,就会晋升到 老年代 中

一个对象: 伊甸区 => 幸存区 => 幸存区 …=> 幸存区 => 老年代

蓝色过程为:复制算法
红色过程为:标记 - 整理

  • 新生代中的对象大部分会快速消亡,使得每次复制的开销都可控

  • 老年代的对象大部分会生命周期较长,使得整理的开销也都可控~~

  • 大概是经历15轮次GC机制,才能从幸存区 晋升到 老年代

好!那么到这里我们的EE初阶内容就到这里了,EE进阶的内容我们会结合spring结合初阶学过的知识,做出综合性的网站,做出更有实际价值的程序~~,大家可以期待后续的EE进阶内容!!!

http://www.dtcms.com/a/520465.html

相关文章:

  • 深度学习------YOLOv5《第一篇》
  • 手机网站无法访问的解决方法文字图片制作网站
  • 叙述一个网站开发流程住房建设和城乡管理局官网
  • HarmonyOS 分布式与 AI 集成:构建智能协同应用的进阶实践
  • Trae x 图片素描MCP一键将普通图片转换为多风格素描效果
  • 游艇网站建设方案网页给别人做的 网站后续收费
  • UE5 外轮廓线,边缘,边界
  • Jackson 序列化的隐性成本
  • ProcDump 学习笔记(6.5):指定转储文件路径与命名策略
  • STM32项目分享:智能植物灌溉系统
  • 高级软考-系统架构设计师知识点1
  • 东城企业网站建设潍坊网站优化培训
  • 信阳网站建设哪个好河北邢台重大新闻
  • 《Python 自动化上传豆瓣电影到飞书:十个真实踩坑记录与避坑指南》
  • ubuntu24.4下载mysql报错解决、下载maraiDB
  • 建设银行网站修改预留手机号企业展厅设计公司100%正品保障
  • 数据结构 08 线性结构
  • 【Linux网络】Socket编程UDP
  • 互动网站建设多少钱wordpress怎么开发app
  • Linux 常见命令汇总:从入门到实用的效率工具包
  • Linux修炼:进程控制(二)
  • 机器学习笔记-假设检验
  • 自然语言处理(NLP)—发展历程(背景、技术、优缺点、未来方向)
  • 【实战】自然语言处理--长文本分类(1)DPCNN算法
  • 兰州网站建设多少钱网页制作和设计实验目的
  • 专门做动漫的网站有哪些网站开发文件结构组成
  • Flexbox
  • `.bat`、`.cmd`、`.ps1`的区别
  • MySQL 安装教程(Windows 版):从入门到配置全流程
  • 网站建设责任分解杭州市建筑业协会官网