关于JVM

news2024/12/24 3:08:38

作者:~小明学编程 

文章专栏:JavaEE

格言:热爱编程的,终将被编程所厚爱。
在这里插入图片描述

目录

内存区域的划分

程序计数器(线程私有)

Java虚拟机栈  (线程私有)

本地方法栈

堆(线程共享)

方法区(线程共享)

类加载的过程

Loading

Linking

验证

准备

解析

Initializing

双亲委派模型

垃圾回收机制

如何判断垃圾

基于引用计数

基于可达性分析

回收垃圾

标记-清除

复制算法

 标记整理

分代回收

垃圾回收的实现


内存区域的划分

我们JVM的内存区域主要分为4个区域:

  1. 程序计数区
  2. 栈区
  3. 堆区
  4. 方法去

程序计数器(线程私有)

程序计器所占的区域是内存中最小的一块其主要的作用就是保存我们我们的下一条指令所在的位置,因为操作系统是以线程为单位调度执行的,每个线程都得记录自己的执行位置,所以每个线程都会有一个程序计数器,指令就是字节码(就编译产生的字节码文件【后缀 .class】),程序要想运行,JVM 就得把 字节码文件 加载起来,放到内存中。然后程序就会把一条条指令,从内存中取出来,放到 CPU 上执行。

Java虚拟机栈  (线程私有)

我们的栈区放的是我们的局部变量和方法调用的信息。

我们在调用一个方法的时候将会创建栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息相当于入栈,当我们执行完该方法之后就会执行出栈操作。

另外我们的栈区域也比较小一般情况下只有几兆或者几十兆,当我们在使用递归的时候如果递归的程度比较深的话或者死循环这样的情况就会导致我们的栈溢出。

本地方法栈

本地方法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使用的,而本地方法栈是给本地方法使用的。

堆(线程共享)

程序中创建的所有对象都在保存在堆中,每一个进程都会分配一个堆区,我们一个进程中的多个线程也就共享一个堆区。

值得注意的是当我们在一个方法中new了一个对象那么该对象是放在堆中的但是该对象的引用是局部变量所以就放在栈区之中。

方法区(线程共享)

方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。

我们的.java代码会被解析成.class的二进制字节码,然后.class就会被jvm构成类对象,这个类对象就被放在了方法区,对象就描述了 这个类 “长什么样”,描述类名字是啥,里面有哪些成员,有哪些方法,每个成员的名字和类型等等,同时方法区里面还放着 static 修饰的成员(类属性)。

类加载的过程

类加载主要就是把 .class 文件加载到内存中,构建成类对象。主要有三个部分:loading、linking、initializing。

Loading

Loading阶段我们主要完成三件事:

  1. 先找到对应的 .class 文件,然后打开并通过二进制的字节流读取 .class 文件,同时初步生成一个 类对象。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构:我们看到第一行u4 magic就代表着我们首先会读取前4个字节,其中magic代表着我们文件的格式,然后一一的读取出来然后就按照这个格式将读取到的数据填写到我们初步生成的类对象中。
  3. 内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

Linking

在linking阶段主要分成三部,分别是验证,准备和解析。

验证

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节 流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。

准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

解析

解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。

Initializing

初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程。

双亲委派模型

双亲委派模型是 类加载 中的一个环节,描述的是 JVM 中的 类加载器,如何根据类的全限定名(如java.lang.String)找到 .class 文件的过程。

我们的类加载器主要分为下面三个部分:

  1. BootStrapClassLoader:负责加载标准库中的类(String,ArrayList,Random,Scanner)。
  2. ExtensionClassLoader:负责加载 JDK 扩展的类(现在很少用)。
  3. ApplicationClassLoader:负责加载当前项目目录中的类。

查找标准库中的类:

1.当我们查找标准库中的类的时候我们首先会区看看我们的父类也就是ExtensionClassLoader类加载器有没有加载当前的要查找的类

2.如果没有那就去父类查找到了父类我们要看看我们当前类的父类有没有加载所在查找的类。

3.如果没有再去父类BootStrapClassLoader去查到,然后同样再去父类查找此时没有父类就查到当前自己的类。 

查找自己的类:

1.当我们查找标准库中的类的时候我们首先会区看看我们的父类也就是ExtensionClassLoader类加载器有没有加载当前的要查找的类

2.如果没有那就去父类查找到了父类我们要看看我们当前类的父类有没有加载所在查找的类。

3.如果没有再去父类BootStrapClassLoader去查到,然后同样再去父类查找此时没有父类就查到当前自己的类。 

4.如果当前类查不到就查到子类也就是ExtensionClassLoader所在的jvm扩展类。

5.如果ExtensionClassLoader所在类查不到就去ApplicationClassLoader中去查找。

这种设计使得一旦我们设计的类与标准库中的类重复了我们还是能找到标准库中的类。

垃圾回收机制

我们在写代码的时候经常的会要申请空间,就像我们上面介绍的那几种内存布局我们针对不同的情况将会申请各自的空间,但是我们一味的申请空间如果不释放的话我们的空间总有被占用完毕的时候,这就是我们的内存泄漏。

对于我们上述的四个内存区域其中Java虚拟机栈和程序计数器和本地方法栈是不需要考虑内存回收问题的,并且这三个区域的内存分配与回收具有确定性,因为当方法结束或者线程结束时,内存就自然跟着线程回收了。因此我们主要关注的为Java堆与方法区这两个区域。

如何判断垃圾

基于引用计数

所谓的引用计数就是给我们的对象额外的增加一块区域用于计算这个对象被引用的次数每有一个引用引用该对象这个计数器就会加1,当有一个引用指向null,这个计数器就减1,如果计数器等于0了,那就释放该对象。

缺点:

1.空间利用率低:当我们对象本身就很小的时候此时加上一个计数器就会显得很浪费空间。

2.循环问题:

class Test {
    Test t = null;
}

Test t1 = new Test();
Test t2 = new Test();


 

t1.t = t2;
t2.t = t1;

这个时候我们将会发现问题如果我们对t1和t2进行null,那么将会出现下面的情况。

 此时两个对象的计数器都是1了,但是这个1不可能变成0了,因为此时指向这两个对象的引用都在这两个对象里面。

基于可达性分析

通过额外的线程,定期的针对整个内存空间的对象进行扫描。有一些起始位置(GCRoots),会类似于 深度优先遍历一样,把可以访问到的对象进行标记,能标记的就是可达对象,如果没标记就是不可达,就是垃圾。

例如下图:我们通过root节点可以访问到所有的节点,这个时候就不存在垃圾。 

但是对于下面这种情况root的右节点被切断了,此时c和f都访问不到了,那么他们就都是垃圾。

GCRoots:
a)栈上的局部变量
b)常量池中的引用指向的对象
c)方法区中的家庭成员指向的对象

回收垃圾

回收垃圾主要有三种方法。

标记-清除

标记就是可达性标记,清除就是直接释放内存,释放之后的内存可能是不连续的,就是内存碎片,所以当内存碎片多了就会影响我们开辟大一点的内存,明明内存是够的但是因为内存碎片的存在就导致了开辟不了那么大的内存。

复制算法

为了解决内存碎片,引入的复制算法。就是把申请的内存一分为二,然后不是垃圾的,拷贝到内存的另一边,然后把原来的一半内存空间整体都释放。

接着我们将需要的内存空间给拷贝到另外一边。

 

 此时就解决了内存碎片的问题,但是又引入了一个问题,那就是

  • 内存空间利用率低
  • 当保留的对象多,要释放的对象少,此时复制开销就很大。

 标记整理

针对复制算法,再做出改进。类似于顺序表删除中间元素,有一个搬运操作。

 将未标记的元素都给拷贝到前面

 

但是这种方法依然不能解决复制搬运的开销问题,复制和搬运的开销依然很大。

分代回收

当前 JVM 垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。

1.对象刚创建出来的时候,就放在伊甸区。
2.如果伊甸区的对象熬过一轮GC扫描,就会被拷贝到 幸存区(应用了复制算法)。
3.在后续的几轮 GC 中,幸存区的对象在两个幸存区之间来回拷贝(复制算法),每一轮都会淘汰掉一波幸存者。
4.在持续若干次之后,对象就进入老年代,一个对象越老,继续存活的可能性就越大,所以老年代的扫描频率大大低于新生代,老年代中使用标记整理的方式进行回收。

分代回收中,还有一个特殊情况: 有一类对象可以直接进入老年代(大对象,占有内存多的对象),大对象拷贝开销比较大,不适合使用复制算法,所以直接进入老年代。

1. Minor GC又称为新生代GC : 指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。
2. Full GC 又称为 老年代GC或者Major GC : 指发生在老年代的垃圾收集。出现了Major GC,经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

垃圾回收的实现

常用的垃圾回收器如下:

  • Serial 收集器(新生代)/ Serial Old 收集器(老年代):这俩都是串行收集,在进行垃圾扫描和释放的时候,业务线程要停止工作。就是这边停止工作,他先扫描完,再去进行释放,然后业务线再继续工作。这种方式扫描的慢,释放的也慢,也会产生严重的 STW 问题。
  • ParNew 收集器(新生代)/Parallel Scavenge 收集器(新生代,是并行清除)/ Parallel Old 收集器(老年代):这些回收器都是并发收集的,引入了多线程,Parallel Scavenge 比 ParNew 多出了一些参数,可以用来控制 STW 的时间。

下面是新的垃圾回收器,核心思想就是:化整为零,:

GMS 收集器:设计的比较巧妙,设计初衷是为了尽可能的让 STW 时间短。
a)初始标记,速度很快,会引起短暂的 STW(只是找到 GCRoots)
b)并发标记,虽然速度很慢,但是可以和业务线程并发执行,不会产生 STW
c)重新标记,在 b 的业务代码可能会影响并发标记的结果,针对 b 的结果进行微调,虽然会引起 STW,但是只是微调,速度快。
d)回收内存,也是和业务线程并发的,所以就没有 STW。
G1 收集器:是唯一一款全区域的垃圾回收器。
a)把整个内存,分成了很多小的区域。
b)给这些区域进行了不同的标记。
c)有的区域放新生代对象,有的放老年代对象。
d)然后再扫描的时候,一次扫描若干个区域(不追求一轮 GC 就扫描完,分舵从来扫)对于业务代码影响是更小的。
e)在当下可以优化带让 STW 停顿时间小于 1ms。

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

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

相关文章

Leetcode:77. 组合、216. 组合总和 III(C++)

目录​​​​​​​ 77. 组合: 问题描述: 实现代码与解析: 递归(回溯): 原理思路: 剪枝优化版: 原理思路: 216. 组合总和 III: 问题描述&#xff1a…

[C/C++]指针,指针数组,数组指针,函数指针

文章目录指针内存空间的访问方式指针变量的声明指针的赋值指针运算用指针处理数组元素指针数组用指针作为函数的参数指针型函数指向函数的指针指针 指针是C从C中继承过来的重要数据类型。通过指针技术可以描述各种复杂的数据结构,可以更加灵活的处理字符串&#xf…

Linux下dmi信息分析工具dmidecode原理

dmidecode命令主要是通过DMI获取主机的硬件信息,其输出的信息包括BIOS、系统、主板、处理器、内存、缓存等等。它是通过SMBIOS(System Management BIOS)来获取信息的。SMBIOS是主板或系统制造者以标准格式显示产品管理信息所需遵循的统一规范。 什么是DM…

QA特辑 | 以万变钳制黑灰产之变的验证码产品设计逻辑的答案,都在这里

1月12 日下午,就验证码的攻防对抗问题,顶象反欺诈专家大卫从验证码的破解手段讲起,从防御角度深度剖析如何应对黑灰产的攻击以及验证码在产品能力设计层面应该考虑哪些问题。 直播也吸引了众多关注验证码的观众前来围观,针对验证…

vue3性能优化

文章目录1. Lighthouse1.1 性能参数2. rollup-plugin-visualizer(打包代码块分析)3. vite配置优化4. PWA离线缓存技术5. 其他优化1. Lighthouse 谷歌浏览器自带的 DevTools 也可以全局安装Lighthouse # 安装 yarn global add lighthouse# 使用 lighth…

Android app集成微信支付

Android app集成微信支付 鉴于微信支付的文档入口不太容易找到、以及文档中有些逻辑不通或者容易产生歧义或者缺失一些信息的情况,记录下此次接入的流程和需要关注的一些点。 使用的是app支付-> APP支付产品介绍 首先阅读介绍等,了解一些基础的概念…

c++数据结构-图(详解附算法代码,一看就懂)

图(Graph)是一种复杂的非线性结构,它可以描述数据间的关系,被广泛使用。图 G 由两个集合 V 和 E 组成,记为 。V是顶点的有穷非空集合,E是边的集合。通常,也将 G 的顶点集和边集表示为 V(G) 和 E…

尚医通-登录注册搭建-JWT(二十八)

目录: (1)前台用户系统-登录注册-需求分析 (2)前台用户系统-登录注册-搭建环境 (3)前台用户系统-手机登录-基本实现 (4)前台用户系统-手机登录-整合JWT (…

【JUC并发编程】使用多线程可能带来什么问题

【JUC并发编程】使用多线程可能带来什么问题? 文章目录【JUC并发编程】使用多线程可能带来什么问题?什么是多线程并发为什么会出现线程带来的安全性问题可见性问题原子性问题有序性问题活跃性问题性能问题引起线程切换的几种方式什么是多线程 多线程意味着你能够在同一个应用…

Linux的ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM及分配页释放页函数的简单介绍

Linux的ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM及分配页释放页函数的简单介绍简单介绍一下页:Linux 区:分配页系统调用释放页系统调用简单介绍一下页: 内核把物理页作为内存管理的基本单位。 尽管处理器的最小可寻址单位通常为字(甚至…

ZooKeeper-分布式锁实现

4.11)Zookeeper分布式锁-概念 •在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者Lock的方式来解决多线程间的代码同步问题,这时多线程的运行都是在同一个JVM之下,没有任何问题。 •但当我们的应用是分…

【JavaScript】实现简易购物车

💻【JavaScript】实现简易购物车 🏠专栏:有趣实用案例 👀个人主页:繁星学编程🍁 🧑个人简介:一个不断提高自我的平凡人🚀 🔊分享方向:目前主攻前端…

客快物流大数据项目(一百零四):为什么选择Elastic Search作为存储服务

文章目录 为什么选择Elastic Search作为存储服务 一、​​​​​​​​​​​​​​ElasticSearch简介

【GD32F427开发板试用】懒人新手试用

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:东东_dxGGN2 我收到的开发板是GD32F427R-START,MCU是GD32F427RKT6,如下图(座机拍的见谅) 测试流…

【C++】从0到1入门C++编程学习笔记 - 核心编程篇:内存分区模型

文章目录一、程序运行前二、程序运行后三、new 操作符C程序在执行时,将内存大方向划分为4个区域 代码区:存放函数体的二进制代码,由操作系统进行管理的全局区:存放全局变量和静态变量以及常量栈区:由编译器自动分配释…

2022年回顾 | 被磨砺,被厚待

岁末年首, 最宜盘点过往的时光。 回顾2022团结一心,攻坚克难, 祝福2023大展宏图,鹏程万里。 2022我们遇到了"卷土重来"、 “挥之不去”, 也等到了"再也不见"和 “永远下线”。 2022是一个&…

HTML中的table标签与a标签

这里写自定义目录标题一、table标签1、什么是table标签2、table标签中长见到的标签3、例子代码及其结果二、a标签1、什么是a标签2、a标签中常见的属性3、例子代码及其结果一、table标签 1、什么是table标签 table标签表示整体的一个表格 2、table标签中长见到的标签 <tr…

基于Spring Boot和Spring Cloud实现微服务架构

首先&#xff0c;最想说的是&#xff0c;当你要学习一套最新的技术时&#xff0c;官网的英文文档是学习的最佳渠道。因为网上流传的多数资料是官网翻译而来&#xff0c;很多描述的重点也都偏向于作者自身碰到的问题&#xff0c;这样就很容易让你理解和操作出现偏差&#xff0c;…

采用特殊硬件指令对密码学算法加速

1. 引言 Armando Faz-Hermandez等人2018年论文《SoK: A Performance Evaluation of Cryptographic Instruction Sets on Modern Architectures》&#xff0c;开源代码见&#xff1a; https://github.com/armfazh/flo-shani-aesni&#xff08;C语言&#xff09; slide见&…

Java高手速成 | 多态性实战

多态性&#xff08;polymorphism&#xff09;是OOP最强大、最有用的特性。截至目前&#xff0c;多态性用到了所讲的所有其他OOP概念和特性。在通向精通Java语言编程的征程上&#xff0c;多态性是最高级别概念站点。 一个对象具有跟另一不同类的对象一样的行为&#xff0c;或者具…