Skip to content

Latest commit

 

History

History
81 lines (50 loc) · 6.9 KB

RuntimeDataAreas.md

File metadata and controls

81 lines (50 loc) · 6.9 KB

一、运行时数据区

运行时数据区

👒线程私有的内存区域:

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

🌂线程共享的内存区域:

  • 方法区、Java堆

二、线程私有的内存区域

🍛2.1 程序计数器

Program Counter,简称 PC,用于存放 下一条 指令所在单元的地址,是线程所执行的字节码的行号指示器。因为JVM的多线程是通过轮流切换来分配CPU的执行时间(时间片轮询),当切换到下一条线程的时候,线程要能知道当前要执行的字节码位置,这就要求每条线程都要有一个自己的程序计数器,独立存储待执行的虚拟机字节码指令的地址。

🍮2.2 虚拟机栈

虚拟机栈

虚拟机栈生命周期同线程,所以不必担心垃圾回收问题。**虚拟机栈(VM Stack)**这个内存区域对应的是每个线程class方法执行的内存模型(不仅Java,像Kotlin、Scala、Groovy等于都可以编译成class文件并允许在JVM上)。线程中的每个方法在执行的同时都会创建一个 栈帧(Stack Frame)。每个class方法从被调用执行完成的过程,都对应着一个栈帧在虚拟机栈从入栈出栈的过程。

栈帧是用来存储 (方法内的)局部变量表、(计算时的)操作数栈、(引用的)动态链接、(进入或退出方法时的)方法出入口等。

  • 局部变量表:存放 编译期 可知的数据类型,包括 基本数据类型(boolean、byte、char、short、int、long、float、double)、对象引用(reference类型)、returnAddress类型(指向一条字节码指令的地址)。每个 **局部变量空间(slot)**的长度是 32位,而 longdouble类型的每个变量占用 2个局部变量空间,也就是会占用64位空间。局部变量表所需的内存大小在编译期就已经确定,并记录在class文件中。
  • 操作数栈:栈是一个先进后出的线性表,操作数栈是逻辑计算的数据容器,比如下面的计算:
int a = 1;
int b = 2;
int c = 1 + 2;
retuen c;

🍈2.3本地方法栈

本地方法栈和虚拟机栈相似,只不过虚拟机栈是用于线程中执行Java方法(字节码,因为还有Scala、Kotlin、Groovy等其他运行在虚拟机上的编程语言),本地方法栈是用于执行本地的 native方法

三、线程共享的内存区域

🍊3.1 堆

堆Heap又称 Java堆,这个内存区域是JVM线程共享的区域,也是JVM管理的最大一块内存区域。堆内存区域创建于JVM启动时。

唯一 目的就是存放 实例对象,反之不成立,也就是说有少量的对象实例并不在Java堆中存放,也可以在栈上分配,Java堆仅存放绝大量的对象实例

Java堆 不像 栈 那样,随线程而生、随线程而亡。Java堆中的实例对象可能被多个线程中的变量所引用,一个线程死亡,而其他线程还可能正使用此实例对象,所以 Java堆 成为垃圾收集器收集垃圾一个重要的区域(宏观来讲是垃圾收集器管理的主要区域)。

现在主流的垃圾收集器采用 分代收集算法,堆内存又被细分为新生代(Young Generation)老年代(Old Generation)(默认比: 1:2)。新生代 再细分为:1个Eden空间2个Survivor空间(默认比例8:1:1),2个Survivor空间其中一个是From Survivor空间和一个To Survivor 空间分代分空间的唯一理由就是优化GC性能。具体jvm查看命令可参考jvm内存分析命令文章)

常量池

  • 静态常量池:即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数值)字面量,还包含类、方法的信息,占用class文件绝大部分空间;
  • 运行时常量池:则是JVM虚拟机在完成 类装载 操作后,将class文件中的常量池载入到内存中,并保存在 方法区 中,我们常说的 常量池,就是指方法区中的运行时常量池,存放在字面量和符号引用

🍓3.2方法区

方法区用来存储类信息(类的版本、接口描述)、常量、静态变量、方法(数据与编译后代码)等数据,也有一些方法区的数据生命周期长,进而将方法区称为“永久代(Permanent Generation)”,永久存在不被GC回收,但本质上并不相等。方法区在逻辑上是和Java堆相连的,该区域一般GC很少参与,偶尔会存在对不使用的常量进行内存回收,对于一些卸载的类进行资源回收,因为这些数据占用内存本来就比较少,所以GC的回收效果也非常的一般。

  • JDK1.6的永久代(PermGen)
  • JDK1.7的永久代(PermGen)(过度阶段)
  • JDK1.8的元空间(Metaspace)(直接内存)

用jdk1.6运行后会报错,永久代这个区域内存溢出会报: Exception in thread “main” java.lang.OutOfMemoryError:PermGen space的内存溢出异常,表示永久代内存溢出。

在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是 针对常量池的回收和对类型的卸载。但是在之后的HotSpot虚拟机实现中,逐渐开始将方法区从永久代移除。Java7中已经将运行时常量池从永久代移除,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间(MetaSpace)

使用jdk1.7后 验证如下:执行代码和上面相同 设置参数:-Xmx20m -Xms20m -XX:-UseGCOverheadLimit,这里的-XX:-UseGCOverheadLimit是关闭GC占用时间过长时会报的异常,然后限制堆的大小,运行程序,果然,一会后报异常:

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space 从上面的异常可以知道我们测试增加的常量都放到了堆中,所以限制堆内存以后,不断增加常量,堆内存会溢出。

总结:jdk1.6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面,和堆相独立。所以导致String的intern()方法因为以上变化在不同版本会有不同表现。