JVM-堆、栈、方法区、程序计数器

目录

JDK、JRE、JVM(总的来说:jdk包含jre,jre包含jvm)

JVM 组成

1、程序计数器

2、栈

JVM栈

本地方法栈

3、动态存储区-堆

4、动态存储区-方法区

方法区信息

运行时常量池


JDK、JRE、JVM(总的来说:jdk包含jre,jre包含jvm)

  • JDK(Java Development Kit) 是整个JAVA的核心,是面向开发人员使用的SDK,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。它提供了Java的开发环境和运行环境,jdk包含一个src类库源码压缩包、和其他几个声明文件。其中,真正在运行java时起作用的是以下四个文件夹:
    • bin:最主要的是编译器(javac.exe)
    • include:java和JVM交互用的头文件
    • lib:类库
    • jre:java运行环境
  • JRE( Java Runtime Environment )是Java的运行环境,包含JVM标准实现及Java核心类库。在JDK下面的的jre目录里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。
  • JVM(Java Virtual Machine )的中文名称叫Java虚拟机,它是由软件技术模拟出的一个运行环境。主要负责把我们的程序翻译成系统能识别的语言。Java的程序需要经过jdk编译后产生.class文件,JVM才能识别并运行它,JVM针对每个操作系统开发其对应的解释器jre,所以只要其操作系统有对应版本的JVM,那么这份Java编译后的代码就能够运行起来,这就是Java能一次编译,到处运行的原因。JVM在Java程序开始执行的时候,它才运行,程序结束的时它就停止。一个Java程序会开启一个JVM进程,如果一台机器上运行三个程序,那么就会有三个运行中的JVM进程。

JVM 组成

(忘了是在哪截的图了,只能在此感谢图主了)

方法区和堆由所有线程共享,其他区域都是线程私有的。

1、程序计数器

程序计数器是一块较小的内存空间,可以把它看作当前线程正在执行的字节码的行号指示器。也就是说,程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。

注:但是,如果当前线程正在执行的是一个本地方法,那么此时程序计数器为空。

作用:

  • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

特点:

  1. 是一块较小的存储空间
  2. 线程私有。每条线程都有一个程序计数器。
  3. 是唯一一个不会出现OutOfMemoryError的内存区域。
  4. 生命周期随着线程的创建而创建,随着线程的结束而死亡。

2、栈

栈是系统自动分配的一块连续的内存空间,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈是线程私有,不能线程共享。

JVM栈

定义和组成:

Java虚拟机栈是描述Java方法运行过程的内存模型。它会为每一个即将运行的Java方法创建一块叫做“栈帧”的区域,这块区域用于存储该方法在运行过程中所需要的一些信息,每一个“栈帧”包括4部分信息:

  • 局部变量表

保存方法的参数以及局部变量用的,局部变量表中的变量只在当前函数调用中有效,当函数调用结束后。随着函数栈帧的销毁,局部变量表也会随之销毁。

当方法在运行过程中需要创建局部变量时,就将局部变量的值存入“栈帧”的局部变量表中。当这个方法执行完毕后,这个方法所对应的“栈帧”将会出栈,并释放内存空间。

存放基本数据类型变量(boolean、byte、char、short、int、float)、引用类型的变量(reference)、returnAddress(指向一条字节码指令的地址)类型的变量。

  • 操作数栈

主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。只支持出栈入栈操作。在概念模型中,局部变量表和操作数栈是相互独立的。但是大多数虚拟机的实现都会进行优化,令下面的部分操作数栈与上面的局部变量表部分重叠在一块,这样在方法调用的时候可以共用一部分数据,无需进行额外的参数复制传递。

  • 动态链接

每个栈帧都包含一个指向运行时常量池(后面讲)中该栈帧所有属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。

  • 方法返回地址

在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。

注:人们常说,Java的内存空间分为“栈”和“堆”,栈中存放局部变量,堆中存放对象。这句话不完全正确!这里的“堆”可以这么理解,但这里的“栈”只代表了Java虚拟机栈中每一个“栈帧”的“局部变量表”部分。真正的Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息等。

特点:

  1. 局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建。而且,局部变量表的大小在编译时期就确定下来了,在创建的时候只需分配事先规定好的大小即可。此外,在方法运行的过程中局部变量表的大小是不会发生改变的。
  2. Java虚拟机栈会出现两种异常:StackOverFlowError(栈)和OutOfMemoryError(栈、堆、方法区)。
  3. Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
  4. 每个线程都会建立一个操作栈,每个栈又包含了若干个“栈帧”,每个栈帧对应着每个方法的每次调用。

本地方法栈

  1. 本地方法栈和Java虚拟机栈实现的功能类似,只不过本地方法区是本地方法运行的内存模型。
  2. 本地方法都不是使用Java语言编写的,比如使用C语言编写的本地方法,本地方法也不由JVM去运行,所以本地方法的运行不受JVM管理。HotSpot VM将本地方法栈和JVM栈合并了。

3、动态存储区-堆

堆的优势是可以动态地分配内存大小,生命周期也不必事先告诉编译器,Java的GC会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

定义:

堆是用来存放对象和数组实例的内存空间。几乎所有的对象都存储在堆中。在Java堆中还要能通过这个对象找到它的类型信息(对象类型,父类,实现的接口,包含的字段与方法等)。

特点:

  1. 一个java程序开启一个JVM进程,一个JVM中只有一个堆。程序中所有的线程都共享它。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。
  2. 堆的存取类型为管道类型,先进先出。
  3. 虚拟机启动时创建。
  4. 可以进一步细分为:新生代、老年代。新生代又可被分为:Eden、From Survior、To Survior。不同的区域存放具有不同生命周期的对象。这样可以根据不同的区域使用不同的垃圾回收算法,从而更具有针对性,从而更高效。(具体《 JAVA垃圾回收机制》
  5. 堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的,因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError

-Xms初始分配JVM运行时内存的大小,如果不指定默认物理内存的1/64

 

4、动态存储区-方法区

定义:

Java虚拟机规范中定义方法区是堆的一个逻辑部分。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。

方法区中存放已经被虚拟机加载的类信息(包括全局数据)、常量(存储在方法区常量池)、静态变量、即时编译器编译后的代码等。

特点:

  1. 方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区,当两个线程同时需要加载一个类型时,只有一个类会请求ClassLoader加载,另一个线程会等待。
  2. 方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为老年代。
  3. 内存回收效率低,方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效。
  4. 对方法区的内存回收的主要目标是:对常量池的回收和对类型的卸载。
  5. Java虚拟机规范对方法区的要求比较宽松。和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。

方法区信息

1、对于每一个加载的类型,会在方法区中保存以下信息:

  • 类型的全名(The fully qualified name of the type)
  • 类型的父类型全名(除非没有父类型,或者父类型是java.lang.Object)(The fully qualified name of the typeís direct superclass)
  • 该类型是一个类还是接口(class or an interface)(Whether or not the type is a class )
  • 类型的修饰符(public,private,protected,static,final,volatile,transient等)(The typeís modifiers)
  • 所有父接口全名的列表(An ordered list of the fully qualified names of any direct superinterfaces)
  • 类型的字段信息(Field information)(全局变量)
  • 类型的方法信息(Method information)
  • 所有静态类变量(非常量)信息(All class (static) variables declared in the type, except constants)
  • 一个指向类加载器的引用(A reference to class ClassLoader)
  • 一个指向Class类的引用(A reference to class Class)
  • 基本类型的常量池(The constant pool for the type)

2、对于每一个字段,会在方法区中保存以下信息(字段声明顺序也会保存):

  • 字段名
  • 字段的类型
  • 字段的修饰符(public, private , protected, static, final, volatile, transient)

3、对于每一个方法,会在方法区中保存以下信息(方法声明顺序也会保存):

  • 方法名
  • 方法返回类型(或void)
  • 参数信息
  • 方法修饰符(public, private, protected , static, final, synchronized, native, abstract)

4、如果方法不是抽象方法也不是本地方法(Native Method),还会保存以下信息:

  • 方法的字节码
  • 本地变量表及操作数栈的大小
  • 异常表

5、方法列表

虚拟机需要存储一些数据,用来快速地访问一个类对象中的方法,一般实现为一个方法表。

为了更高效的访问所有保存在方法区中的数据,在方法区中,除了保存上边的这些类型信息之外,还有一个为了加快存取速度而设计的数据结构:方法列表。每一个被加载的非抽象类,Java虚拟机都会为他们产生一个方法列表,这个列表中保存了这个类可能调用的所有实例方法的引用,保存那些父类中调用的方法。

运行时常量池

方法区中还有一部分是运行时常量池,主要用来存储编译时生成的字面量和符号引用,常量也可以在运行时产生,如String的intern方法。

方法区中也可能存在GC,但虚拟机规范对此不做要求,主要是回收一些常量和卸载一些不用的类型信息,不过要卸载一个类的条件很难达到,而且些处GC其实也回收不了多少内存。

方法区中存放三种数据:类信息(包括全局变量)、常量、静态变量(一定是在类中,不能在方法中定义)、即时编译器编译后的代码。其中常量存储在运行时常量池中,JVM常量池也称之为运行时常量池,它是方法区(Method Area)的一部分。用于存放编译期间生成的各种字面量和符号引用。

由“用于存放编译期间生成的各种字面量和符号引用”这句话可见,常量池中存储的是对象的引用而不是对象的本身。

好处:

常量池是为了避免频繁的创建和销毁对象而影响系统性能,它也实现了对象的共享。

例如字符串常量池:在编译阶段就把所有字符串文字放到一个常量池中。

  1. 节省内存空间:常量池中如果有对应的字符串,那么则返回该对象的引用,从而不必再次创建一个新对象。
  2. 节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,==判断引用是否相等,也就可以判断实际值是否相等。

使用:

  1. 我们一般在一个类中通过public static final来声明一个常量。这个类被编译后便生成Class文件,这个类的所有信息都存储在这个class文件中。当这个类被Java虚拟机加载后,class文件中的常量就存放在方法区的运行时常量池中。而且在运行期间,可以向常量池中添加新的常量。如:String类的intern()方法就能在运行期间向常量池中添加字符串常量。
  2. 当运行时常量池中的某些常量没有被对象引用,同时也没有被变量引用,那么就需要垃圾收集器回收。(具体见《JAVA垃圾回收机制》

 

 

 

 

 

 

 

 

 

 

 

 

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值