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方法调用!");
    }
    @Override
    protected 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@1bb3fcd
	test方法调用!
	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)老年代特点:对象存活率高,没有额外空间分配担保,采用 标记—整理算法

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

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

相关文章

拥抱AIGC浪潮,亚信科技将如何把握时代新增量?

去年底,由ChatGPT带起的AIGC浪潮以迅雷不及掩耳之势席卷全球。 当互联网技术的人口红利逐渐消退之际,AIGC就像打开通用人工智能大门的那把秘钥,加速开启数智化时代的到来。正如OpenAI CEO Sam Altman所言:一个全新的摩尔定律可能…

560. 和为 K 的子数组

思路 本题的主要思路为创建一个哈希表记录每个0~i的和,在遍历这个数组的时候查询有没有sum-k的值在哈希表中,如果有,说明有个位置到当前位置的和为k。   有可能不止一个,哈希表负责记录有几个sum-k,将和记录下来。这…

10个问题,带你重新认识smardaten企业级无代码

很多新客户在接触数睿数据,或者在初步认识smardaten企业级无代码的时候,大家更多地以为只是个普通的无代码工具。在交流过程中,大家也提出了很多疑惑: smardaten无代码平台包括哪些能力? 适合开发哪些应用&#xff1f…

AI自动驾驶

AI自动驾驶 一、自动驾驶的原理二、自动驾驶的分类三、自动驾驶的挑战四、自动驾驶的前景五、关键技术六、自动驾驶的安全问题七、AI数据与自动驾驶八、自动驾驶的AI算法总结 自动驾驶技术是近年来备受关注的热门话题。它代表了人工智能和机器学习在汽车行业的重要应用。本文将…

web集群学习:源码安装nginx配置启动服务脚本、IP、端口、域名的虚拟主机

目录 1、源码安装nginx,并提供服务脚本。 2、配置基于ip地址的虚拟主机 3、配置基于端口的虚拟主机 4、配置基于域名的虚拟主机 1、源码安装nginx,并提供服务脚本。 1、源码安装会有一些软件依赖 (1)检查并安装 Nginx 基础依赖…

PHP智能人才招聘网站mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP智能人才招聘网站 是一套完善的web设计系统,对理解php编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 下载地址 https://download.csdn.net/download/qq_41221322/88199392 视频演示 PH…

TextBrewer:融合并改进了NLP和CV中的多种知识蒸馏技术、提供便捷快速的知识蒸馏框架、提升模型的推理速度,减少内存占用

TextBrewer:融合并改进了NLP和CV中的多种知识蒸馏技术、提供便捷快速的知识蒸馏框架、提升模型的推理速度,减少内存占用 TextBrewer是一个基于PyTorch的、为实现NLP中的知识蒸馏任务而设计的工具包, 融合并改进了NLP和CV中的多种知识蒸馏技术&#xff0…

GB28181智慧可视化指挥控制系统之执法记录仪设计探讨

什么是智慧可视化指挥控制系统? 智慧可视化指挥控制平台通过4G/5G网络、WIFI实时传输视音频数据至指挥中心,特别是在有突发情况时,可以指定一台执法仪为现场视频监控器,实时传输当前画面到指挥中心,指挥中心工作人员可…

支持对接鸿蒙系统的无线模块及其常见应用介绍

近距离的无线通信得益于万物互联网的快速发展,基于集成部近距离无线连接,为固定和移动设备建立通信的蓝牙技术也已经广泛应用于汽车领域、工业生产及医疗领域。为协助物联网企业终端产品能快速接入鸿蒙生态系统,SKYLAB联手国产芯片厂家研发推…

VR家装提升用户信任度,线上体验家装空间感

近些年,VR家装逐渐被各大装修公司引入,VR全景装修的盛行,大大增加了客户“所见即所得”的沉浸式体验感,不再是传统二维平面的看房模式,而是让客户通过视觉、听觉、交互等功能更加真实的体验家装后的效果。 对于传统家装…

造个轮子-任务调度执行小框架-任务执行器代理实现

文章目录 前言执行器代理代理对象任务清单代理对象任务清单执项对象处理handler对象状态对象代理工厂任务清单代理工厂清单任务项代理工厂总结前言 不知道为啥,今天好像学不进去,没办法,那就继续编码吧。那么今天的话,加更一篇文章,那就是咱们这个任务执行器的实现。先前…

opencv基础57-模板匹配cv2.matchTemplate()->(目标检测、图像识别、特征提取)

OpenCV 提供了模板匹配(Template Matching)的功能,它允许你在图像中寻找特定模板(小图像)在目标图像中的匹配位置。模板匹配在计算机视觉中用于目标检测、图像识别、特征提取等领域。 以下是 OpenCV 中使用模板匹配的基…

Java SPI介绍

SPI Java SPI : Service Provider Interface 是Java平台提供的一种机制,用于动态的加载和扩展功能的机制,它为框架和库提供了一种松耦合的扩展方式,核心是解耦。 例如JDBC驱动,日志框架,等应用,它为开发…

Linux——基础IO(1)

目录 0. 文件先前理解 1. C文件接口 1.1 写文件 1.2 读文件 1.3 输出信息到显示器 1.4 总结 and stdin & stdout & stderr 2. 系统调用文件I/O 2.1 系统接口使用示例 2.2 接口介绍 2.3 open函数返回值 3. 文件描述符fd及重定向 3.1 0 & 1 & 2 3.2…

记录--前端重新部署如何通知用户

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 1. 场景 前端构建完上线,用户还停留还在老页面,用户不知道网页重新部署了,跳转页面的时候有时候js连接hash变了导致报错跳不过去,并且用户体验不到新功能…

SAP Fiori 问题收集

事务代码篇 启动工作台:/N/UI2/FLP 错误日志: /n/IWFND/ERROR_LOG 服务清单: /n/IWFND/MAINT_SERVICE 创建语义对象:/N/UI2/SEMOBJ 创建目录:/N/UI2/FLPD_CONF(cross-client)或 /N/UI2…

【ES】笔记-箭头函数的实践于应用场景

箭头函数的实践于应用场景 需求-1 点击 div 2s后颜色变成[粉色]从数组中返回偶数的元素 需求-1 点击 div 2s后颜色变成[粉色] html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport…

uniapp之当你问起“tab方法触发时eventchange也跟着触发了咋办”时

我相信没有大佬会在这个问题上卡两个小时吧&#xff0c;记下来大家就当看个乐子了。 当时问题就是&#xff0c;点击tab头切换的时候&#xff0c;作为tab滑动事件的eventchange同时触发了&#xff0c;使得接口请求了两次 大概是没睡好&#xff0c;我当时脑子老想着怎么阻止它冒…

怎么系统的学习机器学习、深度学习?当然是看书了

目录 前言 内容简介 学完本书&#xff0c;你将能够 作者简介 本书目录 京东自购链接 前言 近年来&#xff0c;机器学习方法凭借其理解海量数据和自主决策的能力&#xff0c;已在医疗保健、 机器人、生物学、物理学、大众消费和互联网服务等行业得到了广泛的应用。自从Ale…

【GPT-3 】创建能写博客的AI工具

一、说明 如何使用OpenAI API&#xff0c;GPT-3和Python创建AI博客写作工具。 在本教程中&#xff0c;我们将从 OpenAI API 中断的地方继续&#xff0c;并创建我们自己的 AI 版权工具&#xff0c;我们可以使用它使用 GPT-3 人工智能 &#xff08;AI&#xff09; API 创建独特的…