JVM内存模型深度解读

        JVM(Java Virtual Machine,Java虚拟机)对于Java开发者和运行 Java 应用程序而言至关重要。其重要性主要体现在跨平台性、内存管理和垃圾回收、性能优化、安全性和稳定性、故障排查与性能调优等方面。今天就下学习一下 JVM 的内存模型。

一、JVM内存模型

        JVM 内存模型(Java Virtual Machine Memory Model,简称JMM)是 Java 虚拟机规范定义的一种内存结构组织方式,用于规定 Java 程序在 JVM 中的运行时数据区域划分以及它们之间的关系。它不仅描述了如何划分内存区域,还规定了线程如何访问这些内存区域以及线程间的通信规则。JMM 如下图:

        JVM内存模型主要分为程序计数器、Java虚拟机栈、本地方法栈、堆、方法区、运行时常量池、直接内存,下面来分别看下。

二、程序计数器

         程序计数器(Program counter Register)是一块较小的内存空间,它可以看做是当前线程所执行字节码的指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条要执行的字节码指令,它是程序流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个计数器来完成。每条线程独立拥有程序计数器,各线程之间计数器互不影响。

        如果线程正在执行 java 方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是本地(native)方法,这个计数器值则应为空。程序计数器是唯一一个在《Java虚拟机规范》中没有规定任何 OutOfMemeryError 情况的区域。

 三、 java虚拟机

        与程序计数器一样,java 虚拟机栈也是线程私有的,他的生命周期与线程一样。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法的出口等信息。每一个方法在虚拟机栈中的执行完毕过程,就对应这一个栈帧从入栈到出栈的过程。

        局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(byte、short、char、int 、float、double、long、boolean)、对象引用(refrence类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象句柄或者其他与此对象相关的位置)和returnAddress类型。

        下面看一个例子

public class Test {

    public static int cacl() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }

    public static void main(String[] args) {
        int cacl = cacl();
        System.out.println(cacl);
    }

}

        局部变量表是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。

        操作数栈是一个先进后出的栈,当一个方法刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种各样的字节码指令往操作数栈中写入和提取,也就是出栈和入栈。

        动态链接,每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。Class文件中的常量池存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。

四、 本地方法栈

        本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用非常相似,其区别只是虚拟机栈为虚拟机执行 java 方法服务,而本地方法栈则是为虚拟机使用本地方法服务。

五、 堆

        Java 堆(Java Heap)是虚拟机所管理的内存最大的一块。java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的是存放对象实例。

        new 出来的对象是放到了 Eden 区,老年代默认占整个堆的 2/3 空间。JVM的分代理论中什么条件下会从新生代晋升到老年代?

  1. 默认情况下,对象经历了15次 minor gc,年龄变为15就会变为老年代。
  2. 假如说当前放对象的Survivor 区域里一批对象的总大小大于了这块 Survivor 区域的内存大小的50% ,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了。
  3. 一些大对象在创建是也会放到老年代。老年代的包括一些链接,比如连接池等。

六、 方法区

        方法区(Method Area)与 java 堆一样是线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态常量、即时编译器编译后的代码缓存等数据。方法区是JVM的一个逻辑部分,具体实现在java7之前是永久代,Java8之后是元空间。

        那元空间和永久代有什么区别呢?

        JDK8 及以后把类的元数据放在本地内存中,这一块区域叫做 Metaspace,该区域在 jdk7 及以前是属于永久代的,元空间和永久代都是用来存储 class 相关信息,包括 class 对象的method、field 等。元空间和永久代其实都是方法区的实现,只是实现不同,所以说方法区只是JVM的规范。

        当前的主流框架,如 spring 等对类进行增强时都会使用 CgLib 等字节码技术,当增强的类越多,就需要越大的方法区,以保证动态生成的新类能载入内存。经常在运行时生成大量动态类的应用场景,应特别注意这些类的回收情况。

        在 java7 之后,原先位于方法区里的字符串常量池也被移到了堆中。并在 Java8 中使用元空间代替了永久代。这一替代最大的区别是元空间使用的是本地内存,而永久代使用的是 jvm 内存,使用本地内存的好处是J ava.lang.outofMemoryError:PermGen Space 将不复存在,只受限于本地内存大小的限制,解决了空间不足的问题,不过也不可能任其无限大,jvm在默认运行时会根据需要动态的设置其大小,这一替换的好处如下:

  • 字符串常量池在永久代中容易出现性能问题和内存溢出问题。
  • 类的方法信息大小难以确定,因此给永久代的大小指定带来了困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。永久代会为 GC 带来不必要的复杂性,并且回收效率偏低,在永久代中元数据可能会随着每一次 full gc 发生而进行移动,而 hotspot 虚拟机每种类型的垃圾回收器都要特殊处理永久代中的元数据,分离出来后可以简化 full gc。

        如下为永久代合堆的存储位置

七、 运行时常量池

        运行时常量池(Runtime Constant Pool)是方法区的一部分。class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项常量池,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池。

        运行期间也可以将新的常量放入池中,比如String的intern()方法

八、 直接内存

        直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁的使用,而且也可能导致OutOfMemoryError异常的出现。

        在 JDK1.4 中新加入的 NIO 类,引入了一种基于通道(channel)与缓存区(Buffer)的 I/O方式,它可以使用 Native 函数库直接分配内存,然后通过一个存储在 Java 堆里面的DirecByBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提升性能,因为避免了在 java 堆和 native 堆中来回复制数据。

        显然,本地直接内存分配不受 java 堆大小的限制,但是既然是内存,则肯定还是会受到本机总内存大小及处理器寻址空间的限制。

        因为通常的垃圾收集日志等记录,并不包含 Direct Buffer 等信息,所以 Direct Buffer 内存诊断也是个比较头疼的事情,在JDK 8 之后的版本,可以方便的使用 Native Memory Tracking(NMT) 特性来诊断,可以在程序启动时增加下面的参数:

-XX:NativeMemoryTracking={summary|detail}

        注意,激活NMT通常会导致JVM 5%~10%的性能下降,需要慎重使用。

        总结:了解了JVM的内存模型后才能更好的理解对象创建、垃圾回收等等功能,后续将继续介绍这部分内容。

往期经典推荐

实时数据传输的新里程——Server-Sent Events(SSE)消息推送技术-CSDN博客

SpringBoot项目并发处理大揭秘,你知道它到底能应对多少请求洪峰?_springboot并发处理-CSDN博客

领航分布式消息系统:一起探索Apache Kafka的核心术语及其应用场景-CSDN博客

Redis性能大挑战:深入剖析缓存抖动现象及有效应对的战术指南_redis 缓存抖动怎么解决-CSDN博客

MySQL中order by原来是这么工作的-CSDN博客

  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
线程的状态以及各状态之间的转换详解.mp4 线程的初始化,中断以及其源码讲解.mp4 多种创建线程的方式案例演示(一)带返回值的方式.mp4 多种创建线程的方式案例演示(二)使用线程池.mp4 Spring对并发的支持:Spring的异步任务.mp4 使用jdk8提供的lambda进行并行计算.mp4 了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的原理(理论层面).mp4 synchronized保证线程安全的原理(jvm层面).mp4 单例问题与线程安全性深入解析.mp4 理解自旋锁,死锁与重入锁.mp4 深入理解volatile原理与使用.mp4 JDK5提供的原子类的操作以及实现原理.mp4 Lock接口认识与使用.mp4 手动实现一个可重入锁.mp4 AbstractQueuedSynchronizer(AQS)详解.mp4 使用AQS重写自己的锁.mp4 重入锁原理与演示.mp4 读写锁认识与原理.mp4 细读ReentrantReadWriteLock源码.mp4 ReentrantReadWriteLock锁降级详解.mp4 线程安全性问题简单总结.mp4 线程之间的通信之wait notify.mp4 通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及原理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与实现原理剖析.mp4 ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析.mp4 提前完成任务之FutureTask使用.mp4 Future设计模式实现(实现类似于JDK提供的Future).mp4 Future源码解读.mp4 ForkJoin框架详解.mp4 同步容器与并发容器.mp4 并发容器CopyOnWriteArrayList原理与使用.mp4 并发容器ConcurrentLinkedQueue原理与使用.mp4 Java中的阻塞队列原理与使用.mp4 实战:简单实现消息队列.mp4 并发容器ConcurrentHashMap原理与使用.mp4 线程池的原理与使用.mp4 Executor框架详解.mp4 实战:简易web服务器(一).mp4 实战:简易web服务器(二).mp4 JDK8的新增原子操作类LongAddr原理与使用.mp4 JDK8新增锁StampedLock详解.mp4 重排序问题.mp4 happens-before简单概述.mp4 锁的内存语义.mp4 volatile内存语义.mp4 final域的内存语义.mp4 实战:问题定位.mp4
精通并发与netty视频教程(2018)视频教程 netty视频教程 Java视频教程目录: 1_学习的要义 2_Netty宏观理解 3_Netty课程大纲深度解读 4_项目环境搭建与Gradle配置 5_Netty执行流程分析与重要组件介绍 6_Netty回调与Channel执行流程分析 7_Netty的Socket编程详解 8_Netty多客户端连接与通信 9_Netty读写检测机制与长连接要素 10_Netty对WebSocket的支援 11_Netty实现服务器端与客户端的长连接通信 12_Google Protobuf详解 13_定义Protobuf文件及消息详解 14_Protobuf完整实例详解 15_Protobuf集成Netty与多协议消息传递 16_Protobuf多协议消息支援与工程最佳实践 17_Protobuf使用最佳实践与Apache Thrift介绍 18_Apache Thrift应用详解与实例剖析 19_Apache Thrift原理与架构解析 20_通过Apache Thrift实现Java与Python的RPC调用 21_gRPC深入详解 22_gRPC实践 23_Gradle Wrapper在Gradle项目构建中的最佳实践 24_gRPC整合Gradle与代码生成 25_gRPC通信示例与JVM回调钩子 26_gRPC服务器流式调用实现 27_gRPC双向流式数据通信详解 28_gRPC与Gradle流畅整合及问题解决的完整过程与思考 29_Gradle插件问题解决方案与Nodejs环境搭建 30_通过gRPC实现Java与Nodejs异构平台的RPC调用 31_gRPC在Nodejs领域中的静态代码生成及与Java之间的RPC通信 32_IO体系架构系统回顾与装饰模式的具体应用 33_Java NIO深入详解与体系分析 34_Buffer中各重要状态属性的含义与关系图解 35_Java NIO核心类源码解读与分析 36_文件通道用法详解 37_Buffer深入详解 38_NIO堆外内存与零拷贝深入讲解 39_NIO中Scattering与Gathering深度解析 40_Selector源码深入分析 41_NIO网络访问模式分析 42_NIO网络编程实例剖析 43_NIO网络编程深度解析 44_NIO网络客户端编写详解 45_深入探索Java字符集编解码 46_字符集编解码全方位解析 47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO与Netty之间的关联关系分析 49_零拷贝深入剖析及用户空间与内核空间切换方式 50_零拷贝实例深度剖析 51_NIO零拷贝彻底分析与Gather操作在零拷贝中的作用详解 52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址绑定底层源码分析 57_Reactor模式透彻理解及其在Netty中的应用 58_Reactor模式与Netty之间的关系详解 59_Acceptor与Dispatcher角色分析 60_Netty的自适应缓冲区分配策略与堆外内存创建方式 61_Reactor模式5大角色彻底分析 62_Reactor模式组件调用关系全景分析 63_Reactor模式与Netty组件对比及Acceptor组件的作用分析 64_Channel与ChannelPipeline关联关系及模式运用 65_ChannelPipeline创建时机与高级拦截过滤器模式的运用 66_Netty常量池实现及ChannelOption与Attribute作用分析 67_Channel与ChannelHandler及ChannelHandlerContext之间的关系分析 68_Netty核心四大组件关系与构建方式深度解读 69_Netty初始化流程总结及Channel与ChannelHandlerContext作用域分析 70_Channel注册流程深度解读 71_Channel选择器工厂与轮询算法及注册底层实现 72_Netty线程模型深度解读与架构设计原则 73_Netty底层架构系统总结与应用实践 74_Netty对于异步读写操作的架构思想与观察者模式的重要应用 75_适配器模式与模板方法模式在入站处理器中的应用 76_Netty项目开发过程中常见且重要事项分析 77_Java NIO Buffer总结回顾与难点拓展 78_Netty数据容器ByteBuf底层数据结构深度剖析 79_Netty
JVM(Java虚拟机)模型是指Java程序在运行时的执行环境,包括JVM的组成部分和它们的工作原理。JVM内存模型是指Java虚拟机管理程序运行时内存的方式,包括内存划分、内存分配和对象回收等机制。 JVM模型的组成部分包括类加载器、解释器、即时编译器、垃圾收集器等。类加载器负责将Java类加载到JVM中,并将其转换为可执行代码;解释器负责解释字节码并执行相应的指令;即时编译器则将频繁执行的代码编译成本地机器码,以提高程序的执行效率;垃圾收集器则负责回收程序运行时不再使用的内存JVM内存模型规定了Java程序运行时内存的分配和管理方式。JVM内存模型内存划分为不同的区域,包括方法区、堆、虚拟机栈、本地方法栈和程序计数器。其中,堆是Java程序运行时内存中最大的区域,用于存储对象实例。虚拟机栈和本地方法栈则用于存储程序执行时的局部变量和方法调用信息。方法区用于存储已加载的类信息、常量池、静态变量等数据。程序计数器则用于记录当前线程所执行的字节码指令位置。 JVM内存模型还包括垃圾回收机制,用于自动回收程序运行时不再使用的内存。垃圾回收机制采用标记-清除、复制、标记-整理等不同的算法来回收内存。 总之,JVM模型JVM内存模型是Java程序运行时的重要组成部分,了解和掌握它们的工作原理对于编写高效、稳定的Java程序至关重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超越不平凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值