【JVM】JVM相关机制

1. JVM内存区域划分

1.1 内存区域划分简介

内存区域划分:实际上JVM也是一个进程,进程运行时需要向操作系统申请一些系统资源(内存就是典型的资源),这些内存空间就支撑着后续Java程序的运行,而这些内存又会根据不同的应用场景被划分为不同的空间,这就叫做"内存区域划分",例如下图所示:

1.2 不同区域的应用场景

JVM内存区域大致分为四个部分:堆、栈、程序计数器、元数据区

  1. :代码中使用new关键字创建的对象(不是引用)都会存放在堆上,而且对象中持有的非静态成员变量也存放在堆上
  2. :栈又被分为"本地方法栈"和"虚拟机栈",主要包含代码中的局部变量以及方法的调用关系,需要注意本地方法栈是指JVM内部通过C/C++等语言编写的代码中的调用关系和局部变量(一般来说谈到栈,指代的是虚拟机栈)
  3. 程序计数器:这是相对来说区域较小的空间,专用用来存放下一条要执行的Java代码的地址,与x86CPU中的eip等寄存器类似
  4. 元数据区:在1.8之前也被称为"方法区","元数据"是计算机中一个常用术语,通常是起辅助性质、描述性质的属性,主要包含类的信息、方法的信息(一个程序有哪些类、一个类有哪些方法、每个方法中包含哪些指令)

如果面试官提问:“了解Java中的堆和栈吗?”,这个时候一定要先反问面试官:“您说的是数据结构中的堆和栈还是JVM内存区域中的堆和栈”

特点小结:

  • 其中栈和程序计数器可能在程序中持有多份(每个线程占有一份),而堆和元数据区所有线程共享一份

1.3 经典笔试题

给出如下代码:

class Test {
    private int n;
    private static int m; 
}

public class Main {
    public static void main(String[] args) {
        Test t = new Test();
    }
}

问:上述代码中,变量n、m、t各自处于JVM内存中的哪个区域?
答:其中n在堆上,m在元数据区中,t在栈上

  • 变量n是对象中持有的非静态成员变量,因此在"堆"上
  • 变量m是对象中持有的静态成员变量,又被称为"类属性",因此存放在"元数据区"
  • 变量t是方法中的局部变量,是一个引用类型(不是对象本身),因此存放在"栈"上

区分一个变量究竟处于哪一块内存区域归根到底是看变量的类型,究竟是局部变量还是静态成员变量还是实例成员变量!

2. 类加载机制

2.1 类加载的过程

类加载:指的是Java进程运行的时候,需要把.class文件从硬盘上,读取到内存中,并进行一系列校验解析的过程
类加载的过程大体上可以分为如下五个步骤(网上有些说法是三个步骤,这个情况及时把3、4、5步骤整合到一起了)

  1. 加载:把硬盘上的.class文件找到(涉及双亲委派模型,后续进行介绍),打开文件,读取文件内容(二进制文件)
  2. 验证:当前需要确保读取到的二进制文件内容,是合法的.class文件(字节码文件)格式

具体的验证依据,在Java的虚拟机规范中有明确说明

image.png如上图所示,我们可以简单分析一下其中部分字段含义:

  • magic:也被称为"magic number"(魔幻数字),广泛应用于各种二进制格式文件中,用来标识当前的二进制文件是哪种类型
  • u4、u2:表示当前字段是一个由多少字节构成的无符号整数,例如u4就代表4字节的无符号整数,u2代表2字节的无符号整数
  • major_version、minor_version:主版本和次版本,平常所说的Java8、11、17都是日常中使用的版本,但是JVM内部还有另一套版本体系,此处用来验证版本是否符合要求
  1. 准备:给类对象申请内存空间,此时申请到的内存空间,里面的默认值为全0(这个阶段类对象中的静态成员变量的值也是0了)

  2. 解析:主要是针对类中的字符串常量进行处理,是Java将常量池内部符号引用替换为直接引用的过程,也就是初始化常量的过程,此处.class文件中填充给s的内容可以看做是"符号引用"

    比如说类中有一个字符串常量String s = “hello”,那么这个字符串是否需要存储在.class文件中呢?是一定需要进行存储的,但是文件没有"地址"这样的概念,于是可以存储一个类似于"地址偏移量"的概念,当.class文件加载到内存中时,就具有了"地址",于是s中的内容就可以替换为真实的地址了

  3. 初始化:针对类对象完成后续的初始化(例如执行静态代码块的逻辑、可能还会触发父类的加载)

2.2 双亲委派机制

双亲委派机制:首先需要明确,“双亲委派"机制是在上述五步骤中 加载 环节生效的!它描述了如何在硬盘上查找.class文件的策略
类加载器:在JVM中用于执行类加载的过程中,有一个专门的模块就叫做"类加载器”(ClassLoader),通过给定全限定类名,如java.lang.String,就可以找到对应的.class文件,JVM的默认类加载器有三个(可以自定义),分别称为"BootstrapClassLoader"、“ExtensionClassLoader”、“ApplicationClassLoader”

  • BootstrapClassLoader:负责查找标准库当中的目录
  • ExtensionClassLoader:负责查找扩展库当中的目录
  • ApplicationClassLoader:负责查找当前项目的目录以及第三方库的目录

上述三个类加载器,存在着"父子关系"(不是继承中的父子关系,而是类似于"二叉树",孩子节点中有一个parent引用指向自己的父亲节点),下面我们就来介绍查找项目目录org.example.MyClass为例介绍"双亲委派机制"的工作流程

  1. ApplicationClassLoader作为入口出发,开始工作
  2. ApplicationClassLoader不会立刻搜索自己负责的目录,而是会将搜索任务交给父加载器
  3. ExtensionClassLoader也不会立刻搜索自己负责的目录,而是会将搜索任务交给父加载器
  4. BoostrapClassLoader发现自己没有父加载器,于是搜索自己的负责目录(标准库目录),没找到则返回给自己的子类加载器
  5. ExtensionClassLoader搜索自己的负责目录(扩展库目录),没找到则继续返回给自己的子加载器
  6. ApplicationClassLoader搜索自己的负责目录(项目目录以及第三方库),此时找到了,就打开文件进行读取,如果此时还未找到,就抛出ClassNotFoundException异常信息,表明类加载失败

该种设定方式,可以有效避免因自己命名的类路径与标准库类冲突而导致标准库功能失效!

注意:上述JVM的"双亲委派机制"只是JVM提供的默认类加载器的默认遵守标准,如果实现一个自定义的类加载器,完全可以打破此规则,此时就不适用"双亲委派机制"了

3. GC垃圾回收机制

3.1 GC背景介绍

垃圾回收机制的引入:相信大家都学过C/C++这样的语言,其中malloc/free这样的函数就是用来进行动态内存管理的函数,此处申请到的内存的生命周期伴随整个进程,这对于服务器程序而言是相当不友好的!试想一下如果一个请求就要通过malloc申请一块内存,但是不使用free函数释放,那么终究会导致服务器内存不够用,这就是典型的 “内存泄漏” 问题

事实上很容易出现忘记手动调用free函数的情况,因为一段程序中一旦业务逻辑十分复杂,执行过程中很容易出现异常、return语句提前结束,就存在安全隐患!

因此大佬们就在探究,是否可能做到让程序来执行自动回收内存这样的机制呢?Java就属于早期便支持垃圾回收机制这样的语言,程序会自动判定某个内存是否还会被使用,如果不使用就自动释放

背景介绍:C/C++这样的语言为什么不补充垃圾回收机制呢?C语言比较"摆烂",自然没有引入新技术的动力,而C++标准委员会的大佬们认为GC这种机制会影响程序的执行效率以及引入额外的系统开销,这与C++追求性能到极致的初衷违背,例如说:引入GC会导致有些程序的正常业务逻辑暂停执行,业界称为"STW(Stop The World)"问题

3.2 GC工作流程

首先我们需要明确,内存区域中哪些部分需要进行GC:

  • 程序计数器:不需要进行GC
  • 栈:不需要GC,因为栈中的局部变量出了作用域就会被回收,这是栈的特性,与GC无关
  • 元数据区:不需要GC,因为通常只有"类加载"的过程,没有"类卸载"的说法
  • 堆:是GC的主战场

这里的GC,我们通常指的是回收 对象 来达到释放内存的目的,我们需要明确以下两个问题:1、如何识别出对象是"垃圾";2、被标记为垃圾的对象如何进行回收

3.2.1 如何识别"垃圾"

由于在Java中我们都是通过 引用 的方式来使用对象的,所以我们只需要判断当前对象是否存在引用指向,就可以判断出这个对象是否是"垃圾",我们常用的判断方法有以下两种:1、引用计数;2、可达性分析

3.2.1.1 引用计数

引用计数:通过给每个对象分配额外的内存空间,用来保存当前对象存在多少个引用指向
例如有如下程序:

class Test {}

public class Main {
    public static void main(String[] args) {
        Test t1 = new Test();
        Test t2 = t1;
    }
}

此时内存空间可以简单表示为下图:

由于垃圾回收机制中专门的扫描线程,会获取到每个对象的引用计数情况,当发现引用计数为0时,就标记为"垃圾",有此时堆上的对象具有两个引用指向,即t1和t2,因此此时引用计数器的值为2,然后将上述main方法的代码修改一下:

public class Main {
    public static void main(String[] args) {
        Test t1 = new Test();
        Test t2 = t1;
        t1 = null; // 新增代码
        t2 = null; // 新增代码
    }
}

此时引用计数情况就会置为0!

此时发现该对象的引用计数为0,就可以将对象标记为垃圾,然后后续进行释放了!但是引用计数机制存在以下致命问题:

  1. 占用额外的存储空间:倘若每一个对象都是用2字节的内存空间来表示当前的引用计数值,那么如果堆空间中的对象很多时,总的消耗空间也会非常大
  2. 存在 循环引用 问题:我们下面专门分析循环引用的场景

循环引用示例
倘若具有如下代码:

class Test {
    private Test t;
}

public class Main {
    public static void main(String[] args) {
        Test a = new Test();
        Test b = new Test();
        a.t = b;
        b.t = a;
        a = null;
        b = null;
    }
}

此时我们执行a = null; b = null;代码之前的内存区域示意图:

此时堆空间地址为0x11的对象引用计数值为2,因为存在引用a以及堆空间地址为0x12的成员变量t,此时我们执行代码a = null; b = null;此时内存示意图如下图所示:

此时堆空间中地址为0x11的对象内部引用计数仍为1,因为有地址为0x12的对象中的成员变量t引用,但是此时我们已经无法通过引用来使用对象了,因为此时堆中的对象都需要使用对方来进行访问!形成了类似"死锁"的局面

  • 同时,也因为上述的问题,JVM中并没有使用"引用计数"的方案,而是采用下面"可达性分析"的方式,但是诸如PHP、Python等就是用这样的方式来处理的(如何解决我就不得而知了~)
3.2.2.2 可达性分析

可达性分析:在程序中会存在各种各样的变量,如栈中的局部变量、对象中的非静态成员变量、static修饰的静态变量、常量池中的变量,以这些变量作为根节点出发,沿着这些变量找到其中持有的引用类型的成员,逐步遍历继续访问,所有能够被访问到的对象就不是"垃圾"了,此外的不能被访问到的变量就会被回收,算法类似于图的遍历
可达性分析代码示例

class Node {
    public int val;
    public Node left;
    public Node right;
}

public class Main {
    public static void main(String[] args) {
        Node root = buildTree();
    }

    public static Node buildTree() {
        Node a = new Node();
        Node b = new Node();
        Node c = new Node();
        Node d = new Node();
        Node e = new Node();
        Node f = new Node();
        Node g = new Node();
        a.left = b;
        a.right = c;
        b.left = d;
        b.right = e;
        e.left = g;
        c.right = f;
        return a;
    }
}

其中数据结构可以表达如下图所示:

其中我们虽然栈中只有一个引用root,但是从root节点出发我们就可以遍历到a-g中所有的节点,但是如果其中执行代码root.right = null;那么此时节点c、e就无法被遍历到,此时对象c和e就会被标记为"垃圾"

3.2.2 "垃圾"如何回收

下面我们要探究的就是如何将已经被标记为"垃圾"的对象进行回收释放内存了:主要的释放策略有三种:1、标记-清除算法;2、标记-复制算法;3、标记整理算法

3.2.2.1 标记-清除算法

这是最朴素的方式,例如在堆空间中有如下需要释放的对象:

标记-释放算法就是将已经被标记为"垃圾"的空间直接释放,但是此时可能存在 内存碎片 问题:
上述的释放方式会导致有很多离散的、小的空闲内存空间,就很有可能导致后续申请连续内存失败!例如我们需要申请1M内存空间,剩余内存空间远远大于1M,但是并没有连续的1M内存空间可以提供,此时申请就会失败!

3.2.2.2 标记-复制算法

标记-复制 算法示意图如下:

"标记-复制"算法的核心就是采用"半区"复制的方式,不直接释放内存,而是将不是"垃圾"的对象,拷贝到另一半区,然后整体释放左半区内存空间,其虽然能够解决"内存碎片"问题,但是也有如下弊端:

  • 总的可用内存变少了
  • 当需要释放的对象远远少于剩余空闲内存时,此时需要复制的对象很多,复制开销就会很大!
3.2.2.3 标记-整理算法

"标记-整理"算法示意图如下:

类似于顺序表中的删除中间元素的搬运过程,然后也能解决"内存碎片"问题,而且不像"标记-复制"算法那样浪费大量内存空间,但是其中"搬运"的开销也是很大的!因此JVM并没有采取上述三种方法,而是采用"取长补短"这样的综合性方案

3.2.3 JVM分代回收算法

分代回收:JVM当中引入了一个概念:“对象年龄”,由于JVM中有专门的扫描线程周期性扫描/释放,如果一个对象被扫描了一次,仍然可达(不是垃圾),就将年龄+1(初始值可以看做0),JVM会根据对象的年龄的大小划分为不同的区域

新生代:对象年龄较小的区域,内部含有Eden区(伊甸区),Survivor区(幸存区)
老年代:对象年龄较大的区域

  1. 首先使用new关键字刚刚创建的对象都会被存放在Eden伊甸区,一个经验规律是伊甸区中的很多对象都是活不过第一轮GC扫描的
  2. 第一轮GC扫描完毕,少数伊甸区中存活的对象就会通过"标记-复制"算法拷贝到"幸存区",后续GC扫描线程不仅需要扫描伊甸区,也要扫描幸存区,而幸存区大部分对象也会被扫描而标记为垃圾,如果存活,就继续使用"标记-复制"算法拷贝到另一个幸存区当中(注意这里的幸存区是两块大小相同的内存空间),每次经历一轮GC存活就将年龄+1
  3. 如果有对象在幸存区当中存活了多轮GC,JVM就会认为这个对象生命周期很长,就会使用"标记-复制"算法将其拷贝到老年代
  4. 老年代的对象当然也会被GC扫描,但是扫描频次就大大降低了
  5. 对象在老年代如果标记为垃圾,JVM就会使用"标记-整理"算法释放内存

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

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

相关文章

【go语言开发】swagger安装和使用

本文主要介绍go-swagger的安装和使用,首先介绍如何安装swagger,测试是否成功;然后列出常用的注释和给出使用例子;最后生成接口文档,并在浏览器上测试 文章目录 安装注释说明常用注释参考例子 文档生成格式化文档生成do…

T3SF:一款功能全面的桌面端技术练习模拟框架

关于T3SF T3SF是一款功能全面的桌面端技术练习模拟框架,该工具针对基于主场景事件列表的各种事件提供了模块化的架构,并包含了针对每一个练习定义的规则集,以及允许为对应平台参数定义参数的配置文件。 该工具的主模块能够执行与其他特定模…

Python学习 问题汇总(None)

None的总结 在Python中,对于一些变量往往需要赋初始值,为了防止初始值与正常值混淆,通常采用置0或置空操作,置0比较简单,置空则是赋NoneNone是一个空值,可以赋给任意类型的变量,起到占位的作用…

德人合科技 | —数据泄露可能会对公司造成哪些影响?

数据泄露可能会对公司造成多方面的影响,以下是一些可能的影响: 财务损失:数据泄露可能导致公司遭受财务损失。攻击者可能会盗取公司的敏感信息,如客户信息、银行账户信息、商业机密等,并利用这些信息进行欺诈、盗窃等非…

从键盘输入5个整数,将这些整数插入到一个链表中,并按从小到大次序排列,最后输出这些整数。

设节点定义如下struct Node {int Element; // 节点中的元素为整数类型struct Node * Next; // 指向下一个节点 }; 从键盘输入5个整数,将这些整数插入到一个链表中,并按从小到大次序排列,最后输出这些整数。注释那段求指出错误,求解…

微信自动回复,基于python

#!/usr/bin/python3 # -*- coding: utf-8 -*-import numpy as np import pandas as pd from uiautomation import WindowControl import csvwx WindowControl(Name微信,searchDepth1 ) # 切换窗口 wx.ListControl() wx.SwitchToThisWindow() # 寻找会话控件绑定 hw wx.…

【竞技宝jjb.lol】LOL:圣枪剑魔屡建奇功 JDG2-0轻松击败TT

北京时间2024年3月2日,英雄联盟LPL2024春季常规赛继续进行,昨日共进行三场比赛,第三场比赛由JDG对阵TT。本场比赛TT前期和JDG有来有回,但中期团战处理还是稍欠火候,最终JDG2-0轻松击败TT。以下是本场比赛的详细战报。 …

Shellcode ---> 脚本命令入门

今天来浅讲一下shellcode,开始之前,先来乐一乐,哈哈哈哈哈哈哈哈哈哈哈哈 以下的命令你们都别乱用 !!!!!!!!!!&#xff01…

九、JavaAgent核心——Instrumentation

九、JavaAgent核心——Instrumentation 动态 Instrumentation 是 Java SE 5 的新特性,它在 java.lang.instrument 包中,它把 Java 的 instrument 功能从本地代码中释放出来,使其可以用 Java 代码的方式解决问题。使用 Instrumentation&#…

docker报错 fatal error: runtim: out of memory

fatal error: runtim: out of memory 真无语了 系统内存也够用 原来是虚拟机的不够用了 (原本1g已经加到2g还是会报错) 直接3台虚拟机都加到4g

物联网与智慧城市:融合创新,塑造未来城市生活新图景

一、引言 在科技飞速发展的今天,物联网与智慧城市的融合创新已成为推动城市发展的重要力量。物联网技术通过连接万物,实现信息的智能感知、传输和处理,为智慧城市的构建提供了无限可能。智慧城市则运用物联网等先进技术,实现城市…

Flink基本原理 + WebUI说明 + 常见问题分析

Flink 概述 Flink 是一个用于进行大规模数据处理的开源框架,它提供了一个流式的数据处理 API,支持多种编程语言和运行时环境。Flink 的核心优点包括: 低延迟:Flink 可以在毫秒级的时间内处理数据,提供了低延迟的数据…

7款炫酷的前端动画特效分享(二)(附效果图及在线演示)

分享7款好玩的前端动画特效 其中有CSS动画、SVG动画、js小游戏等等 下方效果图可能不是特别的生动 那么你可以点击在线预览进行查看相应的动画特效 同时也是可以下载该资源的 jQuery拉开帷幕特效 基于jQuery实现的帷幕特效 点击右侧拉条 可以实现帷幕的收起也展开 非常的炫酷…

协议和序列化反序列化

“协议”和序列化反序列化 “协议”的概念: “协议”本身是一种约定俗成的东西,由通讯双方必须共同遵从的一组约定,因此我们一定要将这种约定用计算机语言表达出来,此时双方计算机才能识别约定的相关内容 我们把这个规矩叫做“…

【论文阅读】基于图像处理和卷积神经网络的板式换热器气泡识别与跟踪

Bubble recognizing and tracking in a plate heat exchanger by using image processing and convolutional neural network 基于图像处理和卷积神经网络的板式换热器气泡识别与跟踪 期刊信息:International Journal of Multiphase Flow 2021 期刊级别:…

02-Vue 计算属性与监听器与VUE-cli使用

1.计算属性 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width,…

C++从入门到实践要掌握的20个的代码案例及入门技巧

文章目录 C从入门到实践要掌握的20个的代码案例C从入门到实践&#xff0c;开发者需要掌握的基础知识和技能可以分为以下几个阶段&#xff1a;初级阶段&#xff1a;中级阶段&#xff1a;高级阶段&#xff1a; C快速入门技巧&#xff1a; C从入门到实践要掌握的20个的代码案例 C…

vSphere资源管理

一 内存、CPU、资源池和vApp 内存部分&#xff1a; 关联VM内存 我们可以超额的关联内存给VM。例如&#xff1a;ESXI物理主机内存只有8G&#xff0c;但我们可以给三个VM都分配4G内存。 2.ESXI四大高级内存控制技术 a.Page sharing&#xff08;透明的页面共享&#xff09; 虚…

鸿蒙Harmony应用开发—ArkTS声明式开发(自定义事件分发)

ArkUI在处理触屏事件时&#xff0c;会在触屏事件触发前进行按压点和组件区域的触摸测试&#xff0c;来收集需要响应触屏事件的组件&#xff0c;再基于触摸测试结果分发相应的触屏事件。在父节点&#xff0c;开发者可以通过onChildTouchTest决定如何让子节点去做触摸测试&#x…

iclone空白处粘贴帧是否归集到前面的clip的关键开关

1假设走到这里了我想给他一个停止站立的姿势即把最开始的站立姿势给他让他自动过渡&#xff08;想的美&#xff0c;其实此路不通&#xff0c;因归集的pose方位不会随前一个clip末帧因此过程只会滑动或滑转过去&#xff0c;不适合脚步行走的生物&#xff0c;详见下面的实例&…