【JVM】亿级流量调优(一)

news2024/9/20 11:32:05

亿级流量调优

oop模型

在这里插入图片描述

前面的klass模型,它是Java类的元信息在JVM中的存在形式。这个oop模型是Java对象在JVM中的存在形式

内存分配策略:

  • 1.空闲列表
  • 2.指针碰撞(jvm采用的)
    2.1 top指针:执行的是可用内存的起始位置
    2.2 采用CAS的方式
  • 3.TLAB 线程私有堆
  • 4.PLAB 老年代的线程私有堆

对象的创建

Java是一门面向对象的编程语言,在Java程序运行过程中无时无刻都有对象被创建出来。在语言层面上,创建对象(例如克隆、反序列化)通常仅仅是一个new关键字而已,而在虚拟机中,对象(引用类型的对象)的创建又是一个怎样的过程呢?
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号一弄,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,那必须先执行响应的类加载过程。
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针想空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为"指针碰撞"(Bump the Pointer)。如果Java堆中的内存并不是规整的,已使用内存和空闲的内存相互交错,那就没有办法简单进行指针碰撞了,虚拟机就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为"空闲列表"(Free List).选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法时指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表
(内存分配算法也跟对象的存活周期有关,新生代大部分对象都朝生夕死,复制算法进行GC完之后,内存就是规整的,而老年代,存活对象相比新生代来说存活率要高,内存不太容易规整,如果不带整理的话,使用指针碰撞失败的概率会高很多。所以老年代采用空闲列表)
除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案,一种是堆分配内存空间的动作进行同步处理——实际上迅即采用CAS配上失败重试的方式保证更新操作的原子性;另外一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用了TLAB,可以通过-XX:+/-UseTLAB参数来设定

内存分配完成之后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

接下来,虚拟机要对对象进行必要的设置,例如这个对象时哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。根据虚拟机当前的运行状态不同,如是否启用偏向锁等,对象头会有不同的设置方式。

在上面的工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始——方法还没有执行,所有的字段都还为0,所以,一般来说(由字节码中是否跟随invokespecial指令所决定),执行new指令之后会接着执行方法,把对象按照程序员的医院进行初始化,这样一个真正可用的对象才算完全生成出来

HotSpot源码,_new字节码指令分析

在这里插入图片描述

  • 1.确保常量池中存放的是已解释的类
  • 2.确保对象所属类型已经经过初始化阶段
  • 3.取对象长度
  • 4.记录是否需要将对象所有字段置为零
  • 5.是否在TLAB中分配对象,否则直接在Eden中分配对象.cmpxchg是x86中的CAS指令,这里是一个C++方法,通过CAS方式分配空间,如果并发失败,转到retry中充实,知道成功分配为止
  • 6…如果需要,则为对象初始化零值
  • 7.根据是否启用偏向锁来设置对象头信息
  • 8.将对象引用入栈,继续执行下一条指令

1.空闲列表

在这里插入图片描述

OS把不常用的内存写到硬盘上,如果有进程需要读取引发缺页异常,进而会去硬盘上读
空闲列表机制
在操作系统中,内存分配策略的空闲列表机制是一种管理内存资源的方法。以下是其基本原理和步骤

  • 1.基本原理:

  • 1.1 内存块管理:操作系统将内存划分为多个块(block),每个块可以是空闲的,也可以是已分配的

  • 1.2 空闲列表:操作系统维护一个记录所有空闲内存块的列表,称为空闲列表。这个列表通常会记录每个空闲块的大小和起始地址。

  • 2.步骤:

  • 2.1 初始化:当系统启动时,除了操作系统本身占用的内存外,其余的内存都被视为一个大的空闲快,并被加入到空闲列表中

  • 2.2 分配内存:

  • 2.2.1 当一个进程请求内存时,操作系统会根据请求的大小在空闲列表中查找何时的空闲块

  • 2.2.2 查找策略可以是首次适配(first fit)、最佳适配(best fit)或最坏适配(worst fit)

  • 2.2.3 一旦找到合适的空闲块,操作系统会从空闲列表中移除该块,并将其标记为已分配,然后将内存分配给请求的进程

  • 2.3 内存释放

  • 2.3.1 当进程释放内存时,操作系统会回收这块内存,并将其标记为空闲

  • 2.3.2 操作系统可能会将这块空闲与周围的空闲块合并,形成一个更大的空闲块,以减少内存碎片
    2.3.3 合并后的空闲块或新的空闲块会被重新加入到空闲列表中

  • 2.4 碎片整理

  • 2.4.1 随着内存的分配和释放,内存可能会出现碎片化,即空闲内存分散在各个角落,导致无法满足大的内存请求

  • 2.4.2 空闲列表机制可能会通过移动已分配的内存块来整理碎片,但这在实际操作中可能比较复杂且耗时

  • 优点:
    简单性:空闲列表机制相对简单,易于实现
    灵活性:可以根据不同的内存分配策略(如首次适配、最佳适配等)来优化内存使用

  • 缺点:
    维护开销:随着内存分配和释放的频繁进行,空闲列表的维护可能会带来一定的开销
    内存碎片:可能导致内存碎片,尤其时当空闲块和已分配块的大小频繁变动时。

操作系统为什么不采用指针碰撞机制进行内存分配?

操作系统不采用指针碰撞的机制进行内存分配,主要是因为操作系统的内存管理需要面对复杂和多样化的环境。以下是一些关键原因:

  • 1.多任务和多用户
    操作系统:必须支持多个进程和线程的运行,每个进程或线程可能需要不同大小的内存,且分配和释内存的时间点是随机的
    指针碰撞:适用于单一连续内存分配的场景,不适合处理多任务环境下的复杂内存需求
  • 2.内存碎片
    操作系统:需要处理内存碎片问题,因为不同大小的内存块被分配和释放后,内存中可能会留下无法被利用的小空闲块
    指针碰撞:不擅长处理内存碎片,因为它假设内存分配是连续的,如果内存碎片严重,指针碰撞机制将无法有效工作
  • 3.内存分配的灵活性
    操作系统:需要能够分配任意大小的内存块以满足不同进程的需求
    指针碰撞:通常需要一个连续的内存区域,并且当内存区域不足以容纳新分配的内存块是,需要额外的机制来处理这种情况
  • 4.物理内存与虚拟内存
    操作系统:使用虚拟内存技术,将物理内存与虚拟内存地址映射起来,这使得内存分配更加复杂
    指针碰撞:适用于简单的物理内存分配,不涉及复杂的地址映射
  • 5.安全性和隔离性
    操作系统:需要确保不同进程的内存是隔离的,防止一个进程访问或修改另一个进程的内存
    指针碰撞:需要额外的机制来保证内存的安全性和隔离性
  • 6.性能考量
    操作系统:必须高效地管理内存以满足性能需求,这通常意味着需要一个能够快速响应的内存分配策略
    指针碰撞:虽然分配速度快,但在多任务环境下,它可能导致内存利用率低下,因为它可能留下很多小的空闲内存块
  • 7.系统调用和API
    操作系统:提供了系统调用和API供应用程序请求和释放内存,这些调用需要能够处理各种复杂的内存分配请求
    指针碰撞:无法直接适应这些系统调用和API的需求

2.指针碰撞(CAS)

在这里插入图片描述

如图所示,bottom指向内存区域的头部,end指向内存区域的尾部,top指针开始指向头部(可用内存的起始位置)。如果new_top = top(当前top) + 对象大小满足这个等式,则分配成功,并top=new_top

为什么JVM不采用空闲列表的内存分配策略而是采用指针碰撞的形式?

Java虚拟机(JVM)内存分配策略与操作系统分配策略的不同,主要由以下几个因素决定的:

  • 1.内存管理的抽象层级不同:
    操作系统:操作系统负责管理物理内存,直接与硬件交互,需要处理多种复杂情况,如内存碎片、多进程/线程的内存需求等
    JVM:JVM运行在操作系统之上,主要负责管理Java程序的运行时内存,它对内存的管理更加抽象化,并且通常不需要处理硬件级别的内存碎片问题
  • 2.内存分配的特点
    空闲列表:适用于需要频繁分配和释放不同大小内存的场景,且物理内存可能存在碎片
    指针碰撞:适用于对象大小相对一致且频繁创建和销毁的场景,如JVM中的对象分配
  • 3.JVM内存分配的具体考虑
    效率:指针碰撞时一种非常高效的内存分配方式。在JVM中,当一个新的对象需要被分配时,只需要移动以下指针(分配指针),而不需要遍历整个空闲列表来查找合适的内存块。这大大减少了内存分配的开销
    内存连续性:指针碰撞可以保证分配的内存是连续的,这对于提高缓存命中率有好处,因为连续的内存访问往往能更好地利用CPU缓存
    内存碎片:由于JVM通常会分配和释放大量大小相似的对象,内存碎片问题不像在操作系统中那么严重。JVM通过垃圾回收来管理内存,看可以在GC过程中重新整理内存,减少碎片
    垃圾回收:JVM采用垃圾回收机制来自动管理内存。当对象不再被引用时,垃圾回收期会自动回收他们所占用的内存。这种方式与空闲列表的内存分配策略相比,减少了手动内存释放的复杂性,并且通过不同的垃圾回收u算法来优化没存使用
  • 4.JVM的内存模型:
    堆空间:JVM的堆空间是用于存储Java对象的地方,通常分为老年代和新生代等,不同代的内存管理策略不同。新生代采用复制算法,老年代采用标记-清除或标记-整理算法。这些算法与指针碰撞的内存分配策略更为契合。

JVM选择指针碰撞的内存分配策略,而不是空闲列表,是因为这种策略更符合JVM内存管理的需求,能够提供更高的内存分配和回收效率,并且与JVM的垃圾回收机制更为兼容

3.TLAB线程私有堆(新生代)

JVM里面如果用锁来控制对象内存的分配的话,会比较繁琐,它是堆中某一块私有内存区域,如果用完了再还给JVM,重新盛情一块更大的内存

4.PLAB(TLAB的老年代)

对象的内存布局

在这里插入图片描述

  • 1.MarkWord:
    32位机器下占4B,64位机器下占8B
  • 2.类型指针:
    开启指针压缩占4B,关闭:占8B
  • 3.数组长度
    如果是数组对象:4B,非数组对象的话是没有的
  • 4.对象头中的对齐填充(数组对象才有)对象头尾部
  • 5.实例数据:非静态属性数据
    boolean:1B
    byte:1B
    char:Java(2B) C++(1B)
    int:4B
    float:4B
    long:8B
    double:8B
    引用类型:开启指针压缩:4B 关闭8B
  • 6.对齐填充区域中的对齐填充
    跟jvm规范有关,所有的对象大小都必须能被8整除,也叫8字节对齐

计算对象大小

没有属性的对象

在这里插入图片描述

MarkWord:(64位)8B
类型指针:(默认是开启指针压缩的)4B
由于8B+4B=12B,不够被8整除,所以对齐填充4B
对象大小:8B+4B+4B=16B

有属性的对象

Mark Word:8B
类型指针:4B
实例数据:4B+4B
对象大小

数组对象

在这里插入图片描述

Mark Word:8B
类型指针:注意这里的数组类型是int类型,也就是说它的OopDesc是TypeArrayOopDesc,4B
数组长度:4B
实例数据:4B+4B+4B
8B+4B+4B+4B+4B+4B=28B,还是不能被8整除,所以对齐填充需要补4B
8B+4B+4B+4B+4B+4B+4B=32B

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

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

相关文章

使用PhaGCN2/vConTACT2进行病毒分类注释

Introduction 在微生物群落的研究中,分类和注释数量庞大的未培养古细菌和细菌病毒一直是一个难题,主要原因是缺乏统一的分类框架。 目前,用于病毒分类的多种基于基因组的方法已经被提出,主要集中在细菌、古细菌和真核生物病毒的…

隧道代理ip使用

简介 隧道代理(Tunnel Proxy)是一种特殊的代理服务,它的工作方式是在客户端与远程服务器之间建立一条“隧道”。这种技术常被用来绕过网络限制或提高网络安全性。 主要功能 IP地址变换:隧道代理能够改变客户端的IP地址&#xf…

《javaEE篇》--线程池

线程池是什么 线程的诞生是因为进程创建和销毁的成本太大,但是也是相对而言,如果频繁的创建和销毁线程那么这个成本就不能忽略了。 一般有两种方法来进一步提高效率,一种是协程(这里不多做讨论),另一种就是线程池 假如说有一个学校食堂窗口…

【Node】【2】创建node应用

创建node应用 node应用,不仅可以实现web应用,也能实现http服务器。 如果是php写后端,还需要有http服务器,比如apache 或者 nginx。 但是现在主流都是java写后端,也可以像 Node.js 一样用于实现 Web 应用和 HTTP 服务…

chapter08-面向对象编程(包、访问修饰符、封装、继承)day08

目录 277-包的使用细节 278-访问修饰符规则 279-访问修饰符细节 281-封装介绍 282-封装步骤 283-封装快速入门 284-封装与构造器 285-封装课堂练习 286-为什么需要继承 277-包的使用细节 1、需要哪个包就导入哪个包,不建议全部导入* 2、包的使用细节&…

宵暗的妖怪

宵暗的妖怪 错误代码和正确代码对比 #include <iostream> #include <string.h> using namespace std; const int N1e510; long long a[N],f[N],g[N]; // f 以i为结尾&#xff0c;饱食度最大值 // g 0-i中&#xff0c;饱食度最大值int main() {int n;cin>>…

Linux 内核源码分析---IPv6 数据包

IPv6是英文“Internet Protocol Version 6”&#xff08;互联网协议第6版&#xff09;的缩写&#xff0c;是互联网工程任务组&#xff08;IETF&#xff09;设计的用于替代IPv4的下一代IP协议&#xff0c;其地址数量号称可以为全世界的每一粒沙子编上一个地址。 由于IPv4最大的…

看图学sql之sql中的子查询

&#xfeff;&#xfeff; &#xfeff;where子句子查询 语法&#xff1a; SELECT column_name [, column_name ] FROM table1 [, table2 ] WHERE column_name OPERATOR(SELECT column_name [, column_name ]FROM table1 [, table2 ][WHERE]) 子查询需要放在括号( )内。O…

课后作业-第四次

1.将web-ssrfme.zip解压缩在Ubuntu下 Docker-compose up -d 更新后的镜像重新启动容器 2.拉取成功ssrfme镜像 3.使用端口访问文件&#xff0c; 可以看到有一个过滤条件&#xff0c;它限制了file&#xff0c;dict协议&#xff0c;127.0.0.1和localhost 也不能用&#xff0c;…

中仕公考怎么样?2025年国考现在准备来得及吗?

2025年国考时间线 2024年10月发布公告——2024年11月报名确认——2024年11月打印准考证——2024年12月笔试——2024年3月现场资格审查——2024年3-4月面试——2025年5月补录环节 考试科目&#xff1a; 笔试的主要科目是:行测申论 ①行测:常识、言语理解、判断推理、数量关系…

2025智能电动窗帘推荐!电动窗帘应该如何选择?看一篇窗帘省钱攻略!一体化电动窗帘

史新华 智能家居已经走进我们的生活。 智能马桶、智能穿衣镜、语音茶吧机、小爱音箱、天猫精灵这些家居好物让生活更加精致&#xff0c;智能窗帘走进千家万户还会远吗&#xff1f;&#xff01; 我最开始关注电动窗帘&#xff0c;就是从住酒店开始的&#xff0c;经常出差的同学…

uni-app里引入阿里彩色矢量图标(Symbol),却发现图标显示为黑白

当使用uniapp并尝试引入阿里iconfont的彩色图标时&#xff0c;发现图标显示为黑白。原因是Fontclass模式不支持彩色图标。 解决方法是下载Symbol模式的SVG文件&#xff0c;使用iconfont-tools进行转换&#xff0c;然后在项目中全局引入转换后的CSS文件&#xff0c;最终在组件中…

探索联邦学习:保护隐私的机器学习新范式

探索联邦学习&#xff1a;保护隐私的机器学习新范式 前言联邦学习简介联邦学习的原理联邦学习的应用场景联邦学习示例代码结语 前言 在数字化浪潮的推动下&#xff0c;我们步入了一个前所未有的数据驱动时代。海量的数据不仅为科学研究、商业决策和日常生活带来了革命性的变化&…

Datawhale X 李宏毅苹果书 AI夏令营_深度学习基础学习心得

本次学习了深度学习中的局部最小值 1、书上说有时候模型一开始就训练不起来&#xff0c;不管怎么更新参数损失都不下降。我之前遇到过这种情况&#xff0c;大概是做一个数据很不平衡的二分类&#xff0c;正负样本比例大概为9&#xff1a;1&#xff0c;模型倾向于全部预测为正样…

docker-compose自动化部署前后端项目--最终篇

docker-compose部署 一个项目肯定包含多个容器&#xff0c;每个容器都手动单独部署肯定费时费力。docker-compose可以通过脚本来批量构建镜 像和启动容器&#xff0c;快速的部署项目。 使用docker-compose部署主要是编写 docker-compose.yml 脚本。 项目结构 不论是 Dockerfil…

set的所有操作

1.基本概念 2.构造和赋值 3.大小和交换 4.插入和删除 5.查找和统计 6.set和multiset的区别 7.pair对组创建 用p.first和p.second调用前后两个属性。 8.仿函数实现降序排列 自定义数据类型也一样用仿函数&#xff1a;

【ubuntu20.4 常用经验分享】

文章目录 背景&#xff1a;问题解答1、软件替换3、办公4、提供多少价值 总结 背景&#xff1a; 个人AI深度学习&#xff0c;在windows下很不方便&#xff0c;容易各种莫名错误&#xff0c;各种生态也不好 那么linux是一个选择&#xff0c;开始时候时保守安装了双系统&#xff…

QTableView的一行里添加两个按钮

我是光明正大地抄&#xff0c;作者说的欢迎转载 作者&#xff1a;李鹏 出处&#xff1a;http://www.cnblogs.com/li-peng/ 本文版权归作者和博客园共有&#xff0c;欢迎转载&#xff0c;但未经作者同意必须保留此段声明&#xff0c;且在文章页面明显位置给出原文连接&#xff0…

基于Android Studio 多功能记事本-MySQL版

目录 一、项目演示 二、开发环境 三、项目详情 四、项目完整源码 一、项目演示 基于Android Studio 多功能记事本--MySQL版 二、开发环境 三、项目详情 1.启动页 这段代码主要实现了以下功能&#xff1a; 1. **延迟跳转**&#xff1a;在 StartActivity 中&#xff0c;使用…

polarctf靶场[WEB]cookie欺骗、upload、签到

[web]cookie欺骗 考点&#xff1a;cookie值 工具&#xff1a;Burp Suite抓包 根据题目提示&#xff0c;cookie欺骗&#xff0c;所以要在cookie值寻找关键 进入网页之后&#xff0c;说只有admin用户才能得到flag&#xff0c;而我们此时只属于普通访客 我们查看cookie值&…