jvm之java内存区域(一)

简介: jvm之java内存区域(一)
一、概述

用c或者c++写过算法的人都该知道,对于内存管理区域,需要手动设置和管理,即拥有每个对象的所有权,但也背负着每个对象生命的开始和结束。但是在java中,就不需要这么复杂的操作了,在虚拟机自动内存的管理机制下,不需要特意的去管理对象的 ‘生’ 和 ‘死’ 了,也因虚拟机的存在,不容易出现内存泄露和溢出问题。但是正是因为虚拟机帮java程序员解决了内存管理的问题,因此需要去学习 jvm 是如何操作的,这样一旦在出现内存泄露的问题的时候,将可以快速的排查错误和解决问题

二、运行时数据区

如上图所示,java虚拟机在执行java程序的过程中会将它管理的内存划分为若干个不同的数据区域,不同的区域有着不同的用途,根据《java虚拟机规范》的规定,java虚拟机所管理的内存包括上图上面的几个运行时数据区域

1,程序计数器

程序计数器:Program Counter Register,指的是一块较小的内存空间,可以当做是当前线程所执行的字节码的行号暗示器,主要是通过改变这个计数器的值选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都是需要这个计数器来完成。一个线程只会执行一条程序中的命令,每条线程都会有一个独立的程序计数器。


2,java虚拟机栈

属于线程私有,即它的生命周期与线程的相同。虚拟机栈描述的是java方法执行的内存模型:每个方法在被执行的时候,都会创建一个栈帧,用于存储局部变量表,操作数栈,动态连接,方法出口等。主要用于存放局部变量,如java虚拟机的基本数据类型和对象应用类型。局部变量表所需的空间在编译期间完成分配,因此在进入一个方法时,这个方法所需要的栈帧是确定的,在方法运行期间不会改变局部变量表的大小

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果java虚拟机可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError


3,本地方法栈

本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别是虚拟机栈为虚拟机执行的java方法服务,而本地方法则是为虚拟机使用到本地方法服务,与虚拟机栈一样,当栈扩展失败或者栈深度溢出时分别会抛出OutOfMemoryError异常和StackOverflowError异常


4,java堆

java堆是被所有线程共享的一块区域,在虚拟机启动时创建,此区域的唯一目的是存放对象的实例,在java中,“几乎” 所有的实例都是这分配内存。如 A a = new A(),new A()存储的地点就是在堆里面的,而a是存储在栈里面的。

根据《java虚拟机规范》的规定,java堆可以处于物理上不连续的内存空间,但在逻辑上他应该是被视为连续的,这点就像磁盘空间去存储文件一样,并不要求每个文件都连续存放。

java堆也可以根据参数(-Xmx和-Xms)来实现扩展,如果在java堆中没有内存完成实例分配,并且对也无法在进行扩展时,java虚拟机将会抛出OutOfMemoryError异常


5,方法区

各个线程共享的内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

在《java虚拟机规范》中对方法区的约束是非常宽松的,除了和java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集;并根据规定,如果方法区中无法满足内存分配的需求时,将抛出OutOfMemoryError异常


6,运行时常量池

该常量池也属于方法区的一部分,用于存放编译器生成的各种字面量与符号应用,这部分内容将在类加载后存放到方法区的运行常量池中,如一些静态的变量或者属性。因此在static 的变量或者属性中,这些都是随着类的加载而加载的,并且这些属性或者变量即存在常量池中,所以有一些很坑的面试题,问你 String str = new String(“abc”) 该对象可能创建了几次,因此就是可能在静态常量池中创建了一次,又在堆中创建了一次,或者只在堆中创建了一次,所以答案是一次或者两次,这取决于在常量池中是否创建过。

运行时常量池相对于Class文件常量池的另外一个重要的特征就是具备动态性,java语言并不要求一定只有在编译期才能产生,也就是说,运行期间也能将新的常量放入到常量池中。常量池属于方法区的一部分,所以如果常量池中无法满足内存分配的需求时,将抛出OutOfMemoryError异常

三,对象的创建

java是一门面向对象的语言,java程序中无时无刻都有程序被创建出来。当java虚拟机在遇到一条字节码为new的指令的时候,首先会去检查这个指令的参数是否能在常量池中去定位到一个类的符号引用,并检查这个符号引用代表的类是否被加载、解析和初始化,如果没有,则必须执行相应的类加载过程。


在类加载通过之后,接下来虚拟机将会为新生对象分配内存,对象所需的内存大小在类加载之后便可完全确定,为对象分配空间的任务实际上就是将一块确定大小的内存从java堆中划分出来。主要方式有两种,分别是 指针碰撞 和 空闲列表


指针碰撞:假设堆中内存是绝对规整的,所有使用过的内存都被放在一边,空闲的放在另外一边,之间放着一个指针作为分界点的指示器,分配内存的方式就是把那个指针向空闲的一方挪动一段与对象大小相等的距离

空闲列表:假设堆中内存并不规整,那么虚拟机需要维护一张表,记录哪些内存是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表的记录


区别:选择哪种方式由java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否带有 空间压缩整理 的能力决定


当然以上是在单线程的情况下操作,如果在并发情况下,线程也是不安全的,解决这个问题有两种方案,一种是CAS来保证更新操作的原子性,另一种就是将内存分配的动作按照线程划分在不同的空间进行,即每个线程在java堆中预先分配内存,称为本地线程分配缓冲


在以上工作都完成之后,从虚拟机的视角来看,一个新的对象以及产生了,但是从java程序的视角来看,对象创建才刚刚开始——构造函数,即Class文件的()方法还没有开始执行。因此在java编译器遇到new关键字的地方会同时生成这两条字节码指令,new指令后会接着执行()方法,这样一个真正的可用的对象才被构建出来

四,对象的访问定位

创建对象自然就是为了后续的使用该对象,主流的方式主要有 句柄 和 直接指针 等两种方式

20210505222322283.png


图一使用的为句柄访问,即在java堆中将可能会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄包含了对象实例数据域类型数据各自具体的地址信息

图二为使用直接指针访问,Java堆中对象的内存布局就必须考虑放置访问数据类型的相关信息,reference中存储的对象地址就是直接地址,如果就是访问对象本身的话,就不需要一次间接访问的开销


优缺点:

使用句柄访问时带来的最大的好处就是reference中存储的是稳定的句柄地址,在对象被移动的时候(垃圾回收而移动)只会改变句柄中的实例数据指针,而reference本身不需要改变。但是会很废内存,增加内存的开销和访问的时间开销

使用直接指针来访问最大的好处就是速度更快,他节省了一次指针单位的时间开销和内存的开销,由于对象在java中访问非常的频繁,因此这类开销积少成多也是一项极为客观的执行成本。但是就是不稳定,随着对象的移动而需要重新定位


因此从整个软件的开发范围来看,各种语言和框架还是更偏向使用句柄访问


参考书籍:《深入理解Java虚拟机》–周志明版

相关文章
|
30天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
31 0
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
40 0
|
10天前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
1月前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
39 8
|
30天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
1月前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
1月前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
1月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
29天前
|
存储 监控 算法
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
43 0
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
356 1