本篇为观看寒食君视频所做的笔记。
视频链接:https://www.bilibili.com/video/BV15f4y1A7S3?spm_id_from=333.999.0.0
JVM中的内存结构和内存模型都是对物理内存的抽象,只是抽象划分有些不同。
不要混淆JVM中的内存结构和内存模型!JVM的内存模型请看这里《JVM 内存模型》
JVM内存结构:
线程共享:方法区、堆
线程独占:程序计数器、虚拟机栈、本地方法栈
存储字节码的指定地址,提供给执行引擎去取指令来执行
虚拟机栈
虚拟机栈中由栈帧构成。栈帧中主要存在四种结构:局部变量表、操作数栈、动态链接、返回地址。
局部变量表:栈帧是通过方法源码来生成的。调用方法时,传入方法的参数类型、局部变量类型在源码中都是已经确定的,既然数量和类型已确定,那么占用的空间就确定。
操作数栈:用来存储操作数的栈,操作数大部分是方法中的变量。关于操作数栈还存在一个小优化,由于一个方法的返回值经常作为另一个方法中操作数,所以当一个方法调用另一个方法时,调用方法的栈帧中的操作数会和被调用方法的局部变量表地的部分重叠,这样在进行方法调用时可以共享一部分数据而无需进行额外的参数复制传递
动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接(Dynamic linking)
返回地址:正常情况为返回值,异常情况则抛出异常
本地方法栈
用来支持本地(native)方法的调用逻辑
方法区方法区是虚拟机中的抽象概念,不论是什么虚拟机,在具体实现中必须要存在方法这个结构。规范好比是一座房子的图纸,其中该规定了必须有书房这个房间,建造商在拿到图纸后,需要在限定的条件下去设计这个书房,如何设计建造,就是具体的实现方式。
HotSpot中,在JDK8之前使用永久代来实现方法区,JDK8之后使用元空间来实现方法区。方法区是抽象,永久代和元空间是实现。
使用永久代实现方法区的缺点:
1、可能引起内存溢出
永久代的大小设置为多少,可以通过启动参数指定,但其中的存储数据大小是动态变化的,阈值设置太小容易出现类频繁卸载或者内存溢出问题,设置太大则容易出现空间浪费
2、永久代本身的复杂设计并不是方法区需要的,可能带来未知异常。
永久代内的对象不是内存连续的,需要通过额外的存储信息以及实现额外的对象查找机制来定位对象。
方法区内存储的信息:
在类加载阶段,虚拟机将会读取被编译的Class文件生成Class对象,Class对象存储了一些类型信息,而这些类型信息就是存储在方法区中,这些类型信息有属性、方法等
在方法区存储的方法信息中有一个叫做LineNumberTable的变量,数据格式是line xx: xx的形式,表示的是源码行数和字节码index中的映射。我们在debug时,程序能够精准地停留在源码的断点处,但是程序运行的是字节码,断电的所在行是源码,他们就是使用这个LineNumberTable来对应的。
方法区中有运行时常量池,运行时常量池主要存储两大类数据:
1、第一种是编译期产生的,主要是字节码中定义的静态信息,比如由字节码生成的class对象或者字节码生成的字面量(即我们在编写代码时定义的常量变量)
2、第二种是运行时期产生的,这些比较灵活,比如说运行时会把一些符号引用转变为直接引用,而这些转变后的直接引用就可以存储起来;还有常见的字符串常量池。
方法区的垃圾回收:
方法区的数据大部分需要稳定使用,一般不关注该区域的垃圾回收。但这并不意味着完全不需要垃圾回收,当方法区的内存占用到一定阈值,也会进行GC。
会进行垃圾回收的信息比如有当内存紧张时,可以对小部分类进行卸载,等以后需要这些类时再重新加载。内存紧张时字符串常量池也会进行垃圾回收。
堆
堆中大部分存储的是对象。关于堆的内容主要是涉及垃圾回收!



