一、整体架构
五个部分
image.png
线程私有:右侧部分,虚拟机栈、本地方法栈、程序计数器
线程共享:左侧部分,堆,方法区
五个模块:类装载子系统、运行时数据区、执行引擎、本地方法接口、垃圾收集模块
image.png
(class字节码文件— 加载(选择类加载器)—连接—初始化)
二、运行时内存
2.1程序计数器
线程私有的,记录字节码行号(类似寄存器的程序计数器)。可以告诉我们每一个线程执行到了哪一个位置。
pc寄存器所在的线程失去执行权时会记录下一条指令对应的编号,在线程重新获取执行权时执行。
2.2虚拟机栈
线程私有,内存连续。每调用一个方法都是入栈。虚拟机栈包含栈帧(方法A),每个方法压栈之后会有一个新的栈帧,栈帧内包含局部变量表、操作栈,动态链接、方法返回地址,虚拟机栈中有很多栈帧(方法)。
- -Xss1m:设置虚拟机栈的大小
- 局部变量表:存放方法参数、方法内声明的局部变量。包括8种基本类型、对象引用、returnAddress(指向一条字节码指令的值)
- 操作数栈:随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作
- 动态连接:指向运行时常量池的符号引用,比如#2这个符号引用代表了运行时常量池中的某个类和某个方法。持有这个引用的目的是为了支持方法调用过程中的动态链接(Dynamic Linking)
public class StackDemo2 {
public static void main(String[] args) {
int i = 1;
int j = 2;
int z = i + j;
}
}
如上,i,j,z和它们的值一开始存在局部变量表中,当程序计数器指向i=1这行的时候,将1从局部变量表复制到操作数栈中,接着指向j=2,将2复制到操作数栈,再然后指向j+i,得到结果3,将结果3放入操组数栈并且返回保存到局部变量表
- 方法返回地址:调用该方法的pc寄存器的值。比如方法A调用完成,返回值是调用该方法的指令的下一条指令的地址的值。方法报错出现异常,返回的是异常的信息
2.3本地方法栈
线程私有,为native方法提供服务。在虚拟机栈调用本地方法的时候提供调用服务
2.4堆
线程共享,内存不连续,包含新生代和老年代。
堆空间大设置:最大:Xmx20m 最小:Xms
-
线程共享
-
是jvm管理的内存最大的一块区域。
-
有一小块区域,叫线层缓冲区,thread local allocation buffe,是私有的
-
堆中基本上存储所有对象,逃逸的可能,对象有可能放在方法区。
-
垃圾回收器主要管理的区域
jdk7堆空间分类
image.png
perm在jdk8以后被移除,因为方法区被元空间取代
jdk8堆空间分类
image.png -
对象分配过程:
对象创建,放入年轻代的伊甸园区,伊甸园填满,触发minor gc,将无引用的对象清除,剩余对象放入幸存者0区,如果再次gc,会将幸存的还存在引用的对象放入幸存者1区,反复15次,进入老年代,老年代内存不足的时候,进行major gc,如果还是内存不足,full gc,还放不下就报错。 -
新生代收集(Minor GC / Young GC): 只是新生代的垃圾收集
-
老年代收集 (Major GC / Old GC): 只是老年代的垃圾收集 (CMS 的 majirGC 单独回收老年代,其它的major相当于full)
-
混合收集(Mixed GC):收集整个新生代及老年代的垃圾收集 (G1 GC会混合回收, region区域回收)
-
整堆收集(Full GC):收集整个java堆和方法区的垃圾收集器
2.5元空间
1.8开始,元空间取代了方法区,位于本地内存中而不是jvm虚拟机中。
元空间中存放了原本存在于方法区中的类的元信息,而方法区中的静态变量和常量池并入堆中。
元空间好处:
- 方法区存放的信息导致其空间大小不容易确定,很容易出现内存溢出
- 提升类元数据的独立性
- 降低gc复杂度
区别
元空间metaspace可以在运行时扩展。
元空间metaspace是本机内存的一部分,而permGen是堆的一部分。
2.6方法区
线程共享,被虚拟机栈加载过的数据保存在方法区,比如类的信息、常量,静态变量。包含永久代。
去除后,方法区的内容被瓜分:(类静态变量、字符串常量池好像在jdk7就已经转移到堆了)
- 类信息——元空间
- 类静态变量、字符串常量池——堆
Java7及以前版本的Hotspot中方法区位于永久代中。同时,永久代和堆是相互隔离的,但它们使用的物理内存是连续的。
2.7运行时常量池
运行时常量池vs常量池
- 常量池位于字节码文件中,用来存放编译期间生成的各种字面量与符号引用。常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
- 运行时常量池位于方法区中,是常量池在运行时的表现形式
理解为字节码中的常量池 Constant pool 只是文件信息,它想要执行就必须加载到内存中。而Java程序是靠
JVM,更具体的来说是JVM的执行引擎来解释执行的。执行引擎在运行时常量池中取数据,被加载的字节码常量池
中的信息是放到了方法区的运行时常量池中。
它们不是一个概念,存放的位置是不同的。一个在字节码文件中,一个在方法区中。
2.8直接内存
在JDK 1.4中新加入了NIO(New Input/Output) 类, 引入了一种基于通道(Channel) 与缓冲区 (Buwer) 的I/O方式, 它可以使用Native函数库直接分配堆外内存, 然后通过一个存储在Java堆里面的 DirectByteBuwer对象作为这块内存的引用进行操作。 这样能在一些场景中显著提高性能, 因为避免了 在Java堆和Native堆中来回复制数据。
类信息:
反射获取method、field、interface等都是从private transient volatile SoftReference<Class.ReflectionData<T>> reflectionData; 这个变量中获取,在java.lang.Class中被声明为类变量。
字符串常量池(String Pool)
- java6:
- 存在于永久代中。
- 字符串常量池保存的是字符串常量。
- java7:
- 转移到了堆中。
- 字符串常量池存的是字符串常量和堆内的字符串对象的引用。
静态常量池(class文件常量池)
- 用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
- 字面量:文本字符串int long 等基本类型和被声明为final的常量值等。
- 符号引用:是一组符号来描述所引用的目标,符号可以是任何形式的字面量一般包含以下三种:
- 类和接口的全限定名:例如对于String这个类,它的全限定名就是java/lang/String。
- 字段的名称和描述符:这里的字段就是类或者接口中声明的变量。
- 方法的名称和描述符:这里的描述符是方法的参数类型+返回值类型。
- 静态常量池其实就是编译后的class文件里的一部分内容。
运行时常量池
- 当类加载到内存中后,jvm就会将静态常量池中的内容存放到运行时常量池中。
- 运行时常量池中的字符串在从静态常量池加载时
- 会先去String Poll中查询此此字符串在String Poll中的引用
- 如果没有则在String Poll中创建此字符串然后返回其引用
- 用返回的引用替换运行时常量池的字符串
- 运行时常量池是全局共享的,多个类共用一个运行时常量池。并且class文件中常量池多个相同的字符串在运行时常量池只会存在一份
在JDK1.8中,使用元空间代替永久代来实现方法区,但是方法区并没有改变,变动的只是方法区中内容的物理存放位置。正如上面所说,类型信息(元数据信息)等其他信息被移动到了元空间中;但是运行时常量池和字符串常量池被移动到了堆中。但是不论它们物理上如何存放,逻辑上还是属于方法区的。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
网友评论