JVM内存结构解释

java代码运行过程:

首先,java源代码文件(.java)会被java编译器(javac.exe,反编译为javap.exe)编译为二进制字节码文件(.class,相当于jvm的系列指令),接着jvm中的类加载器加载各个类的字节码文件。加载完毕后,交由java执行引擎运行,JVM指令经过解释器,转化成机器码,最后再交给CPU运行

JVM的好处:

  1. 一次编写,多次运行。跨平台,屏蔽了不同的操作系统于字节码的差异
  2. 自动内存管理,自动垃圾回收
  3. 数组下标越界检查

java内存结构:

JVM内存结构解释JVM内存结构解释

程序计数器:

作用:

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。可以记住下一条jvm指令的执行地址,解释器向程序计数器(在CPU中的寄存器)中寻找执行指令的地址
特点:

  • 由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
  • 如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。
  • 此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError情况的区域(因为程序计数器是由虚拟机内部维护的,不需要开发者进行操作)。

虚拟机栈:

特点:

每一个运行在Java虚拟机里的线程都拥有自己的线程栈。该线程栈包含了这个线程调用的方法当前执行点相关的信息。一个线程仅能访问自己的线程栈。一个线程创建的本地变量对其它线程不可见,仅自己可见。即使两个线程执行同样的代码,这两个线程任然在在自己的线程栈中的代码来创建本地变量。因此,每个线程拥有每个本地变量的独有版本,是线程私有的。每一个栈是有多个栈帧(动态链接,局部变量表,返回地址等)组成,一个栈帧为每个方法运行时需要的内存。每个方法执行结束,栈帧弹出。如果方法内的局部变量没有脱离方法的作用范围,他是线程安全的

变量的存储位置:

  • 所有原始类型的本地变量都存放在线程栈上,因此对其它线程不可见。一个线程可能向另一个线程传递一个原始类型变量的拷贝,但是它不能共享这个原始类型变量自身。
  • 一个本地变量可能是原始类型,在这种情况下,它总是“呆在”线程栈上。
  • 一个本地变量也可能是指向一个对象的一个引用。在这种情况下,引用(这个本地变量)存放在线程栈上,但是对象本身存放在堆上。
  • 一个对象可能包含方法,这些方法可能包含本地变量。这些本地变量任然存放在线程栈上,即使这些方法所属的对象存放在堆上。
  • 一个对象的成员变量可能随着这个对象自身存放在堆上。不管这个成员变量是原始类型还是引用类型。
  • 静态成员变量跟随着类定义一起也存放在堆上。

存放在堆上的对象可以被所有持有对这个对象引用的线程访问。当一个线程可以访问一个对象时,它也可以访问这个对象的成员变量。如果两个线程同时调用同一个对象上的同一个方法,它们将会都访问这个对象的成员变量,但是每一个线程都拥有这个本地变量的私有拷贝。

JVM内存结构解释

eg:

两个线程拥有一些列的本地变量。其中一个本地变量(Local Variable 2)执行堆上的一个共享对象(Object 3)。这两个线程分别拥有同一个对象的不同引用。这些引用都是本地变量,因此存放在各自线程的线程栈上。这两个不同的引用指向堆上同一个对象。但是,这个共享对象(Object 3)持有Object2和Object4一个引用作为其成员变量(如图中Object3指向Object2和Object4的箭头)。通过在Object3中这些成员变量引用,这两个线程就可以访问Object2和Object4。

注:

垃圾回收不会涉及栈内存,是自动弹出压入的,是自动完成的。栈不会越大越好,栈只会增加运算的次数 不会更快,使线程数更少

栈内存溢出:

  • 栈帧过多(递归调用)使栈内存溢出 stackOverFlow
  • 栈帧过大使栈内存溢出。

堆:

JVM管理的最大的一块内存区域,存放着对象的实例,是线程共享区,是垃圾收集器管理的主要区域,因此也被称为“GC堆”

JAVA堆的分类:

从内存回收的角度上看,可分为新生代(Eden空间,From Survivor空间、To Survivor空间)及老年代(Tenured Gen)从内存分配的角度上看,为了解决分配内存时的线程安全性问题,线程共享的JAVA堆中可能划分出多个线程私有的分配缓冲区(TLAB)

JAVA堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。但是堆内存空间不足也会抛OutOfMemoryError异常

方法区:

线程共享,存储着与类结构相关信息(成员变量,方法数据,成员方法,构造器方法,特殊方法【类的构造器】,常量,静态变量)
方法区在虚拟机启动时被创建,逻辑上是堆的一部分。(在jdk1.8方法区是在系统内存中)

运行时常量池:

运行时常量池是方法区的一部分。常量池是.class文件中的,当该类被加载的时候,他的常量信息就会放入运行时常量池信息,并把里面的符号变为真实的地址。

运行时常量池相比于class文件中的常量池所不同的是其具备了动态性。class文件中常量池中的常量在编译期间就已经定义好了,而运行时常量池在程序运行期间也可以将常量放入该常量池中,最常见的做法就是调用String类的intern()方法。

本地方法栈:

与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机直接就把本地方法栈和虚拟机栈合二为一。

 

参考链接:

https://blog.****.net/weixin_30888027/article/details/96721753 

https://blog.****.net/qq906627950/article/details/81324825 

https://www.cnblogs.com/zmy-520131499/p/11128580.html