JVM-2 运行时数据区
运行时数据区
程序执行时:
每个线程独立的拥有 程序计数器、本地方法栈、虚拟机栈
线程共享:方法区和堆空间
程序计数器(pc寄存器)
作用:
pc寄存器用于存储下一条指令的地址,即将要执行的指令代码,由执行引擎读取下一条指令
常见问题
1、为什么使用PC寄存器记录当前线程的执行地址?
答:因为CPU需要不停的切换各个线程,切换一个线程后需要记录下次执行从哪开始。明确下次开始的地址
2、 PC寄存器为什么被设定为每个线程一份
答:因为每个线程是独立的,cpu执行时并发的执行多线程,切换时需要记录每个线程执行的地址。
虚拟机栈
Java的指令是基于栈的架构
栈是运行时单位,而堆是存储单位
作用:
主管JAVA程序的运行,它保存方法的局部变量,部分结果,并参与方法的调用和返回。
JVM对栈的操作只有出栈和压栈。且允许Java栈的大小是动态的或是固定不变的。
对于栈来说不存在垃圾回收,但存在异常
栈可能的异常
1、*Error
当固定栈的容量时,线程请求分配的栈容量超过Java虚拟机栈允许的最大容量。
2、OutofMemoryError
虚拟机的栈容量是可变时,尝试扩展内存但无法申请额外内存。
设置栈大小命令:`-Xss
在运行时的 VMoptions
栈内部的存储单位
每个线程都有自己的栈,栈中的数据都是以栈帧的形式存在。
在这个线程上执行的每个方法都对应着一个栈帧,
栈运行原理
1、不同线程中所包含的栈帧是不允许存在相互引用的,即不能在一个栈帧中引用另一个线程的栈帧。
2、Java方法有两种返回方式:一种是正常的return 返回,一种是抛出异常。这两种都会导致方法栈帧被弹出
栈帧的内部结构
1、局部变量表
定义:一个数字数组,主要存储方法参数和定义在方法内部的局部变量。
特点:
局部变量表不涉及安全问题
局部变量表所需要的容量在编译器就确定了
局部变量表的基本存储单元是槽(Slot)
32位以内的类型占用一个槽:
64位的类型占用两个操:long double
补充说明:
局部变量表中的变量是重要的垃圾回收根节点。
2、操作数栈(表达式栈)
在执行方法过程中,根据字节码指令,往栈中写入数据或提取数据。
理解为方法执行过程中的一个个指令执行的临时数据
栈顶缓存技术:将栈顶元素全部缓存在物理cpu寄存器中,降低对物理内存的读写
3、动态链接(指向运行时常量池的方法引用)
每个栈帧内部包含一个指向运行时常量池中该栈帧所属方法的引用
静态链接:当目标调用的方法在编译期间确定并始终保持不变时,调用的过程称之为静态链接
动态链接:当目标调用的方法在编译期间不能确定(接口),调用的过程称之为动态链接。
方法的调用分类
非虚方法:在编译期确定的方法。
虚方法:在编译器不能确定的方法。
4、方法返回地址
存放该方法pc寄存器的值
5、一些附加信息
可有可无
本地方法栈
介绍本地方法栈之前,需要介绍本地方法区
本地方法区
一个native method 就是一个Java调用非Java方法的接口
本地方法栈用于管理本地方法的调用。
其错误与虚拟机栈一致。
堆
堆是每个线程共有的部分,是进程中的唯一。每个JVM实例只能有一个堆
堆中存放的几乎都是对象和数组。栈帧内部的局部变量表存放的就是指向对象或数组的引用。
在方法结束后,堆中的对象不会立即被移除,仅仅在垃圾收集时才会被移除。
堆是GC(垃圾回收)重点对象。
堆的内存结构
设置堆大小指令:
-Xms :表示堆区(新生区和老年区)的起始内存大小
-Xmx :表示堆区(新生代老年代)设置的最大内存
堆区的默认起始内存大小为物理内存的1/64,最大内存为物理内存的1/4。
**出错:**OOM:OutOfMemory 内存满了
新生代与老年代
其中新生代又可以被划分为Eden(伊甸园)空间、S0和S1区。
配置新生代老年代在堆结构的占比(一般不修改)
默认-XX:NewRatio=2,表示新生代占1、老年代占2、新生代占整个堆的1/3.
几乎所有的Java对象都是在Eden区被new出来的
对象分配过程
YGC:又称minor GC,是垃圾回收器的一种。回收Eden区和幸存者区
堆中的垃圾回收
频繁在新生区回收,很少在老年区回收,几乎不再永久区/元空间回收。
堆中的几个垃圾回收
1、Minor GC
针对于新生区的垃圾回收
2、Major GC
针对 老年区进行垃圾回收
3、Full GC
整堆收集,收集整个Java堆和方法区的垃圾收集
方法区
尽管所有的方法区在逻辑上属于堆的一部分,但具体的实现上与堆分开。
方法区与堆一样,是线程共享的内存结构
针对于HotSpot 虚拟机而言。
JDK 1.7 以前 方法区叫做 永久代
JDK 1.8 以后 方法区叫做 元空间
使用本地内存中实现了元空间,不在占用虚拟机内存。增加
方法区的内部结构
方法区存储已被虚拟机加载的类型信息、常量、静态变量、即时编译后的代码缓存
类型信息
类的全限定类名
类型的直接父类的完整有效名
类的修饰符
类型直接接口的一个有序列表
类的内部属性
类的方法信息
运行时常量池
常量池:class文件中包含了常量池。常量池包含各种字面量和对类型、域和方法的符号引用。
为什么需要一个常量池呢?
将类中的各种信息以符号引用存储,运行时,加载符号对应的信息。减少class文件的大小
运行时常量池是方法区的一部分,常量池通过类加载器加载到方法区后,称为运行时常量池的引用。
其特点是具有 动态性
方法区的演变
1、首先明确,只有hotspot虚拟机才有永久代
2、hotspot虚拟机方法区的变化
-
jdk 1.6 及以前:有永久代,静态变量存放在永久代上
-
jdk 1.7 :有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除保存在堆中。
-
jdk 1.8 :无永久代,类型信息、字段、方法、常量保存在本地内存中,但是字符串常量池、静态变量仍保存在堆中。
方法区的垃圾回收
Java虚拟机规范中对方法区的垃圾回收是比较宽松的
方法区的垃圾回收主要针对两个部分:常量池中废弃的常量和不再使用的类型
常量池:当常量池的常量不用时,回收不再使用的类型:
1、该类所有的实例已经被回收
2、该类的类加载器已经被回收
3、该类对应的Java.lang.Class 对象在任何地方没有被引用。