【Java EE初阶三十】JVM的简单学习

1. JVM 内存区域划分

        
        一个运行起来的 Java 进程,就是一个 JVM 虚拟机,需要从操作系统申请一大块内存,就会把这个内存,划分成不同的区域,每个区域都有不同的作用.
        JVM 申请了一大块内存之后,也会划分成不同的内存区域,下面来详细讲解一下各个区域:

1、方法区

        (jdk1.7 及其之前)/ 元数据区 (jdk1.8 开始)
        这里存储的内容,就是类对象。.class 文件加载到内存之后,就成了类对象。

2、堆
        这里存储的内容,就是代码中 new 的对象(该区域是占据空间最大的区域)

3、栈
        这里存储的内容,就是代码执行过程中,方法之间的调用关系,如下图所示:

        4. 程序计数器
        是一个比较小的空间,主要就是存放一个"地址",表示下一条要执行的指令在内存中的哪个地方(方法区里每个方法里面的指令, 都是以二进制的形式保存到对应的类对象中的),刚开始调用方法,程序计数器记录的就是方法的入口的地址,随着一条一条的执行指令,每执行一条,程序计数器的值都会自动更新,即去指向下一条指令;

        如果是一个顺序执行的代码,下一条指令就是把指令地址进行递增;

        如果是条件/循环代码,下一条指令就可能会跳转到一个比较远的地址;

        下面进行举例:

主要知识点如下图所示:

        本地方法:指的是使用 native 关键字修饰的方法.这个方法不是使用 java 实现,而是在jvm 内部通过 C++ 代码实现的,JVM 内部的 C++ 代码调用关系;

        虚拟机栈,以及程序计数器都是每个线程都有一份,JVM 进程中有 10 个线程就会有 10 个虚拟机栈,也会有 10 个程序计数器(每个线程各自有一个)

        堆区和元数据区,这俩东西在 JvM 进程中是只有一份的

        下面给你一个代码,问你某个变量是处于内存中的哪个区域中?

        一个变量处于哪个区域,和变量的形态密切相关,局部变量处于栈上。成员变量在堆上,静态变量在与数据区内;同时我们要知道变量处于哪个空间上,和变量是不是引用类型,是不是基本类型是没有关系的

        new 出来的对象在堆上,于此同时又创建了一个局部变量 Test t引用类型的变量,就把对象地址存储到t里面了,t里面存储的不是对象本身;

        一个 JvM 进程里,可能有多个线程.每个线程,有自己的程序计数器和栈空间,这些线程共用同一份堆和方法区;

2. jvm中类加载的过程

        java 代码会被编译成.class 文件(包含了一些字节码),java 程序要想运行起来,就需要让 jvm读取到这些.class 文件,并且把里面的内容,构造成类对象并保存到内存的方法区中.(所谓的"执行代码”,就是调用方法.就需要先知道每个方法编译后生成的指令都是啥)

        一般来说将整个类加载的过程分为5个部分:

2.1 加载

        找到 .class 文件,打开文件,读取文件内容;

        往往代码中,会给定某个类的,"全限定类名“,例如java.lang.String,java.util.ArrayList,jvm 就会根据这个类名,在一些指定的目录范围内查找.

2.2 .验证

        .class 文件是一个二进制的格式,(每一个字节,都是有某些特定含义的)就需要验证你当前读到的这个格式是否符合要求.

        下图就是.class文件需要遵循的格式:

2.3 准备 

        给类对象分配内存空间 (最终的目标就是要构造出 类对象)

        我们这一步只是分配内存空间,还没有初始化呢.此时这个空间上的内存的数值,就是全0 ,(此时如果尝试打印类的 static 成员,结果就是全0的);

2.4 解析

         针对类对象中包含的字符串常量进行处理, 进行一些初始化操作,java 代码中用到的字符串常量在编译之后,也会进入到 .class 文件中;下面通过对字符串s的解析进行进一步说明:

        于此同时,.class 文件的二进制指令中,也会有一个s这样的引用被创建出来,由于引用里面本质上保存的是一个变量的地址,在 .class 文件中,这是文件不涉及到内存地址,因此住 .class 文件中,s 的初始化语句,即先会被设置成一个“文件偏移量”,通过偏移量,就能找到"test"这个字符串所在的位置;

        下面来简单说明一下文件偏移量:

        接下来,

这个过程,也叫做: 把"符号引用(文件偏移量)替换成“"直接引用)(内存地址)

2.5 初始化

        针对类对象进行初始化

        把类对象中需要的各个属性都设置好,同时还需要初始化好 static 成员变量,还需要执行静态代码块,以及还可能需要加载一下父类.

2.6 双亲委派模型

        双亲委派模型属于类加载中第一个步骤->"加载"过程中的一个环节,主要是根据负责根据全限定类名来找到 .class 文件.

        类加载器是 JVM 中的一个模块,JM 中中内置了下面如图所示的三个类加载器:

        如上图所示,这个父子关系并不是“继承"构成的,而是这几个 ClassLoader 里有一个 parent 这样的属性,指向了一个 父"类加载器";

        下面来详细描述一下类加载的过程(即找 .class 文件的过程):

1、给定一个类的全限定类名,形如 java.lang.String;

2、从 Application ClassLoader 作为入口,开始执行査找的逻辑

3、Application ClassLoader不会立即去扫描自己负责的目录(负责的是搜索项目当前目录和第三方库对应目录),而是把查找的任务,交给它的父亲,Extension ClassLoade

4、 Extension ClassLoader,也不会立即扫描自己负责的目录(负责的是 JDK 中一些扩展的库所对应的目录),而是把查找的任务交给它的父亲BootStrap ClassLoade;

5、Bootstrap ClassLoader,也不想立即扫描自己负责的目录(负责的是 标准库 的目录),也想把任务交给它的父亲,结果发现,自己没有父亲,因此 BootStrap ClassLoader 只能亲自负责扫描标准库的目录,如下所示:

        像java.lang.String.这种类,就能够在标准库中,找到对应的 .class 文件,就可以进行打开文件,读取文件......此时,查找 .class 文件的过程就结束了;但是,如果给定的类不是标准库的类,任务仍然会被交给孩子来执行;

6、没有扫描到就会回到 Extension ClassLoader.Extension ClassLoader 就会扫描负责的扩展库的目录.如果找到,就执行后续的类加载操作,此时查找过程结束还没有没找到,就会把任务交给他的孩子来执行.

7、没有扫描到, 就会回到 Application ClassLoade),Application ClassLoader 就会负责扫描当前项目和第三方库的目录.如果找到,就执行后续的类加载操作.如果没找到,就会抛出一个 ClassNotFoundException;

        综上所述,所谓的“双亲委派模型“,其实就是一个查找优先级的问题;简单的查找流程如下图所示:

        之所以搞这一套流程,主要的目的是为了确保标准库的类被加载的优先级最高,其次是扩展库,其次是自己写的类和第三方库;

3. GC 垃圾回收 

        C 语言中,像 malloc 这种, 就属于是"动态申请内存”(运行时),其实很多时候, 需要程序真正跑起来,才能确定内存的大小;

        在C 语言中, 使用 malloc 申请的内存,需要在用完之后, 通过 free 来释放。(此处如果不释放,就会产生"内存泄露”这样的问题)

        C++里,动态申请内存,变成 new,malloc 只是申请内存.new,能够申请内存,也能初始化(调用构造函数),在C++ 中,使用 new 申请的内存,用完之后, 通过 delete 来释放;

        在 Java 中, new 一个对象也就是"动态内存申请”;

        综上所述,相比之下, java 给出了一个方案,垃圾回收机制 (GC),让 JVM 自行判定,某个内存是否就不再使用了,如果这个内存后面确实不用了,JVM 就自动的把这个内存给回收掉,此时就不必让程序猿自己手动写代码回收;

        下面首先来了解一下GC机制的缺陷:

        1.系统开销,需要有一个或一些特定的线程,不停的扫描内存中的所有的对象, 看是否能够回收,此时是需要额外的内存和CPU 资源的.

        2.效率问题,这样的扫描线程,不一定能够及时的释放内存(扫描总是有一定周期的)一旦同一时刻,出现大量的对象都需要被回收,GC 产生的负担就会很大.甚至引起整个程序都卡顿.(STW 问题:即stop the world);

        GC 是垃圾回收,GC 回收的目标其实是内存中的对象,对于 Java 来说, 就是 new 出来的这些对象;栈里的局部变量,是跟随着栈帧的生命周期走的,(方法执行结束,栈帧销毁,该部分的内存自然释放);静态变量的生命周期就是整个程序,这个始终存在就意味着静态变量是无需释放的;因此真正需要 gc 释放的, 就是堆上的对象了.

        gc 可以理解成两个大的步骤:
1. 找到垃圾
2. 释放垃圾

3.1  找到垃圾

        在 GC 的领域中, 有两种主流的方案:

1)、引用计数 [Python, PHP]

        new 出来的对象,单独安排一块空间,来保存一个计数器,如下图所示:

        在Java 中,使用对象,必须要依靠引用,如果一个对象,没有引用指向了,就可以视为是垃圾了(引用计数就是0 )

        对于上述代码,出了 { }之后,t 和 t2 就都销毁了,即引用计数就要归0了,当对象的引用计数为 0,此时代码中就不可能访问到这个对象了,此时这个对象就可以视为是垃圾了;

        关于 java 不使用引用计数的分析,引用计数存在两个重要的问题:

        1、比较浪费内存.
        计数器最少需要2个字节,如果对象本身就很小,这个计数器占据的空间比例就很大;
比如对象本身就 2 个字节,计数器占据的空间就是 50%;如果对象本身 4个字节?计数器占据的空间就是 33% ;如果对象很少, 或者对象比较大, 都影响不大。但是如果对象小并且很多,计数器所占据的空间就十分巨大;

        2、引用计数机制,存在"循环引用”问题,如下面的一段代码所示:

 a,b及其引用的内存分布如下所示:

        此时,当前a和 b 两个引用已经销毁了,new 出来的这俩对象,已经无法被其他代码访问到了,但是他们的引用计数却是不为0的,所以这俩对象是不能被回收的;此时,第一个对象引用了第二个对象,第二个对象引用了第一个对象.

2)、可达性分析 【java】
        可达性分析本质上是时间换空间的手段;

        有一个/一组线程周期性的扫描我们代码中所有的对象,从一些特定的对象出发,尽可能的进行访问的遍历,把所有能够访问到的对象,都标记成“可达”,反之,经过扫描之后, 未被标记的对象,就是垃圾;

        不仅仅是所有的局部变量,还有常量池中引用的对象,还有方法区中的静态引用类型引用的变量,都统称为 GCRoots,当然,这里的遍历大概率是 N 叉树.主要就是看所访问的某个对象,里面有多少个引用类型的成员,并针对每个引用类型的成员都需要进一步的进行遍历;

        可达性分析都是周期性进行的,当前某个对象是否是垃圾,是随着代码的执行而发生改变,总之就是可达性分析比较消耗系统资源,开销比较大;

3.2 回收垃圾

3.2.1 标记清除

        该方式是比较简单粗暴的释放方式,下面黑色区域是被标记的要被清除的,

        把对应的对象,直接释放掉,就是标记清除的方案,但是这个方案其实非常不好,因为会产生很多的内存碎片,释放内存的主要目的是为了让别的代码能够申请到"连续”的内存空间,但是这样会导致我们能用的内存是断断续续的;随着时间的推移,内存碎片的情况就会越演越烈,如此就会导致后续内存申请举步维艰.

3.2.2 复制算法

        通过复制的方式,把有效的对象归类到一起.再统一释放剩下的空间;

        

        把内存分成两份,一次只用其中的一半,这个方案可以有效解决内存碎片的问题.但是缺点也很明显:
1、内存要浪费一半,利用率不高;
2、如果有效的对象非常多,拷贝开销就很大;

3.2.3 标记整理

        既能够解决内存碎片的问题,又能处理复制算法中利用率;

        类似于顺序表删除元素的搬运操作

3.2.4 分代回收

        实际上,JVM 采取的释放思路是上述基础思路结合体,分代回收,对象能活过的 GC 扫描轮次越多, 就是越老;

        伊甸区:

        刚 new 的新的对象放到伊甸区,从对象诞生,到第一轮可达性分析扫描,这个过程中
虽然时间不长(往往就是毫秒或秒)但是,在这个时间里,大部分的对象都会成为垃圾,

        释放过程如下所示:

1)、伊甸区 =>幸存区

         使用复制算法,每一轮 GC 扫描之后, 都把有效对象复制到幸存区中,伊甸区就可以整个释放了,由于经验规律,真正需要复制的对象不多,所以非常适合复制算法;

2)、GC 扫描线程也会扫描幸存区.
        就会把活过GC 扫描的对象(扫描过程中可达),拷贝到幸存区的另一个部分,幸存区之间的拷贝,每一轮会拷贝多个对象,每一轮也会淘汰掉一批对象(有些对象随着时间的推移,就成了垃圾)

3)、当这个对象已经在幸存区存活过很多轮 GC 扫描之后,JVM 就认为这个对象,短时间内应该是释放不掉了,就会把这个对象拷贝到老年代;

4)、进入老年代的对象, 虽然也会被 GC 扫描,但是老年代 GC 扫描的频率就会比新生代;

        分代回收,是 JVM 中主要的回收的思想方法.,但是在垃圾回收器具体实现的时候,可能还会有一些调整和优化.

ps:到这里java ee初阶的内容就结束了,感谢陪伴了很久的自己,尤其是在上完班后晚上敲写博客的自己,还是那句话,虽然多阻滞,花发再重荣!!!

        

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

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

相关文章

Git 基于ED25519、RSA算法生成 SSH 密钥

Git 基于ED25519、RSA算法生成 SSH 密钥 基于ED25519算法&#xff0c;生成密钥对命令如下&#xff1a; ssh-keygen -t ed25519 -C "邮箱地址"基于RSA算法&#xff0c;生成密钥对命令如下&#xff1a; ssh-keygen -t rsa -C "<注释内容>"基于ED255…

Day14:信息打点-主机架构蜜罐识别WAF识别端口扫描协议识别服务安全

目录 Web服务器&应用服务器差异性 WAF防火墙&安全防护&识别技术 蜜罐平台&安全防护&识别技术 思维导图 章节知识点 Web&#xff1a;语言/CMS/中间件/数据库/系统/WAF等 系统&#xff1a;操作系统/端口服务/网络环境/防火墙等 应用&#xff1a;APP对象/…

【多模态融合】CRN 多视角相机与Radar融合 实现3D检测、目标跟踪、BEV分割 ICCV2023

前言 本文介绍使用雷达与多视角相机融合&#xff0c;实现3D目标检测、3D目标跟踪、道路环境BEV分割&#xff0c;它是来自ICCV2023的。 会讲解论文整体思路、输入数据分析、模型框架、设计理念、损失函数等。 论文地址&#xff1a;CRN: Camera Radar Net for Accurate, Robus…

如何使用 CSS object-fit 进行图片的缩放和裁剪

简介 在处理图片时&#xff0c;你可能会遇到需要保持原始宽高比的情况。保持宽高比可以防止图片被拉伸或压缩而出现失真。解决这个问题的常见方法是使用 background-image CSS 属性。更现代的方法是使用 object-fit CSS 属性。 在本文中&#xff0c;你将探索 object-fit CSS …

如何在有/没有备份的情况下恢复华为上已删除的视频?6 个推荐选项

“我不小心删除了华为手机上的一堆视频。我怎样才能把它们找回来&#xff1f;我在谷歌上也找不到它们”。——来自知乎 在我们日常生活的喧嚣中&#xff0c;意外时有发生。无论是由于华为手机上的无意删除、恢复出厂设置、病毒感染、数据损坏还是系统故障&#xff0c;这些视频…

GEE数据——GEDI04_A_和GEDI02_A_002_MONTHLY出现的数据问题

简介 产品介绍 该数据集包含全球生态系统动力学调查&#xff08;GEDI&#xff09;第 4A 级&#xff08;L4A&#xff09;第 2 版对地上生物量密度&#xff08;AGBD&#xff0c;单位为兆克/公顷&#xff09;的预测&#xff0c;以及对每个采样地理定位激光足迹内预测标准误差的估…

python+django高校澡堂洗浴浴室预约签到管理系统8d8c

本系统在设计过程中&#xff0c;高校洗浴管理系统的出现就有很大的需求。该系统可以很好地解决这些麻烦和问题。 很好地发挥了该开发方式的优势&#xff0c;让实现代码有了良好的可读性&#xff0c;而且使代码的更新和维护更加的方便&#xff0c;操作简单&#xff0c;对以后的维…

前端将html导出pdf文件解决分页问题

这是借鉴了qq_251025116大佬的解决方案并优化升级完成的&#xff0c;原文链接 1.安装依赖 npm install jspdf html2canvas2.使用方法 import htmlToPdffrom ./index.jsconst suc () > {message.success(success);};//记得在需要打印的div上面添加 idlet dom document.que…

文心一言 VS 讯飞星火 VS chatgpt (209)-- 算法导论15.4 6题

六、设计一个 O(nlgn) 时间的算法&#xff0c;求一个 n 个数的序列的最长单调递增子序列。&#xff08;提示&#xff1a;注意到&#xff0c;一个长度为 i 的候选子序列的尾元素至少不比一个长度为 i-1 候选子序列的尾元素小。因此&#xff0c;可以在输入序列中将候选子序列链接…

如何在Linux上为PyCharm创建和配置Desktop Entry

在Linux操作系统中&#xff0c;.desktop 文件是一种桌面条目文件&#xff0c;用于在图形用户界面中添加程序快捷方式。本文将指导您如何为PyCharm IDE创建和配置一个 .desktop 文件&#xff0c;从而能够通过应用程序菜单或桌面图标快速启动PyCharm。 步骤 1: 确定PyCharm安装路…

Nodejs 第五十二章(定时任务)

什么是定时任务&#xff1f; 定时任务是指在预定的时间点或时间间隔内执行的任务或操作。它们是自动化执行特定逻辑的一种方式&#xff0c;可用于执行重复性的、周期性的或计划性的任务。 定时任务通常用于以下情况&#xff1a; 执行后台任务&#xff1a;定时任务可用于自动…

Nodejs 第五十一章(限流阀)

限流功能 目前我们学习了redis,lua,nodejs&#xff0c;于是可以结合起来做一个限流功能&#xff0c;好比一个抽奖功能&#xff0c;你点击次数过多&#xff0c;就会提示请稍后重试&#xff0c;进行限制&#xff0c;我们来实现一下该功能。 安装依赖 npm i ioredis express代码…

『操作系统OS笔记』MAC(m1芯片)电脑安装FFmpeg

MAC(m1芯片)电脑安装FFmpeg mac电脑安装ffmpeg两种方法 文章目录 1. brew安装FFmpeg2. 官网下载FFmpeg压缩包3. 使用FFmpeg将音频和视频合并 1. brew安装FFmpeg brew install ffmpeg # 需要等比较久的时间&#xff0c;安装很多东西&#xff0c;安装过程中如果遇到报错对应解决…

第十一篇 - 应用于市场营销视频场景中的人工智能和机器学习技术 – Video --- 我为什么要翻译介绍美国人工智能科技巨头IAB公司?

IAB平台&#xff0c;使命和功能 IAB成立于1996年&#xff0c;总部位于纽约市。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司&#xff0c;互动广告局&#xff08;IAB- the Interactive Advertising Bureau&#xff09;自1996年成立以来&#xff0c;先后为700多家媒体…

MATLAB环境下基于图像处理的计算病理学图像分割(MATLAB R2021B)

人工智能是病理学诊断和研究的重要新兴方法&#xff0c;其不仅可用于病理形态数据分析&#xff0c;还可整合免疫组化、分子检测数据和临床信息&#xff0c;得出综合的病理诊断报告&#xff0c;为患者提供预后信息和精准的药物治疗指导。计算病理学是病理学与AI、计算机视觉等信…

机器人编程学习有哪些好处?

机器人编程学习有许多好处&#xff0c;无论是对个人还是对社会都具有重要意义。以下是机器人编程学习的一些好处&#xff1a; 1. **培养计算思维&#xff1a;** 通过机器人编程学习&#xff0c;可以培养逻辑思维、问题解决能力和创新思维。编程过程中需要分析问题、设计算法、…

旧物回收小程序开发:环保与科技的创新结合

随着科技的飞速发展&#xff0c;我们的日常生活越来越离不开手机应用程序。而在环保日益成为社会焦点的今天&#xff0c;如何将科技与环保相结合&#xff0c;成为了一个值得深思的问题。今天&#xff0c;我们将探讨旧物回收小程序的开发&#xff0c;它如何助力环保&#xff0c;…

dolphinscheduler海豚调度(五)seatunnel案例

seatunnel作为新一代流行的数据集成工具&#xff0c;其功能非常强大且简单易用&#xff0c;今天演示一下如何通过dolphinscheduler创建并运行seatunnel任务 本次dolphinscheduler和seatunnel均部署在同一机器上的单机版本 1、环境配置 打开dolphinscheduler安装目录&#xf…

Appium系列(1)安装启动Appium

Appium环境准备 Mac电脑jdk环境AndroidSDK环境node>8.1.0&#xff08;最好用最新版本&#xff09; 安装命令 npm i -g appium安装不成功请检查node 版本是否正确 安装成功命令行输入appium回车查看 安装驱动程序 1、先检查当前驱动情况 通过 appium driver list 进行…

How to use Grammarly with Overleaf

遵循以下步骤使用Grammarly和Overleaf: 安装Grammarly提供的浏览器扩展。更多信息请参考Grammarly的支持页面。 安装首选的浏览器扩展后&#xff0c;你需要注册一个Grammarly帐户。 下面是Chrome的Grammarly扩展钉到Chrome工具栏的截图: 1、 2、 3、