【JVM】JVM 内存结构

程序计数器

Cpu 要不停的切换执行线程,所以在切换回同一个线程的时候要知道程序执行到哪了,程序计数器(PC 计数器),用来存储指向下一条指令的地址,也就是将要执行的代码。

程序的分支、循环、跳转、异常处理、线程恢复都需要程序计数器。

程序计数器是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域

虚拟机栈

概述

每个线程在创建的时候都会创建一个虚拟机栈,虚拟机栈里存储的是一个个栈帧,对应着一次次的 Java 方法调用,是线程私有的,生命周期与线程一致。

虚拟机栈对栈帧的操作只有压栈和出栈两个操作,在一个活动线程中,只有正在执行的方法的栈帧是有效的,这个栈帧被称作当前栈帧,与当前栈帧对应的方法叫做当前方法,当前方法所对应的类叫做当前类。

栈帧的结构

栈帧主要由四部分组成:

  • 局部变量表:存储方法的参数和定义在方法中的局部变量
  • 操作数栈:在字节码执行的过程中,向操作数栈中写入和写出数据,主要用于保存计算的中间结果,同时作为程序执行过程中,变量的临时存储
  • 动态链接:每一个栈帧中都包含一个指向运行时常量池中这个栈帧所属方法的引用
    在 Java 源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在 Class 文件的常量池中。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
  • 方法返回地址

本地方法栈

本地方法栈类似于虚拟机栈,栈帧对应的方法为本地方法,native 修饰的方法,一般为 C语言编写的

内存划分

Java 堆是 Java 管理的最大一块内存,被所有线程共享。堆内存的唯一目的就是存放对象实例,几乎所有的对象实例的内存都是在这里被分配的。

Java 堆被分成两个代,新生代和老年代,新生代又被分为 eden 区和 survivor 区(from 和 to),分代的唯一目的就是使垃圾回收更有效。

  • 新生代:新对象和没有到达一定年龄的对象都在年轻代
  • 老年代:被长时间使用的对象,老年代的空间要比新生代大很多

年轻代

年轻代是几乎所有新对象创建的地方,年轻代被分为两个区域,eden 区和 survivor 区,suvivor 区又被分成两个部分,from 和 to,比例是 8:1:1,当年轻代内存满了之后会发生 GC,这种 GC 被称为 Minor GC

  • 几乎所有新创建的对象都会在 eden 区被分配内存
  • 当 eden 区满了会发生第一次 GC,扫描 eden 区将存活的对象都移动至 survivor from 区,清除掉垃圾对象
  • 当 eden 区再次触发 GC 的时候,会扫描 eden 区和 survivor from 区,对这两个区域进行垃圾回收,将存活的对象移动至 survivor to 区,同时将年龄 +1
  • 清空 eden 区和 survivor from 区后,将 from 区和 to 区交换,也就是说谁空谁是 to 区

老年代

老年代存储的对象包括经过许多轮 Minor GC(年龄15) 后仍然存活的对象和大对象

快速分配策略和逃逸分析

TLAB

对 Eden 区进行划分,为每个线程划分一个线程私有的缓存,多线程同时分配的时候,使用 TLAB 可以进行快速分配避免一些多线程的安全问题,这就是快速分配策略

逃逸分析

器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。

逃逸分析的基本行为就是分析对象动态作用域:

  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
  • 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中,称为方法逃逸。

方法区

永久代和元空间

  1. 方法区是 JVM 规范中的定义的一个概念,具体实现各个jvm厂商可能不同,以 HotSpot 为例,在 Java7 中使用永久代来实现方法区,永久代在物理上是与堆空间连续的受JVM垃圾回收的管理;在Java8中,使用了元空间来代替永久代实现方法区,元空间使用的是本地内存也就是堆外内存,不受JVM管理,因此很少出现方法区的内存溢出。
  2. 永久代和元空间存储的内容是不同的,元空间存储的是类的元信息,静态变量和常量池都并入堆中;相当于永久代的内容被划分到了堆(静态变量和常量池)和堆外内存(类的元信息)
    方法区的内部结构
    方法区用于存储已经被虚拟机加载的类型信息、常量、静态变量以及即时编译器编译后的代码缓存。

运行时常量池

  1. 常量池表:
    我们知道常量池是 class 字节码文件的一部分,主要存储的是字面量和符号引用(类和接口的全限定名;变量的名称和描述符;方法的名称和描述符)
    一个 Java 的类、接口编译后会产生一个字节码文件,这个字节码文件通常需要数据的支持,但是这个数据可能会很大,不能存在字节码中,所以就换一个方式存储在常量池表中,然后字节码有指向这个常量池的符号引用。下图中的#2即为符号引用,引用了常量池
    常量池可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
  2. 运行时常量池:
  • 在类加载到虚拟机之后会创建对应的运行时常量池
  • 常量池表是 Class 字节码文件中的一部分,用于存储编译时期生成的各种字面量和符号引用,这一部分在类加载后会被存储到方法区的运行时常量池中
  • 运行时常量池中包含了各种不同的的常量,既包括在编译期间就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或字段引用。此时不再是常量池中的符号地址了,这里换为真实地址
  • 运行时常量池,相对于 Class 文件常量池的另一个重要特征是:动态性,Java 语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String 类的 intern() 方法就是这样的
相关推荐
Ysjt | 深1 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
阿伟*rui8 小时前
jvm入门
jvm
学点东西吧.12 小时前
JVM(五、垃圾回收器)
jvm
请你打开电视看看14 小时前
Jvm知识点
jvm
程序猿进阶15 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
阿龟在奔跑1 天前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
王佑辉1 天前
【jvm】方法区常用参数有哪些
jvm
王佑辉1 天前
【jvm】HotSpot中方法区的演进
jvm
Domain-zhuo1 天前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
Theodore_10222 天前
7 设计模式原则之合成复用原则
java·开发语言·jvm·设计模式·java-ee·合成复用原则