JVM学习01节-运行时数据区域

JVM运行时数据区域

1. 运行时数据区图解

从上图可以看出,运行时数据区主要分为5个部分

  • 程序计数器
  • 本地方法栈
  • 虚拟机栈
  • 方法区

其中程序计数器、本地方法栈和虚拟机栈是线程私有的,方法区和堆是线程共享的

何为线程私有?

线程私有数据区域生命周期与线程相同,依赖用户线程的启动/结束而创建/销毁(在 Hotspot VM 内,每个线程都与操作系统的本地线程直接映射,因此这部分内存区域的存/否跟随本地线程的生/死对应)。

何为线程共享?

线程共享区域随虚拟机的启动/关闭而创建/销毁。

2. 程序计数器

2.1 程序计数器是什么

一块较小的内存空间,可以理解为当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器。

2.2 为什么要每个线程都要有一个独立的程序计数器

java的多线程是通过切换、分配处理器处理的,任何时刻,一个处理器都只会处理一个线程中的指令,为了能够让线程切换后‘指示器’恢复到正确的执行位置,所以每个线程都要有一个独立的程序计数器,互不干扰。

2.3 程序计数器有什么特点

  • 正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为
  • 这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

3. 虚拟机栈

3.1 虚拟机栈是什么

我们通常所说的栈,就是虚拟机栈,它是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame),每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

3.2 栈帧是什么

栈帧是方法运行时的基础数据结构。主要存储数据如下:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法出口信息

栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

3.3 局部变量表

局部变量表存放了编译期可知的

  • 各种基本数据类型:8中基本数据类型
  • 对象引用:reference类型,不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置。
  • returnAddress:指向了一条字节码指令的地址

局部变量表所需的内存空间在编译期间完成分配,方法运行期间不会改变局部变量表的大小。

3.4 栈有什么特点

  • 对于栈来说不存在垃圾回收问题,因为方法结束后,对应的栈帧也会销毁。
  • 栈先进先出

4. 本地方法栈

本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用的方式以及数据结构并没有强制规定,因此虚拟机可以自由实现它,甚至 Sun HotSpot 中直接将这两个区域合二为一。

5. 堆

5.1 堆是什么

对于大多数应用来说,堆是 Java 虚拟机所管理内存中最大的一块,它被所有的线程共享,此区域存放的唯一目的就是存放对象实例。但是随着 JIT 编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有对象实例分配在堆上也不是那么“绝对”了。

堆是垃圾收集器管理的主要区域,因此被称为“GC堆”。

站在垃圾收集器的角度来看,可以把内存分为新生代与老年代,默认情况下 Young : Old = 1 : 2,这个可以通过–XX:NewRatio参数进行设定。

其中,新生代 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 From Survivor 和RTo To Survivor。默认情况下 Eden : From : To = 8 : 1 : 1 ,该值可以通过参数 –XX:SurvivorRatio 参数进行设定。

虚拟机每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

前面说过,可以使用 Xms 与 Xmx 来指定堆的最小与最大空间。如果 Xms 小于 Xmx,堆的大小不会直接扩展到上限,而是留着一部分等待内存需求不断增长时,再分配给新生代。Vritual空间便是这部分保留的内存区域。

5.2 堆的常见配置

  • -Xms20M:最小20M

  • -Xmx20M:最大20M

  • -XX:NewSize=10M:新生代最小值

  • -XX:MaxNewSize=10M:新生代最大值

  • -Xmn10M:新生代大小,-XX:NewSize=10M-XX:MaxNewSize=10M的快捷设置

  • -XX:NewRatio=n:老年代与新生代的比例,eg:2,即代表 老年代:新生代 = 2:1

  • -XX:SurviorRatio=n:Eden区与Survivior区的比例,eg:8,即代表 Eden:S0:S1=8:1:1

  • -XX:MaxTenuringThreshold=n:在新生代中对象存活次数(经过Minor GC的次数)后仍然存活,就会晋升到旧生代

5.3 堆有什么特点

  • 被所有线程共享
  • 存放对象实例
  • 垃圾收集器管理的主要区域
  • 按照分代收集器可分为新生代和老年代

5.4 为什么存在两个Survivor?

这个在看到‘内存分配’时在进行学习。

6. 方法区

6.1 什么是方法区

所有线程共享, 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,他还有一个别名:非堆(Non-Heap)。

在HotSpot虚拟机上,方法区也就是我们常说的永久代(Permanent Generation),但是对于其他的虚拟机来说,并不存在这一概念。使用永久带来实现方法区并不是一个好的主意,因为永久代有 -XX:MaxPermSize 的上限,极少数方法,如**String.intern()**可能会引起内存溢出的问题。

并不是说被称为永久代,GC就不会对该区域进行管理,只是较少管理而已。这个区域内存回收的目标主要是针对常量池的回收和对类型的卸载。

方法区中还有一个运行时常量池,用于存储编译生成的字面量和符号引用

6.2 方法区有什么特点

  • 被所有线程共享
  • 存储定义的方法的信息
  • 有大小限制 -XX:MaxPermSize
  • GC较少回收该区域
  • 还有一个运行时常量池,用户存储编译后的字面量和符号引用

6.3 字面量、符号引用

  • 字面量:基本数据类型以及进制,String字符串。int a = 8中的8和String a = "hello"中的hello都是字面量。

  • 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。个人理解:符号引用可以看作是一个虚拟地址,只有在JVM加载完类,确认了字面量的地址,就会将这个符号引用(字符串)解析成直接引用(指针)

7. 常量池

名称存放数据内存位置
class文件常量池(Constant Pool Table)用于存放编译器生成的各种字面量和符号引用,这部分在类被加载后,进入方法区的运行时常量池中存放.class文件
运行时常量池(Run Constant Pool)在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用方法区
全局字符串常量池在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例JDK1.7前(方法区),后(堆)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值