目录
- 概述
- 堆
- 虚拟机栈
- 栈帧
- 当前栈帧
- 创建栈帧
- 栈异常的两种情况
- 本地方法栈
- 方法区
- 方法区存储
- 永久代和元空间的区别
- 结束
概述
整个 jvm 构成里,主要由三部分组成:类加载系统、运行时数据区、执行引擎。
由上图总结如下。
按照线程使用情况和职责分成两大类:
- 线程独享(程序执行区域)
- 虚拟机栈、本地方法栈、程序计数器
- 不需要垃圾回收
- 线程共享(数据存储区域)
- 堆和方法区
- 存储类的静态数据和对象数据
- 需要垃圾回收
堆
java 堆在 jvm 启动时创建内存区域,去实现对象、数组与运行时常量的内存分配,它是虚拟机管理最大的,也是垃圾回收的主要内存区域。
内存划分:
核心逻辑就是三大假说,基于程序运行情况进行不断的优化设计。
堆内存为什么会存在新生代和老年代
分代收集理论:当前商业虚拟机的垃圾收集器,大多数都遵循了 分代收集 (Generational Collection) 的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:
- 弱分代假说 (Weak Generational Hypothesis) :绝大多数对象都是很快会消亡的
- 强分代假说 (Strong Generational Hypothesis) : 熬过越多次垃圾收集过程的对象就越难以消亡。
这两个分代假说是几个常用垃圾收集器的一致的设计原则 :收集器应将java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
这就同时 兼顾了垃圾收集的时间开销和内存的空间有效利用。
虚拟机栈
栈帧
栈帧(Stack Frame) 是用于支持虚拟机进行方法执行
的数据结构。
栈帧存储了方法的 局部变量表
、操作数栈
、动态连接
和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机里从入栈到出栈的过程。
栈内存为线程私有的空间,每个线程都会创建私有的栈内存,生命周期与线程相同,每个java方法在执行的时候都会创建一个栈帧(Stack Frame)
。栈内存大小决定了方法调用的深度,栈内存过小则会导致方法调用的深度过小,如递归调用的次数较少。
当前栈帧
一个线程中方法的调用链可能会很长,所以会有很栈帧。只有位于jvm虚拟机栈,栈顶的元素才是有效的,即称为当前栈帧
,与这个栈帧相关连的方法称为当前方法
,定义这个方法的类叫做当前类
.
执行引擎运行的所有字节码指令
都只针对当前栈帧
进行操作。如果当前方法调用了其方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。
创建栈帧
什么时候创建栈帧?
调用新的方法时,新的栈帧也会随之创建。并且随着程序控制权转移到新方法时,新的栈帧成为了当前栈帧。
栈异常的两种情况
- 如果线程请求的栈深度大于虚拟机所允许的深度(Xss 默认 1m),会抛出 StackOverflowError异常
- 如果在创建新的线程时,没有足够的内存去创建对应的虚拟机栈,会抛出OutOfMemoryError导常(不一定能复现)
本地方法栈
本地方法栈
和虚拟机栈
(自己编写的代码是在此运行)相似,区别就是虚拟机栈为虚拟机执行 java 服务(字节码服务),而本地方法栈为虚拟机使用到的 Native
方法(比如 C++) 服务。
简单来说,一个Native Method 就是一个 java 调用非 java 代码的接口。
方法区
方法区(Method Area) 是可供各个线程共享的运行时内存区域,方法区本质上是 java 语言编译后代码存储区域,它存储每一个类的结构信息
,如:运行时常量池、成员变量、方法数据、构造方法和普通方法的字节码指令等内容。很多语言都有类似区域。
方法区的具体实现有两种:永久代(PermGen)、元空间(Metaspace)
方法区存储
存储了以下数据
Class1 | Class2 |
---|---|
1.类型信息 | 1.类型信息 |
2.类型的常量池 | 2.类型的常量池 |
3.字段信息 | 3.字段信息 |
4.方法信息 | 4.方法信息 |
5.类变量 | 5.类变量 |
6.指向类加载器的引用 | 6.指向类加载器的引用 |
7.指向Class实例的引用 | 7.指向Class实例的引用 |
8.方法表 | 8.方法表 |
运行时常量池(所有Class共用,此为 Class1 与 Class2) |
总结如下:
主要有如下三种类型。
- 第一:Class
- 类型信息,比如Class(idea中定义的类)
- 方法信息,比如Method(方法名称、方法参数列表、方法返回值信息)
- 字段信息,比如Field(字段类型、字段名称需要特殊设置才能保存的住)
- 类变量(静态变量):jdk1.7这后,转移到堆中存储(按jvm规范是在方法区,实现不一定)
- 方法表(方法调用的时候) 在A类的main方法中去调用B类的method方法时,是根据B类的方法表去查找合适的方法,进行调用。
- 第二:运行时常量池(字符串常量池):从class中的常量池加载而来,jdk1.7之后,转移到堆中存储
- 字面量类型
- 引用类型 --> 内存地址
- 第三:JIT编译器编译之后的代码缓存
如果需要访问方法区中类的其它信息,都必须先获得Class对象,才能获取该Class对象关联的方法信息或者字段信息。
永久代和元空间的区别
- jdk1.8之前使用的方法区实现是永久代,jdk1.8及以后使用的方法区实现是元空间。
- 存储位置不同
- 永久代所使用的内存区域是jvm进程所使用的区域,它的大小受整个jvm的大小所限制。
- 元空间所使用的内存区域是物理内存区域;那么元空间的使用大小只会受物理内存大小的限制。
- 存储内容不同
永久代
存储的信息基本上就是上面方法区存储内容中的数据。- 元空间只存储类的元信息,而静态变量和运行时常量池都挪到堆中。
结束
至此,运行时数据区就结束了,如有疑问,欢迎评论区留言。