JVM自动内存管理

  • 一、JAVA内存区与内存溢出
    • 1.1 概述
    • 1.2 运行时数据区
      • 1.2.1 程序计数器 (Program Counter Register)
      • 1.2.2 Java虚拟机栈(Java Virtual Machine Stack)
      • 1.2.3 本地方法栈 (Native Method Stacks)
      • 1.2.4 Java堆 (Java Heap)
      • 1.2.5 方法区(Method Area)
        • 1.2.5.1 运行时常量池(Runtime Constant Pool)
      • 1.2.6 直接内存(Direct Memory)
    • 1.3 Hot-Spot 虚拟机对象 详解
      • 1.3.1 对象的创建
      • 1.3.2 对象的内存布局
      • 1.3.3 对象的访问定位
      • 欢迎大家 收藏 点赞 每天持续更新
    • 1.4 OutOfMemoryError异常
  • 二、垃圾收集器与内存分配策略
  • 三、虚拟机性能监控、故障处理工具

一、JAVA内存区与内存溢出

1.1 概述

java程序员在JVM自动内存管理机制的条件下,不需要为每一个new的操作去写配对的delete/free代码,虽然这样可以省去很多麻烦,但是正是因为我们吧对象的创建交给JVM去管理,所以一旦出现内存泄漏和溢出的问题,如果我们对jvm了解的不深入那我们在修正这些问题是会遇到很多麻烦

1.2 运行时数据区

上一张大家都见过的图

1.2.1 程序计数器 (Program Counter Register)

  • 程序计数器是一块占用内存比较小的空间,可以看成是当前线程所执行的字节码的行号指示器(可以用电梯的指示楼层号信号灯来比喻),字节码解释器工作时就是通过改变当前程序计数器的值来选取下一条要执行的字节码指令,程序计数器是程序控制流的指示器,
    分支、循环、跳转、异常处理、线程恢复等基础功能都是依赖程序计数器来完成的。Java虚拟机的多线程是通过线程轮流切换、分配处理器的执行时间来实现的,在任意一个确定的时间内 一个处理器只会执行一个线程(多核处理器理解成为一个内核)中的指令,因此为了线程切换后能恢复到正确的执行位置,所以每个线程都有一个独立的程序计数器,每个线程的计数器互不影响,独立内存。所以也被称为线程私有的

1.2.2 Java虚拟机栈(Java Virtual Machine Stack)

  • 与程序计数器一样 Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java的方法执行的线程模型:每个方法被执行的时候都会同步的创建一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从被调用到执行结束的过程,也就是栈帧从入栈到出栈的过程
  • 很多程序员都把JVM的内存笼统的归结为 “堆内存”和“栈内存”,其实本质上JVN的内存区域比这个笼统的说法要复杂很多,这也说明了大家对 “堆内存”和“栈内存”非常感兴趣~~ 其实“”通常就是指的Java虚拟机栈,但是更多的情况下是指的Java虚拟机中的局部变量表的部分
  • 局部变量表中存放了可知的JAVA 基础数据类型 (boolean 、byte 、char、 short、int、float、long、double)、对象引用(reference类型,它可能并不等同于对象本身,可能是指向对象原始地址的引用指针等)
  • 在Java虚拟机中有两类异常情况
    • StackOverFolwError: 当线程请求的栈的深度大于虚拟机允许的最大深度时
    • OutOfMemoryError: 如果虚拟机的栈容量可以进行动态扩展,当虚拟机申请不到足够的栈内存时,会抛出异常。俗称OOM

1.2.3 本地方法栈 (Native Method Stacks)

本地方法栈和Java虚拟机栈的作用非常相似,只不过Java虚拟机栈是为JAVA方法(字节码)进行服务,而本地方法栈则是为虚拟机使用到的本地方法进行服务,值得注意的是在我们常用的Hot-Spot虚拟机直接把本地方法栈和Java虚拟机栈合二为一了,所以在Hot-Spot虚拟机中当本地方法栈无法申请到内存时,也会抛出StackOverFolwError、OutOfMemoryError这两个异常

1.2.4 Java堆 (Java Heap)

Java堆是jvm管理的最大一片内存区域,Java堆是被所有线程共享的一块内存区域,虚拟机启动时堆内存就被创建,堆内存被创建的唯一原因就是存放内存实例 在Java 中几乎所有的的内存都在这里分配内存,这里提到了几乎一词,在Java虚拟机规范中对堆的描述是:“所有对象的实例和数组都应当在堆上分配”,但是随着Java语言的发展,即时编译技术的发展等,所以java 实例都在堆上分配也就没有那么绝对了。

1.2.5 方法区(Method Area)

  • 方法区和Java堆是一样的 也是线程共享的一块内存区域它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 虽然JVM虚拟机规范中吧方法区表述为堆的一个逻辑区域,但是他有一个别名叫做非堆,目的是与Java堆区分开。
  • 这里应该提到一下永久代的概念 JDK8之前许多程序员喜欢把方法区称之为永久代,将两者混为一谈,实际上是因为当时Hot-Spot的设计团队把收集器的分代设计延伸到了方法区中,或者说用永久代的概念去实现方法区,这样就可以使用收集器 管理Java堆一样的管理方法区了,但是在JDK8之后 引入了一个名词叫做元空间来替代了之前的永久代,而之前的方法区改为了现在有本地内存来实现方法区。

1.2.5.1 运行时常量池(Runtime Constant Pool)

  • 运行时常量池也是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表用于生成编译期间生成的各种字面量和符号引用这部分的内容会再类加载之后存放到方法区的运行时常量池中
  • 运行时常量池相比较于Class文件常量池的另外一个重要的特征就是具备动态性。Java语言并不要求常量只有编译时才能产生,运行期间也可以吧新的常量放入常量池中 这里比较常用的就是String类的Intern()方法。

String类的Intern方法简介

当我们后面再次引用假设 我们后面String str4 = new String(“aaa”); 那使用 str4.intern() 时,如果常量池中存在“aaa” 那个就回直接使用常量池的“aaa”

1.2.6 直接内存(Direct Memory)

  • 直接内存并不是Java虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的一部分,但是这部分的内存也是被频繁的使用,而且也可能导致OOM异常的发生
  • 在Java1.4 中加入了NIO类引入了一种基于通道缓冲区的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过储存在Java堆里的DirectByteBuffer对象来作为这块内存的引用进行操作。这样能在一些场景中显著提高性能因为避免了Java堆和Native堆中来回复制数据

上述提到了 “一些场景”这里举例

应用程序的数据流A    app->java程序->堆内存空间->堆外直接内存空间->socket
那么在A的场景下  堆内存空间->堆外直接内存空间的复制操作  会导致GC短暂停止工作(极端条件下当IO非常大时会导致GC堵塞很久)这样会造成应用程序的卡顿应用程序的数据流B    app->java程序->堆外直接内存空间->socket
当我们使用Netty的PooledDirectByteBuf(池化的堆外直接内存)时,jvm会把堆内存数据复制到堆外直接内存这样减轻了非常多GC的工作,但是堆外内存的创建和销毁的开销较大总结:优点:使用堆外内存可以减少GC的负担,从而减少GC阻塞的卡顿,可以提高应用程序的速度缺点:直接使用堆外内存的创建和销毁开销较大,具体大多少(大家可以想办法测试一下  手动狗头)在使用时回根据实际的物理机内存区设置-Xmx 等参数信息,但是全完不能忽略直接内存的大小,如果忽略那么极有可能会导致jvm各个内存+直接内存接近或大于物理内存,从而动态的导致OOM

1.3 Hot-Spot 虚拟机对象 详解

虽然各JVM的内存区域大致相同但是实际上对象的创建、对象的布局、如何访问对象等也有区别,我们最常用的就是Hot-Spot虚拟机

所以我们接下来主要使用Hot-Spot虚拟机介绍

1.3.1 对象的创建

  • 首先我们都知道Java是一门面向对象的编程语言,Java程序在运行的过程中无时无刻都有对象被创建出来,在语言层面来说,创建对象(例外:复制、反序列化)仅仅是new关键字而已,而在虚拟机中(仅限于普通对象,不包括数组和Class对象)的创建过程我们在下面详细讲解

  • 当Java虚拟机遇到一个字节码new指令时,首先会再常量池中定位这个类引用,并且检查当前类是否已被加载、解析、初始化过,如果没有的话 首先会进行该类的类加载过程。

  • 在类加载结束之后,会对新的对象进行内存分配在这里有两种方式,

    指针碰撞的内存分配方式如下图

    空闲列表的内存分配方式如下图

思考:对象的创建是非常频繁的事情,那么再并发的情况下,也并不是线程安全的,可能A在分配内存时还没来的及修改,B又改变了指针的指向,那我们如何保存分配内存操作的原子性呢
目前有两种解决思路

  1. 在分配内存是采用CAS配上失败重试的方法
  2. 即每个线程预先分配一小块内存。当前线程创建对象进行分配内存操作是在当前的内存上分配称之为本地现线程分配缓冲(TLAB) 使用时可以通过 -XX:+/-UseTLAB 参数来设定

思考! 这里先停止阅读 我们思考一下 第二种方式在我们会联想到什么呢? 没错!线程池!
我们都知道 线程池最主要的作用是减少线程的创建/销毁的系统开销,那在JVM层面 我们联想到对象的创建时,又有什么有意思的事情发生呢
假设 系统启动时 我们的线程池 设置了30个核心线程数,那意味着 在分配内存时 使用上述第二种解决思路,也就是说 我们已经在堆内存为这30的核心线程对应的分配了30快内存区域,这些线程在创建对象时是不是能够按照第二种解决思路去创建对象呢,这样其实巧妙地避免了创建对象的CAS操作也就是 在当前线程分配的当前内存区域内时不存在创建对象的线程安全问题的!

  • 内存分配完成之后,虚拟机必须将分配到的内存空间(不包括对象头)都初始化为零值,如果使用了上述的TLAB的话 那么在TLAB分配是即将内存空间初始化为了零值,这不操作保证了对象的实例字段在java代码中不赋初始值就可以使用是的程序能访问到这些字段数据的零值。
  • 下一步 虚拟机还要对对象进行必要的设置,例如:这个对象是哪个类的实例、如何才能找到对象的元数据信息、对象的哈希码(实际上是在调用Object::hashCode()时才计算)、对象的GC分代年龄等信息。这些对象都存在于对象头中(Object Header)之中。后面会详细讲解对象头信息

Java 查看头信息

加入依赖<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.10</version></dependency>
  // 执行代码public static void main(String[] args) {System.out.println(ClassLayout.parseInstance(SendCountryExecutor.class).toPrintable());}//打印信息OFFSET  SIZE                                              TYPE DESCRIPTION                               VALUE0     4                                                   (object header)                           01 8e 43 0f (00000001 10001110 01000011 00001111) (256085505)4     4                                                   (object header)                           34 00 00 00 (00110100 00000000 00000000 00000000) (52)8     4                                                   (object header)                           df 03 00 f8 (11011111 00000011 00000000 11111000) (-134216737)12     4                     java.lang.reflect.Constructor Class.cachedConstructor                   null
  • 到目前为止,从虚拟机的角度来看,一个新的对象产生了,但是从Java程序来说才刚刚开始—构造函数,即Class文件中的()方法还没有执行,所有的字段都默认为零值。

1.3.2 对象的内存布局

  • 在Hot-Spot虚拟机中,对象的堆内存的储存布局可以化为三个部分:对象头(Object Header)、实例数据(Instance Data)和对其填充(Padding)。
  • Hot-Spot虚拟机对象的对象头部分包括两类信息 :
    1. 第一部分用于储存帝乡自身运行时的数据,如 哈希吗、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
    2. 第二部分存放指针类型,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例
    3. 第三部分是对齐填充,这个本身没有任何意义,因为HotSpot要求对象的起始地址必须为8字节的整数倍,仅此而已。

1.3.3 对象的访问定位

欢迎大家 收藏 点赞 每天持续更新

1.4 OutOfMemoryError异常

二、垃圾收集器与内存分配策略

三、虚拟机性能监控、故障处理工具

一、JAVA虚拟机------JVM自动内存管理相关推荐

  1. Java虚拟机JVM的内存管理

    Java虚拟机JVM的内存管理 关键词 一.JVM整体架构 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 名称 作用 特征 配置参数 异常 程序计数器 ...

  2. 深入理解java虚拟机-1.自动内存管理

    文章目录 1.自动内存管理 1.1 Java内存区域与内存溢出异常 1.1.1 运行时数据区域 程序计数器 程序计数器为什么是私有的? java虚拟机栈 本地方法栈 虚拟机栈和本地方法栈为什么是私有的 ...

  3. 【深入理解Java虚拟机】自动内存管理机制——垃圾回收机制

      Java与C++之间有一堵有内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来.C/C++程序员既拥有每一个对象的所有权,同时也担负着每一个对象生 ...

  4. java虚拟机的自动内存管理机制(二)

    1.内存分配: a.优先在新生代Eden区分配.Eden区没有足够的空间时,虚拟机发起一次Minor GC. (Major GC 是清理永久代.Minor GC 会清理年轻代的内存,Full GC 是 ...

  5. 解析Java对象引用与JVM自动内存管理(2)

    解析Java对象引用与JVM自动内存管理(2) 作者:杨扬 本文选自:赛迪网 2002年11月22日 Soft References 应用实例 下面以在基于web的应用程序中使用soft refere ...

  6. 第五篇:初识JVM,JVM自动内存管理

    文章目录 一.前言 1.1 计算机==>操作系统==>JVM 1.1.1 虚拟与实体(对上图的结构层次分析) 1.1.2 Java程序执行(对上图的箭头流程分析) 二.JVM内存空间与参数 ...

  7. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  8. 读书笔记——深入理解JVM(JVM自动内存管理)

    简介 本系列为<深入理解Java虚拟机-JVM高级特性与最佳实践>一书的阅读笔记. 本书开头介绍了JVM发展的历史,接着介绍了JVM是如何实现自动内存管理的. 本章节主要介绍: JVM的存 ...

  9. JVM 自动内存管理

    JVM Java 内存区域 虚拟机栈 栈帧的组成 动态分派 栈可能会引发的问题 基于栈的解释执行 本地方法栈 程序计数器 堆 分区 内存分配机制 方法区 元空间与永久区 运行时常量池 直接内存(堆外内 ...

最新文章

  1. 如何自学python到做项目-django教程如何自学
  2. 为人父母始知天下事---“宝宝哭了”的问题来说说什么是分析,什么是设计
  3. 当我们在谈深度学习时,到底在谈论什么(三)--转
  4. 硬计算、软计算与混合计算
  5. Windows 7 在资源管理器中显示软件快捷方式
  6. [HNOI2010]BOUNCE 弹飞绵羊
  7. python乱码转中文_Python中文乱码问题(转)
  8. 计算机课ppt免费,第1课 认识计算机ppt课件.ppt
  9. 大数据与云计算之间的关系是怎样的?
  10. 【CNN】——涨点模块SE,CBAM,CA对比
  11. 主数据管理方法论之主数据全生命周期管理
  12. 物联网通信技术第9章 异构网络协同通信
  13. Informatica使用操作流程--Router(由器器)、排序、序列 使用 案例6
  14. csdn的markdown编辑器如何保持图片原始大小?
  15. Request processing failed; nested exception is com.sun.jersey.api.client.UniformInterfaceException:
  16. 微信小程序云开发入门-数据库插入数据(包含批量)
  17. 创意电子学-第00课:注册Tinkercad 网站账号
  18. Python爬虫自学要多久?
  19. 根据地球上任意两点的经纬度计算两点间的距离
  20. 在Tomcat中添加支持3GP/MP4格式文件的下载

热门文章

  1. 正则表达式-手机号码验证
  2. QT-QPainter介绍
  3. python游戏开发工程师证书_【网易游戏游戏开发工程师面试】网易python开发 游戏公共支持-看准网...
  4. 走进音视频的世界——Opus编解码协议
  5. Ubuntu直接连接网络摄像头
  6. 关闭重复打开的文件夹
  7. springboot 配置JedisPool 简洁有效 复制即可运行
  8. Oracle 登录-用户操作
  9. 电脑截图如何快速识别文字
  10. SV中关键字用法学习笔记