当前位置:网站首页 > 数据科学与大数据 > 正文

jvm内存模型和运行时数据区(jvm内存模型和jvm内存结构)



JVM运行时数据区、JVM内存结构、JVM内存模型都是指Java虚拟机的内存空间划分,主要分为5个部分:

注意:JVM内存模型与Java内存模型有严格不同,Java内存模型是与多线程编程相关。

在这里插入图片描述

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,统称运行时数据区域。

在类加载过程中的装载阶段:将Calss文件对应字节流所代表的静态存储结构转化为方法区的运行时数据结构,在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

也就是说:类文件被类装载器装载进来之后,类中的内容,如变量,常量,方法,对象等数据得需要存储在JVM中对应的空间中,该空间就是运行时数据区。

JDK8同JDK7比,最大差别是:元数据区取代了永久代。元空间Metaspace本质和永久代Perm Space类似,都是对JVM 规范中方法区的实现。不过元空间与永久代之间最大区别在于:元数据空间并不在虚拟机中,而是使用本地内存。

运行时数据区分为2类:

线程私有的:

 

线程共享的:

 

常量池分为静态常量池,运行时常量池,还有字符串常量池。

储存的就是字面量以及符号引用

字符串常量池是JVM为了提升性能和减少内存消耗针对字符串(String类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

JDK7之前,字符串常量池存放在永久代。JDK7字符串常量池和静态变量从永久代移动到Java堆中。

这是因为永久代的GC回收效率太低,只有在Full GC的时候才会被执行回收。Java程序中通常会有大量被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。

 

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量和符号引用的常量池表

 

运行时常量池的功能类似编程语言的符号表,也是方法区的一部分,常量池无法再申请到内存时会抛出OutOfMemoryError错误。

方法区属于是JVM运行时数据区域的一块逻辑区域,是各个线程共享的内存区域,在虚拟机启动时创建

Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来

当虚拟机要使用一个类时,它需要读取并解析Class文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

永久代以及元空间是HotSpot虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是JDK8之前的方法区实现,JDK8及以后方法区的实现变成了元空间。最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存

永久代有一个JVM本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。

元空间里面存放的是类的元数据,加载多少类的元数据就不由MaxPermSize控制, 而由系统的实际可用空间来控制,这样能加载的类就更多。

JDK8之前

 

JDK8

 

Java堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享

堆内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存

JDK7的堆内存模型

JDK7的堆内存由:。

Young年轻区(代)

Young区被划分为三部分: Eden区、两个大小严格相同的Survivor区(S0+S1),S0和S1一样大,也叫From和To

S0和S1某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。

Tenured/Old年老区

Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。

Perm永久区

Perm代主要保存class、method、filed对象,这部份空间一般不会溢出,除非一次性加载很多的类

Virtual区

最大内存和初始内存的差值,就是Virtual区。

JDK8的堆内存模型

JDK8的内存模型组成:

 

在这里插入图片描述
JDK7与JDK8的区别:

最大的Perm区,用Metaspace元数据空间进行替换。Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间。

移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。

由于永久代内存经常不够用或发生内存泄露,爆出异常,基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。

Survivor区是为了:减少老年代对象的产生,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次Minor GC(回收新生代)还能在新生代中存活的对象,才会被移动到老年代。

如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会移动到老年代

当老年代满了后,触发Major GC(回收老年代),注意:Major GC一般伴随着Minor GC,也可以看做触发了Full GC

老年代内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,会影响大型程序的执行和响应速度。

假如增加老年代空间,更多存活对象才能填满老年代。虽然降低Full GC频率,但是随着老年代空间加大,一旦发生Full GC,执行所需要的时间更长

假如减少老年代空间,虽然GC所需时间减少,但是老年代很快被存活对象填满,Full GC频率增加。

S0与S1区的出现是为了解决碎片化

如果只有一个Survivor区,新建对象在Eden中,一旦Eden满了,触发Minor GC,Eden中存活对象移动到Survivor区。如此循环,当下一次Eden区进行Minor GC时,Eden和Survivor各有一些存活对象,此时把Eden区的存活对象放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化的产生。

新生代中Eden:S1:S2的比例默认是8:1:1

 

堆内存中都是线程共享的区域吗?

 

随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么绝对。从JDK7开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(未逃逸出去),那么对象可以直接在栈上分配内存。

新创建的对象都会被分配到Eden区,一些特殊的大对象会直接分配到Old区。当Eden区空间不足GC后进入Survivor区的From区,或者进入Survivor区的To区,来回切换。通常直到GC回收18次后进入Old区。
在这里插入图片描述

大部分情况,对象都会首先在Eden区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入S0或者S1,并且对象的年龄还会加1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 设置。

当创建大量对象时,堆空间不足,则会堆内存溢出

设置堆大小

 
 
 

向方法区中大量添加Class的信息,当方法区内存不足,则方法区内存溢出

设置Metaspace的大小

 

可以借助ASM模拟方法区内存溢出。ASM是一个 Java字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。

Stack Space用来做方法的递归调用时压入栈帧(Stack Frame),当递归调用深度太深,就可能耗尽Stack Space,爆出StackOverflow的错误。

JDK5以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。

Stack Space是根据应用线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,在3000~5000左右较为合理。

线程栈设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

设置每个线程的堆栈大小

 
 
 

虚拟机栈是在JVM运行过程中存储当前线程运行方法所需的数据,指令、返回地址等信息。使用先进后出(FILO)的数据结构

虚拟机栈是基于线程的,是以线程方式运行,同时也是一个线程执行的区域,保存着一个线程中方法的调用状态。

一个Java线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈是线程私有的,独有的,随着线程的创建而创建。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。

每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。调用一个方法,就会向栈中压入一个栈帧,一个方法调用完成,就会把该栈帧从栈中弹出。

大小限制:缺省为,可用参数 –Xss 调整大小,如

官方文档(JDK1.8):

 

如上a、b、c3个方法,a栈帧入栈,接着b栈帧入栈,接着c栈帧入栈;然后c栈帧出栈,b栈帧出栈,直到a栈帧出栈

在这里插入图片描述

在每个Java方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。

栈由一个个栈帧组成,每个栈帧大体都包含四个区域:局部变量表、操作数栈、动态连接、返回地址

数据结构上与栈类似,两者都是先进后出的数据结构,只支持出栈和入栈两种操作。
在这里插入图片描述

方法中定义的局部变量以及方法的参数存放在局部变量表中

它是一个32位的长度,主要存放基础数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型

它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置。如果是局部的一些对象,如Object对象,则只需要存放它的一个引用地址即可。

局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数

以压栈和出栈的方式存储操作数。主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中。

动态链接主要服务一个方法需要调用其他方法的场景。每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。

在Java源文件被编译成字节码文件时,所有变量和方法引用都作为符号引用(Symbolic Reference)保存在Class文件的常量池里。

当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用。

当一个方法开始执行后,只有两种方式可以退出:一种是遇到方法返回的字节码指令,如return语句。一种是遇见异常,并且这个异常没有在方法体内得到处理。

不管哪种返回方式,都会导致栈帧被弹出。也就是说:栈帧随着方法调用而创建,随着方法结束而销毁。无论方法正常完成还是异常完成都算作方法结束。

程序运行中栈可能会出现两种错误:

 

一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。

假如线程A正在执行到某个地方,突然失去CPU的执行权,切换到线程B,然后当线程A再获得CPU执行权的时候,怎么能继续执行?这时候就需要在线程中维护一个变量,记录线程执行到的位置,它就是程序计数器。

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。

为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。

程序计数器主要有两个作用:

 

注意:

 

本地方法栈是为JVM运行Native方法准备的空间,由于很多Native方法都是用C语言实现的,所以它通常又叫C栈

与虚拟机栈的区别:

 

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

在JDK1.4 中新加入了NIO类,引入了一种基于通道Channel与缓冲区Buffer的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java 堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。

因为避免了在Java堆和Native堆中来回复制数据,在一些场景中能显著提高性能。

本机直接内存的分配不会受到Java堆大小的限制,但是还是会受到本机总内存的大小及处理器寻址空间的限制。

直接内存与堆内存比较

 

Code Cache:JVM本身是个本地程序,还需要其他的内存去完成各种基本任务,如JIT编译器在运行时对热点方法进行编译,就会将编译后的方法储存在Code Cache里面

GC功能需要运行在本地线程中,类似部分都需要占用内存空间。

到此这篇jvm内存模型和运行时数据区(jvm内存模型和jvm内存结构)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • 单向链表和双向链表的数据结构(单链表和双向链表的区别)2024-12-17 17:18:06
  • 自动驾驶数据服务(自动驾驶数据服务是什么)2024-12-17 17:18:06
  • 自动驾驶数据集有哪些(自动驾驶 数据集)2024-12-17 17:18:06
  • 小米手机数据迁移(小米手机数据迁移到真我)2024-12-17 17:18:06
  • 大数据培训(大数据培训心得)2024-12-17 17:18:06
  • 学术数据库有哪些(国内专业学术数据库)2024-12-17 17:18:06
  • 数据库培训视频(数据库基础培训视频教程)2024-12-17 17:18:06
  • 3dtile数据(3dtile数据里面有哪些)2024-12-17 17:18:06
  • pymysql查询出来的数据格式(pymysql 查询数据)2024-12-17 17:18:06
  • linux 连接redis数据库(linux连接redis集群)2024-12-17 17:18:06
  • 全屏图片