JVM内存管理

文章目录

  • 1、运行时数据区域
    • 1.1 程序计数器(线程私有)
    • 1.2 JAVA虚拟机栈(线程私有)
    • 1.3 本地方法栈
    • 1.4 Java堆(线程共享)
    • 1.5 方法区(线程共享)
    • 1.6 直接内存(非运行时数据区域)
  • 2、Java对象组成
    • 2.1 对象头(Header)
    • 2.2 实例数据(Instance Data)
    • 2.3 对齐填充(Padding)
  • 3、对象访问方式
    • 3.1 句柄访问方式
    • 3.2 直接指针访问方式
  • 4、GC判断对象回收算法
    • 4.1 引用计数算法(JVM未使用)
    • 4.2 可达性分析算法(JVM使用)
  • 5、对象引用种类
  • 6、对象回收过程(两次标记)
  • 7、方法区回收
  • 8、GC回收算法(思想)
    • 8.1 标记-清除算法
    • 8.2 复制算法
    • 8.3 标记-整理算法
    • 8.4 分代收集算法(JVM常用)

1、运行时数据区域

  • 程序计数器
  • JAVA虚拟机栈
  • 本地方法栈
  • 方法区

在这里插入图片描述

1.1 程序计数器(线程私有)

简述:

  • 作用:当前线程所执行的字节码的行号指示器,字节码解释器通过改变计数器值选取下一条需要执行的字节码指令。
  • 线程私有:Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间方式实现,在任意时刻,一个处理器只会执行一条线程中的指令。为了线程切换后恢复到正确的执行位置,每条先后才能都需要有一个独立的程序计数器,线程之间的计数器互不影响,独立存储。

说明:

  • 线程正在执行java方法,此时计数器记录的是虚拟机字节码地址;
  • 线程正在执行native方法,此时计数器记录值为空,此内存为JVM规范中没有规定任何OutOfMemoryError情况的区域

1.2 JAVA虚拟机栈(线程私有)

简述:
          虚拟机栈描述的是java内存模型,每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法被调用到执行结束的过程,对应着一个栈帧从入栈到出栈的过程。

  • 局部变量表:存放了编译器可知的八种基本数据类型(boolean\byte\char\short\int\float\long\double)和对象引用类型。
  • 局部变量表中long和double占用2个局部变量空间(slot),其它数据类型占用1个,局部变量表所需内存空间在编译期间完成分配,在运行期间该方法局部变量表的大小不会改变。

说明:(异常状况)

  • StackOverflowError:栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
  • OutOfMemoryError:若虚拟机栈可扩展,当无法扩展无法申请到足够内存将抛出OutOfMemoryError异常

1.3 本地方法栈

简述:
          本地方法栈为JVM使用Native服务,虚拟机栈则为Java方法(字节码)服务。

说明:(异常状况)

  • StackOverflowError(暂不赘述)
  • OutOfMemoryError(暂不赘述)

1.4 Java堆(线程共享)

简述:

  • 在虚拟机启动时创建,存放对象实例。
  • 所有对象实例及数组都在堆上分配,但随着JIT编译器、逃逸分析技术、栈上分配、标量替换优化技术的发展,导致并非所有对象都在堆上分配。

Java堆是垃圾收集器管理的主要区域,即GC堆,从内存回收看,现在收集器基本都是分代收集算法。
Java堆分为:新生代老年代。新生代其中包含Eden空间、From Survivor空间、To Survivor空间等。

说明:(异常状况)

  • Java堆可以是物理上不连续的内存空间,只要逻辑连续即可。
  • OutOfMemoryError:若堆中没有内存完成实例分配且堆无法再扩展,则会抛出OutOfMemoryError异常

1.5 方法区(线程共享)

简述:
           存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
说明:

  • 方法区不等价于永久代,GC分代收集扩展到方法区,或使用永久代来实现方法区而已。其他虚拟机是不存在永久代概念;
  • 垃圾收集行为很少出现在方法区,该区域内存回收主要是对常量池和对类型的卸载,基本回收很少,效果不大理想。
  • OutOfMemoryError:当方法区无法满足内存分配需求时,则将抛出OutOfMemoryError异常

运行时常量池:

  • 是方法区的一部分,Class文件中除了类的版本、字段、方法、接口等描述信息外,还有常量池,用于存储编译期生成的各种字面量和符号引用,是类加载后存到方法区的运行时常量池。
  • 运行时常量池除了存储Class文件中描述的符号引用外,还将翻译出来的直接引用也存储在运营时常量池中。
  • Class文件常量池具备动态性,运行期间也可能将新的常量放入运行时常量池,常见的便是String类的intern()方法

1.6 直接内存(非运行时数据区域)

简述:
          直接内存不是JVM运行时数据区的一部分,也不是JVM规范定义中的内存区域。但是这部分内存也被频繁使用,也可能导致OutOfMemoryError异常
          在JDK 1.4中新引入了NIO(New Input/Output)类,是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,可以使用Native函数直接分配堆外内存,通过存储在Java堆中的DirectByteBuffer对象作为引用,去操作这块内存,可以避免在Java堆与Native堆来回复制数据。

2、Java对象组成

在JVM中,java对象在内存中分为3块区域:

  • (1)对象头(Header);
  • (2)实例数据(Instance Data);
  • (3)对齐填充(Padding)。

2.1 对象头(Header)

对象头包含两部分信息:
(1)存储对象自身运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据长度为32bit或64bit(32位或64位虚拟机),官方称为:Mark Word

标志位状态存储内容
01未锁定对象哈希码、对象分代年龄
00轻量级锁定指向锁记录的指针
10膨胀(重量级锁定)指向重量级锁的指针
11GC标记空,不需要记录信息
01可偏向偏向线程ID、偏向时间戳、对象分代年龄

(2) 类型指针即指向它的类元数据的指针,jvm通过其确定对象属于那个类的实例,并非所有虚拟机都必须在对象数据上保留类型指针,查找元数据信息不一定经过对象本身。Java数组对象头中还有一块用于记录数组长度的数据,普通的Java对象元数据信息能确定对象的大小,但数组的元数据无法确定数组大小。

2.2 实例数据(Instance Data)

          实例数据是对象真正存储的有效信息即程序代码中定义的各种类型的字段内容,无论是父类继承的,还是子类定义的,都将记录下来。这部分存储顺序会受到JVM的分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响,相同宽度的字段总是被分配到一起。
          从分配策略中可以看出,在父类中定义的变量尽会出现在子类之前,若CompactFields参数值为true(默认true),那么子类之中较窄的变量可能会插入到父类变量的空隙之中。

2.3 对齐填充(Padding)

          对齐填充并不是必然存在的,没有特别含义,仅仅起到占位符的作用。JVM自动内存管理系统要求对象起始地址必须是8字节的整数倍即对象大小必须是8字节的整数倍,对象头部分正好是8字节的倍数,因此当对象实例数据部分没有对齐时,则需要通过对齐填充来补全。

3、对象访问方式

          Java程序通过栈上的reference数据来操作堆上的具体对象,实际上reference类型在JVM中只规定了一个指向对象的引用,并没有规定是通过哪种方式去定位、访问堆中对象的具体位置,访问对象的方式取决于虚拟机JVM,目前主流访问方式主要分为两种:
(1)句柄访问方式;
(2)直接指针访问方式;

这两种对象访问方式各有优势:

访问方式优势劣势
句柄访问方式GC时对象移动只改变实例数据指针,reference本身不需要改动需要访问两次指针,获取实例数据和类型数据
直接指针访问方式访问速度快,只访问一次指针GC时对象移动,reference需要改动

3.1 句柄访问方式

          句柄访问方式是Java堆划分出来一块内存作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据(指针)和类型数据的具体地址信息(指针)
在这里插入图片描述

3.2 直接指针访问方式

          直接指针访问方式是reference中存储的就是对象地址,但Java堆对象布局就必须考虑如何放置访问类型数据的相关信息(指针);

在这里插入图片描述

4、GC判断对象回收算法

          JVM对堆进行GC前,需要判断对象是否存活(回收),那些已经“死去”(即不可能再被任何途径使用的对象)。

4.1 引用计数算法(JVM未使用)

定义:
          给对象中添加一个引用计数器,每当有一个地方引用,计数器就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

优缺点:
(1)引用计数算法(Reference Counting)实现简单,判定效率高,大部分场景是一个不错算法,如微软公司发COM(Component Object Model)计数、FlashPlayer、Python语言等等;(优点)
(2)很难解决对象之间相互循环引用问题,如两个对象中的某个字段互相引用 testA.field = testB testB.field = testA;(缺点)

4.2 可达性分析算法(JVM使用)

          通过一系列的“GC Roots”的对象作为起始点,从该节点开始向下搜索形成引用链(Reference Chain),当一个对象到GC Roots无任何引用链时(GC Roots与对象不可达),则说明此对象是不可用,可被回收。

在这里插入图片描述

在Java领域中,能作为*GC Roots对象的包括以下4种

  • (1)虚拟机栈(栈帧中的局部变量表)中引用的对象;
  • (2)方法区中类的静态属性引用的对象;
  • (3)方法区中常量引用的对象;
  • (4)本地方法栈中JNI(Native方法)引用的对象。

5、对象引用种类

无论是引用技术算法、还是可达性分析算法都是通过“引用数量”和“引用链是否可达”,判定对象是否存活都与引用有关。

  • (1)强引用(Strong Reference):强引用在程序代码中常见,如Test test = new Test()只要这个强引用还存在,GC收集器永远不会回收掉被引用的对象
  • (2)软引用(Soft Reference):描述一些还有用但并非必须的对象。对于软引用关联的对象,只要系统内存还足够,则不会回收。在系统将要发生内存溢出(OOM)之前,将会把这些对象列入回收范围之中进行第二次回收,若此次回收还没足够内存就会抛出内存溢出异常;
  • (3)弱引用(Weak Reference):描述非必须对象,强度比软引用更弱,只能生存到下一次GC发生之前,一旦GC将会被会回收掉
  • (4)虚引用(Phantom Reference):幽灵引用或幻影引用,最弱的引用关系。对象是否存在虚引用,不会对其生存时间造成任何影响,也无法通过虚引用获取对象实例。虚引用的唯一目的就是在这个对象被GC后收到一个系统通知

6、对象回收过程(两次标记)

    在可达性分析算法中,一旦java对象与GC Roots不可达时,也并非是必死的,要宣布一个对象真正被回收需要经历两个标记过程。
(1)第一个过程:若对象在进行可达性分析后发现没有与GC Roots 相连接的引用链,则进行第一次标记且进行一次筛选(是否有必要执行finalize()方法)。没有必要执行的条件:当对象没有覆盖 finalize() 方法 或 finalize() 方法已经被虚拟机调用过。若被判定为有必要执行finalize()方法,则该对象将会被放置在F-Queue队列中,然后会由一个虚拟机自动建立的、低优先级的Finalizer线程去执行(JVM触发)。

说明:

  • (1)队列中的成员互不影响,各自执行,不会串联等待,防止某个对象执行finalize()方法缓慢或死循环导致队列其他对象永久性等待,最终导致内存回收系统崩溃。
  • (2)finalize()方法是对象逃脱死亡的最后一次机会,后面GC对F-Queue中的对象进行第二次小规模标记,在第一次执行finalize()方法时与其他引用链上的任何一个对象建立关联即可自救(移除即将回收的集合)。

(2)第二个过程:在第一个过程筛选标记后,对F-Queue队列中的对象进行小规模标记,然后进行回收。

public class OOMTest {public static OOMTest obj = null;public void test(){System.out.println("test方法调用!");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");System.out.println("对象this:"+ this);obj = this;}public static void main(String[] args) throws InterruptedException {obj = new OOMTest();obj = null;// 第一次调用gc,会调用finalize()方法,进行自救System.gc();long startTime = System.currentTimeMillis();// 休眠2秒,gc里面的finalize()方法优先级较低Thread.sleep(TimeUnit.SECONDS.toMillis(2));System.out.println("休眠时间:" + (System.currentTimeMillis() - startTime)/1000);System.out.println("对象obj:"+obj);if(obj != null){obj.test();}else{System.out.println("gc 第一次已回收!");}// 第二次进行gc,不会再调用finalize()方法,无法进行自救obj = null;System.gc();if(obj != null){obj.test();}else{System.out.println("gc 第二次已回收!");}}
}
输出结果:finalize method executed!对象thiscom.jvm.OOMTest@1bb3fcd休眠时间:2对象obj:com.jvm.OOMTest@1bb3fcdtest方法调用!gc 第二次已回收!

7、方法区回收

          Java虚拟机规范中确实说过不要求虚拟机在方法区中实现垃圾收集,并且方法区中垃圾收集性价比较低,堆中的新生代,一次垃圾回收能实现70%~90%的空间,永久代的垃圾收集效率远远低于堆中的回收

永久代的垃圾收集主要分为两部分:
(1)废弃常量:没有其他地方引用这个字面量(常量池);
(2)无用的类:

  • (1)堆中没有该类的任何实例;
  • (2)加载该类的ClassLoader已经回收;
  • (3)该类的Class 对象没有被其他地方引用,无法通过反射来访问该类方法;

8、GC回收算法(思想)

8.1 标记-清除算法

最基础的收集算法:标记——清除(Mark-Sweep)算法,分为两个阶段即标记和清除。
(1)标记:首先标记出所有需要回收的对象;
(2)清除:将标记好的对象统一回收;
缺点:
(1)效率问题:标记和清除两个过程效率不高;
(2)空间问题:标记清除之后产生大量不连续的内存碎片,可能会导致分配大对象时无法分配到足够大的内存而触发GC。
在这里插入图片描述

8.2 复制算法

          为了解决效率问题,复制(copying)算法横空出世,将可用内存划分为大小相等的两块,每次使用其中的一块,当这块内存用完了就将活着的对象复制到另外一块儿,然后将使用过的这块内存全部清理掉。

优点:
          每次对使用过程中的那块内存进行整个回收,内存分配时无需考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存,实现简单、运行高效(存活对象少时)。
缺点:
           可用内存缩小了一半。
运用:
          一般的JVM在新生代中使用复制算法,将新生代分为Eden空间和两块Survivor空间(8:1:1),每次使用Eden和其中一块Survivor,回收时将Eden和Survivor中还存活的对象一次性复制到另外未使用过的Survivor空间上。当复制过程中,用于活着的对象在新的Survivor空间无法存储时,会通过分配担保机制进入老年代
在这里插入图片描述

8.3 标记-整理算法

          复制算法在对象存活率较高时需要进行多次复制操作,效率就会急速下降,若其中一半内存不够用则需要额外的空间进行分配担保,保证在极端情况下也能存活,在老年代就不适用复制算法
          根据老年代的特点,“标记-整理”(Mark-Compact)算法随之诞生,标记过程与最基础算法——“标记-清除”中的标记过程一致,只是清除过程改为整理过程即将存活的对象向另外一端移动,然后直接清理掉端边界以外的内存。

在这里插入图片描述

8.4 分代收集算法(JVM常用)

Java堆分为新生代和老年代,然后根据其特点采用最适当的收集算法。
(1)新生代特点:垃圾收集时发现大批对象可回收,少量对象存活,采用 复制算法
(2)老年代特点:对象存活率高,没有额外空间分配担保,采用 标记—整理算法

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

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

相关文章

vue动态生成行

vue代码 <el-table :data"form.lineInfos" :bordertrue style"width: 99.99%;"> <el-table-column type"index" label"序号" width"50"></el-table-column> <el-table-column prop"unitPrice&qu…

golang协程池(goroutine池)ants库实践

golang中goroutine由运行时管理&#xff0c;使用go关键字就可以方便快捷的创建一个goroutine,受限于服务器硬件内存大小&#xff0c;如果不对goroutine数量进行限制&#xff0c;会出现Out of Memory错误。但是goroutine泄漏引发的血案&#xff0c;想必各位gopher都经历过&#…

在Qt中使用LoadLibrary无法加载DLL

Qt系列文章目录 文章目录 Qt系列文章目录前言一、问题分析 前言 最近因项目需要使用qt做开发&#xff0c;之前使用LoadLibrary加载dll成功&#xff0c;很庆幸&#xff0c;当一切都那么顺风顺水的时候&#xff0c;测试同事却发现&#xff0c;在windows平台上个别电脑上加载dll会…

Redis BitMap/HyperLogLog/GEO/布隆过滤器案例

面试问题&#xff1a; 抖音电商直播&#xff0c;主播介绍的商品有评论&#xff0c;1个商品对应了1系列的评论&#xff0c;排序展现取前10条记录用户在手机App上的签到打卡信息&#xff1a;1天对应1系列用户的签到记录&#xff0c;新浪微博、钉钉打卡签到&#xff0c;来没来如何…

【Grafana】中文界面配置 v10.0.3

比如通过 docker run -d -p 3000:3000 -v /e/code/monitor/grafana/grafana.ini.txt:/etc/grafana/grafana.ini grafana/grafana运行一个容器&#xff08;最新是v10.0.3&#xff09;。 在 /admin/settings 可以看到 users 部分有一个 default_language 配置。 所以在挂载到 …

php代码审计,php漏洞详解

文章目录 1、输入验证和输出显示2、命令注入(Command Injection)3、eval 注入(Eval Injection)4、跨网站脚本攻击(Cross Site Scripting, XSS)5、SQL 注入攻击(SQL injection)6、跨网站请求伪造攻击(Cross Site Request Forgeries, CSRF)7、Session 会话劫持(Session Hijacking…

自动化实践-全量Json对比在技改需求提效实践

1 背景 随着自动化测试左移实践深入&#xff0c;越来越多不同类型的需求开始用自动化测试左移来实践&#xff0c;在实践的过程中也有了新的提效诉求&#xff0c;比如技改类的服务拆分项目或者BC流量拆分的项目&#xff0c;在实践过程中&#xff0c;这类需求会期望不同染色环境…

SpringCloud源码探析(九)- Sentinel概念及使用

1.概述 在微服务的依赖调用中&#xff0c;若被调用方出现故障&#xff0c;出于自我保护的目的&#xff0c;调用方会主动停止调用&#xff0c;并根据业务需要进行对应处理&#xff0c;这种方式叫做熔断&#xff0c;是微服务的一种保护方式。为了保证服务的高可用性&#xff0c;…

rknn3588如何查看npu使用情况

cat /sys/kernel/debug/rknpu/load

windows环境下编译OpenJDK12

环境&#xff1a;Windows11 目录&#xff1a; 1、下载OpenJDK12源码 下载地址&#xff1a; https://hg.openjdk.org/jdk/jdk12 点击zip下载到本地。 解压到本地。 Tip&#xff1a;注意本地路径中最好不要包含中文或空格。 2、阅读一遍doc/building.html 如果只是想构建J…

白帽黑帽与linux安全操作

目录 白帽黑帽 Linux安全 白帽黑帽 白帽&#xff08;White Hat&#xff09;和黑帽&#xff08;Black Hat&#xff09;通常用于描述计算机安全领域中的两种不同角色。白帽黑客通常被认为是合法的安全专家&#xff0c;他们通过合法途径寻找和修复安全漏洞&#xff0c;帮助企业和…

Linux/centos上如何配置管理samba服务器?

Linux/centos上如何配置管理samba服务器&#xff1f; 1 samba服务相关知识1.1 SMB协议1.2 samba工作原理1.2.1 相关进程1.2.2 samba工作流程1.2.3 samba功能 2 samba服务器安装2.1 利用光驱安装2.2 利用光盘映射文件 3 启动与停止samba服务4 配置samba服务器4.1 samba主配置文件…

解读百威亚太2023上半年财报:啤酒大年百威如何重塑高端化之路?

随着消费者的需求提升&#xff0c;啤酒行业向高端化发展&#xff0c;其中知名度较高的百威亚太、华润啤酒、青岛啤酒、燕京啤酒、嘉士伯等品牌在高端市场持续鏖战&#xff0c;实际成果如何也可以从业绩一探究竟。 以百威亚太为例。8月3日&#xff0c;百威亚太发布2023年上半年…

记录一次electron打包提示文件找不到的解决方法

没有配置files选项 files的作用是配置打包到应用程序的构建资源 就是说如果你想使用项目那个目录下的文件 就得通过files配置一下不然就会报错 json文件或者yml文件会报的错 格式是这样的 "files": ["dist-electron", "dist"],electron打包配…

【C++学习】STL容器——list

目录 一、list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list 迭代器失效 二、list的模拟实现 2.1 模拟实现list 三、list和vector的对比…

【算法挨揍日记】day01——双指针算法_移动零、 复写零

283.移动零 283. 移动零https://leetcode.cn/problems/move-zeroes/ 题目&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 …

生信豆芽菜-火山图绘制使用说明

网站&#xff1a;http://www.sxdyc.com/visualsVolcano 一、火山图简介 火山图是散点图的一种&#xff0c;它将统计测试中的统计显著性量度&#xff08;如p value&#xff09;和变化幅度&#xff08;logFC&#xff09;相结合&#xff0c;能够快速直观地识别那些变化幅度较大且具…

13个Python最佳编程技巧,越早知道越好

每天我们都会面临许多需要高级编码的编程挑战。你不能用简单的 Python 基本语法来解决这些问题。在本文中&#xff0c;我将分享 13 个高级 Python 脚本&#xff0c;它们可以成为你项目中的便捷工具。如果你目前还用不到这些脚本&#xff0c;你可以先添加收藏&#xff0c;以备留…

springMVC 程序开发

目录 一. 认识 springMVC spring&#xff0c;springBoot&#xff0c;springMVC的关系 二. springMVC 的连接和获取参数 1. 注解分析&#xff08;不带参数&#xff09; 2. 获取参数 3. 获取对象参数 4. 重命名功能 5. 获取 JSON 对象 6. 通过 path 文件路径来传递参数…

新版Android Studio模拟器浮动

&#xff08;水一篇&#xff0c;但其实很多入门同学不知道&#xff09; 安装新版Andorid Studio后会发现模拟器是内嵌在AS中的&#xff0c;如何让她浮动