JVM 运行时数据区
堆,方法区(元空间) 主要用来存放数据 是线程共享的.
程序计数器,本地方法栈,虚拟机栈 是运行程序的,是线程私有的.
程序计数器
jvm中的程序计数器不是cpu中的寄存器, 可以理解为计数器.
JVM 中的 PC 寄存器是对物理 PC 寄存器的一种抽象模拟
是一块非常小的内存空间,运行速度是最快的,不会出现内存溢出情况.,它可以看 作是当前线程所执行的字节码的行号指示器。
作用:记录当前线程中的方法执行的位置. 以便于cpu在切换执行时,记录程序执行的为位置.
在运行时数据区中唯一一个不会出现内存溢出的区域.
它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域.
在 JVM 规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与 线程生命周期保持一致.
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法.程序计数器 会存储当前线程正在执行的 Java 方法的 JVM 指令地址.
它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需 要依赖这个计数器来完成.
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的 字节码指令.
它是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的 区域.
本地方法栈
当我们在程序中调用本地方法时,会将本地方法加载到 本地方法栈中执行.
也是线程私有的, 如果空间不够,也会出现栈溢出错误. hashCode();
虚拟机栈
背景: java为了移植性好(跨平台) 所以将运行程序的设计架构为栈结构运行, 而不是依赖于cpu的寄存器架构.
基于栈的指令设计优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实 现同样功能需要更过的指令集.
栈是运行时的单位(加载方法运行),
而堆是存储的单位(存储对象的).
即:栈解决程序的运行问题,即程序如何执行,或者说如何处理数据.
堆解决的是数据存储的问题,即数据怎么放,放在哪儿.
作用运行方法 一个方法就是一个栈帧. 栈帧中包含( 局部变量(基本类型,引用地址) 方法地址,返回地址)
栈中的操作 入栈,出栈
栈中异常 StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。 递归调用方法次数过多\
栈中存储方法运行时需要的数据
栈的运行原理 第一个方法被加载 入栈 在方法中调用了其他方法, 另一个方法入栈 方法运行结束后出栈.
栈帧的内部结构
每个栈帧中存储着:
局部变量表(Local Variables)
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局 部变量。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量, 则存的是指向对象的引用。
操作数栈(Operand Stack)(或表达式栈)
栈最典型的一个应用就是用来对表达式求值。在一个线程执行方法的过程 中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可 以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
动态链接(Dynamic Linking) (或指向运行时常量池的方法引用) 因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个 引用指向运行时常量。
方法返回地址(Retuen Address)(或方法正常退出或者异常退出的定义)
当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保 存一个方法返回地址。
Java 堆
概述
堆是JVM内存中核心的区域,用来存储创建出来的对象,是线程共享的.
堆空间在jvm启动时被创建,大小可以设置.
物理上不是连续的,逻辑上是连续的空间
堆中会发生垃圾回收.
堆内存区域划分
堆内存划分为:
新生代(新生区)
新生代分为: 伊甸园区(新生成的对象存储)
幸存者0 (from)
幸存者1(to)
老年代(老年区)
为什么要分区?
把不同的生命周期的对象存储在不同区域,这样不同的区域可以使用不同的垃圾回收算法,
可以提高垃圾回收的效率.
对象在堆内存中的的过程:
新建的对象 存放在伊甸园区, 第一次垃圾回收时,垃圾对象直接被回收掉, 存活下来的对象,会把他存放到幸存者0/幸存者1.
再次垃圾回收时,把在幸存者0区 存活的对象 移动到幸存者1区,然后将幸存者0区清空,依次交替执行.
每次保证有一个幸存者区域是空的,内存是完整的.
当对象经过15次垃圾回收后,依然存活的.将被移动到老年区(老年区,垃圾回收的频率就比较低)
堆各区域的占比
新生代 占整堆的三分之一
新生代中的 伊甸园区 幸存者0 幸存者1 占比是 8:1:1
对象经过15次垃圾回收后,依然存活的.将被移动到老年区
为什么最大是15次 , 因为在对象头中只有4个bit位的空间,只能表示最大值15.
堆空间的参数设置
一般所说JVM优化 就是调整jvm相关各区的参数
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
分代收集思想 Minor GC、Major GC、Full GC
一般情况下收集新生代 Minor GC/Yong GC
老年代 会触发Major GC / Old GC
整堆收集 Full GC
整堆收集触发的条件:
System.gc();时
老年区空间不足
方法区空间不足
开发期间尽量避免整堆收集 ( 在垃圾回收时 会STW stop the world 回收时停止其他线程运行 )
TLAB 机制
TLAB 线程本地分配缓存区
在多线程情况下,可以在堆空间中通过-XX:UseTLAB 设置. 在堆空间中为线程开辟一块空间,用来存储线程中产生的一些对象, 避免空间竞争,提高分配效率
字符串常量池位置
jdk7之前,将字符串常量池位置在 方法区(永久代)中存储. jdk8之后 方法区又称为元空间
jdk8之后,将字符串常量池的位置 放到了堆空间. 因为方法区只有触发FUll GC时才会回收.
因为程序中大量的需要使用字符串,所以将字符串常量池的位置改变到了堆中,可以及时回收无效的字符串常量.
方法区
Java 虚拟机定义了序运行期间会使用到的运行数据区,其中有一些会随着虚拟
机启动而创建,随着虚拟机退出而销毁.另外一些则是与线程一一对应的.这些与
线程对应的区域会随着线程开始和结束而创建销毁.
如图:红色的为多个线程共享,灰色的为单个线程私有的,即线程间共享:堆,方法区.
线程私有:程序计数器,栈,本地方法栈.
概述
方法区也是一块内存空间,逻辑上属于堆,为了区分,称为元空间(jdk8之后)
主要用来存储类的信息
在jvm启动时创建 大小可以分配
如果加载的类太多,也会报内存溢出错误
是线程共享的.
方法区,栈,堆的交互关系
方法区的大小可以通过 -XX:MetaspaceSize 设置
方法区在windows中默认大小是21MB
如果到达21MB会触发FULL GC
可以将其值设置的大一些,减少FULL GC的触发
方法区的内部结构
方法区它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编
译后的代码缓存,运行常量池等。
通过反编译字节码文件查看.
反编译字节码文件,并输出值文本文件中,便于查看。参数 -p 确保能查看
private 权限类型的字段或方法
javap -v -p Demo.class > test.txt
方法区中主要回收运行时常量池,类的信息
类的信息卸载(回收) 条件是比较苛刻的.
满足3个条件:
1.该类以及子类的对象没有被引用
2.该类的类加载器被卸载
3.该类的LClass对象也没有被引用