文章目录

  • ​​1.JDK、JRE、JVM关系​​
  • ​​2.JAVA程序的运行(为什么java可以跨平台)​​
  • ​​3.JVM运行时数据区​​
  • ​​程序计数器(Program Counter Register):​​
  • ​​Java虚拟机栈(Java Virtual Machine Stacks)​​
  • ​​本地方法栈(Native Method Stack)​​
  • ​​堆(Heap)​​
  • ​​方法区(Method Area)​​



1.JDK、JRE、JVM关系

JVM内存模型详解(1.7与1.8的区别)_方法区


从图中就可以很清晰的看清他们之间的关系:JDK>JRE>JVM

2.JAVA程序的运行(为什么java可以跨平台)

比如我们编写一个HelloWord.java ,他是如何运行的呢

JVM内存模型详解(1.7与1.8的区别)_java虚拟机_02


因为有JVM,所以我们在不同平台只需要下载对应的JDK即可

3.JVM运行时数据区


JVM内存模型详解(1.7与1.8的区别)_java虚拟机_03

JVM内存模型详解(1.7与1.8的区别)_方法区_04

其中对于方法区,很多人更愿意称为:“永久代(Permanent Generation)”,不过本质上两者并不等价,仅仅是因为习惯使用HotSpot虚拟机的设计团队选择吧GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存,能够省去专门为方法区变编写内存管理代码的工作。不过对于其他虚拟机(如BEA JRockit、IBM J9等)来说并不存在永久代的概念

这是jdk1.8之前的内存模型,其中方法区和堆是是线程共享的,但是在jdk1.8之后

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

JVM内存模型详解(1.7与1.8的区别)_JVM_05

程序计数器(Program Counter Register):

  它是一块较小的内存空间,可以看做是指向当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程回复等基础功能都需要依赖计数器来完成()。

为什么程序计数器为线程私有呢?

 由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令,因此,为了线程切换后能恢复到正常的执行位置,每条线程都需要一个独立的程序计数器,各线程之间计数器互不影响,独立存储,为线程私有的内存。

程序计数器值的问题

 如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器的值则为空(Undefined)。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

Java虚拟机栈(Java Virtual Machine Stacks)

 java虚拟机栈与程序计数器一样,也是线程私有的,他的生命周期和线程保持一致。他是存储当前线程运行方法时所需要的数据、指令、返回地址。在每个方法执行时,虚拟机栈都会创建一个栈帧(Stack Frame),用于存储:局部变量表、操作数栈、动态链接、方法出口等信息。如下图:

JVM内存模型详解(1.7与1.8的区别)_方法区_06

 其中局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同与对象本身,可能是一个指向对象其实地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置) 和returnAddress类型(指向了一条字节码指令的地址)

 局部变量表的存储空间是32位,刚好可以放一个int类型,所以长度为64为的long和double类型的数据会占用2个局部变量空间(Slot),局部变量表的大小在编译器就已经确定了

 在java虚拟机规范中,对java虚拟机栈规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分虚拟机都可以动态扩展,只不过ava虚拟机规范中也允许固定长度的虚拟机栈),扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

本地方法栈(Native Method Stack)

本地方法栈和虚拟机栈所发挥的作用非常相似,区别就是:

  • java虚拟机栈为虚拟机执行java方法(也就是字节码)服务
  • 本地方法栈为虚拟机使用到的Native方法服务

堆(Heap)

JVM内存模型详解(1.7与1.8的区别)_JVM_07


 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor,这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。java堆是java虚拟机管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,堆的唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。

 java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们磁盘空间一样。(不过在实现中既可以大小固定,也可以是可扩展,通过-Xmx 和-Xms控制),如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

方法区(Method Area)

  方法区和堆一样,是各个线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等
ps:方法区中还包括运行时常量池(Runtime Constant Pool),Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Tabel),用于存放编译期生成的各种字面量常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量中存放,当常量池无法再申请到内存时也会抛出OutOfMemoryError异常



文章目录

  • ​​1.JDK、JRE、JVM关系​​
  • ​​2.JAVA程序的运行(为什么java可以跨平台)​​
  • ​​3.JVM运行时数据区​​
  • ​​程序计数器(Program Counter Register):​​
  • ​​Java虚拟机栈(Java Virtual Machine Stacks)​​
  • ​​本地方法栈(Native Method Stack)​​
  • ​​堆(Heap)​​
  • ​​方法区(Method Area)​​


1.JDK、JRE、JVM关系

JVM内存模型详解(1.7与1.8的区别)_方法区


从图中就可以很清晰的看清他们之间的关系:JDK>JRE>JVM

2.JAVA程序的运行(为什么java可以跨平台)

比如我们编写一个HelloWord.java ,他是如何运行的呢

JVM内存模型详解(1.7与1.8的区别)_java虚拟机_02


因为有JVM,所以我们在不同平台只需要下载对应的JDK即可

3.JVM运行时数据区


JVM内存模型详解(1.7与1.8的区别)_java虚拟机_03

JVM内存模型详解(1.7与1.8的区别)_方法区_04

其中对于方法区,很多人更愿意称为:“永久代(Permanent Generation)”,不过本质上两者并不等价,仅仅是因为习惯使用HotSpot虚拟机的设计团队选择吧GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存,能够省去专门为方法区变编写内存管理代码的工作。不过对于其他虚拟机(如BEA JRockit、IBM J9等)来说并不存在永久代的概念

这是jdk1.8之前的内存模型,其中方法区和堆是是线程共享的,但是在jdk1.8之后

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

JVM内存模型详解(1.7与1.8的区别)_JVM_05

程序计数器(Program Counter Register):

  它是一块较小的内存空间,可以看做是指向当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程回复等基础功能都需要依赖计数器来完成()。

为什么程序计数器为线程私有呢?

 由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令,因此,为了线程切换后能恢复到正常的执行位置,每条线程都需要一个独立的程序计数器,各线程之间计数器互不影响,独立存储,为线程私有的内存。

程序计数器值的问题

 如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器的值则为空(Undefined)。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

Java虚拟机栈(Java Virtual Machine Stacks)

 java虚拟机栈与程序计数器一样,也是线程私有的,他的生命周期和线程保持一致。他是存储当前线程运行方法时所需要的数据、指令、返回地址。在每个方法执行时,虚拟机栈都会创建一个栈帧(Stack Frame),用于存储:局部变量表、操作数栈、动态链接、方法出口等信息。如下图:

JVM内存模型详解(1.7与1.8的区别)_方法区_06

 其中局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同与对象本身,可能是一个指向对象其实地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置) 和returnAddress类型(指向了一条字节码指令的地址)

 局部变量表的存储空间是32位,刚好可以放一个int类型,所以长度为64为的long和double类型的数据会占用2个局部变量空间(Slot),局部变量表的大小在编译器就已经确定了

 在java虚拟机规范中,对java虚拟机栈规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分虚拟机都可以动态扩展,只不过ava虚拟机规范中也允许固定长度的虚拟机栈),扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

本地方法栈(Native Method Stack)

本地方法栈和虚拟机栈所发挥的作用非常相似,区别就是:

  • java虚拟机栈为虚拟机执行java方法(也就是字节码)服务
  • 本地方法栈为虚拟机使用到的Native方法服务

堆(Heap)

JVM内存模型详解(1.7与1.8的区别)_JVM_07


 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor,这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。java堆是java虚拟机管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,堆的唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。

 java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们磁盘空间一样。(不过在实现中既可以大小固定,也可以是可扩展,通过-Xmx 和-Xms控制),如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

方法区(Method Area)

  方法区和堆一样,是各个线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等
ps:方法区中还包括运行时常量池(Runtime Constant Pool),Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Tabel),用于存放编译期生成的各种字面量常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量中存放,当常量池无法再申请到内存时也会抛出OutOfMemoryError异常