概念
一个Java进程对应着一个JVM程序,一个JVM只有一个运行时数据区。一个运行时数据区又只有一个堆和方法区。
运行时数据区
1. PC计数器
学过计组的应该对PC计数器不陌生,JVM的PC计数器作用与其大同小异,程序要继续执行则必须知道下一条指令是啥吧。
PC(Program Counter,PC)用来存放当前欲执行指令的地址,它与主存的MAR之间有一条直接通路,且具有自加1的功能,即可形成下一条指令的地址。
中文名指令计数器外文名PC(program counter)应 用存放下一条指令所在单元所属类别单片机及汇编语言
指令计数器是用于存放下一条指令所在单元的地址的地方。
PC计数器没有OOM(Out Of Memory)和GC(Garbage Colletion),它用来储存下一条指令地址,每个线程独有一份。
2. 方法区
JDK8中改名叫元空间,之前叫做永久代,元空间在JDK8后被移到了与堆不相连的本地内存(你的电脑内存条的内存,这部分不归内存JVM管)中,而在之前都是放在堆中的。主要原因是永久代调优困难,且大小不好事先确定。
存放类的元信息、域信息(字段信息)、JIT代码缓存、方法信息、运行时常量池(JDK8后逻辑上在方法区,物理上在堆)。方法区的大小决定了能加载多少类。
字符串常量池和静态变量自JDK7移至堆,以前放在永久代,永久代的gc只有full gc,只有方法区触及回收阈值和老年代满了才会触发full GC,导致回收效率不高。
方法区的垃圾回收对象:常量池中废弃的的常量和不再使用的类型
3. 本地方法区
我的理解是针对native修饰方法的专属方法区。
4. 堆
堆是共享的,JVM自然需要对其做一些并发操作处理。
对象分代理论:新生区 -> 老年区 -> 元空间(参数设置影响不了元空间的实际大小)
新生区分成 Eden:Suvivor:Survivor = 8:1:1 ,假设两个Survivor区分别是A和B,垃圾回收前会把分配的对象放在Eden区中,满了放不下就触发minor GC(针对Eden和非空的那块Survivor的GC,也叫YGC),minor GC后还存活的对象放入空的Survivor A中,清空Survivor B。下次GC后又将幸存的对象移入Survivor B中,清空Survivor A,以此类推。此种策略叫做复制-清除算法。
如果Eden满了触发minor GC后还放不下对象,就会把对象直接放入老年区(说明要放入Eden区的是个大对象)。如果老年代还放不下,就会进行full GC。如果还不行就OOM了。
TLAB(Thread Local Allocation Buffer):线程本地内存,由于堆内存是线程共享的,所以要为每个线程划分一块独立内存。这块内存存在于Eden区内,一个Eden区中有多个TLAB。但TLAB仅占Eden的1%,避免线程太多占满Eden。对象分配优先在TLAB中分配,因为不需要加锁,如果分配失败就会共享内存中分配。
堆的优化*
- 栈上替换,如果对象未逃逸出方法,就直接在栈上分配。
- 标量替换,将不会逃逸的对象分解成标量。换成原始数据类型后(如int,byte)就不会占用堆内存了。
5. 虚拟机栈
对于栈我们应该知道一个方法的执行对于入栈操作,一个方法的返回对应出栈操作,而方法对应了栈的单位就是栈帧。
栈帧组成:局部变量表,操作数栈,动态链接(指向运行常量池的方法引用),方法返回地址,一些附加信息
局部变量表:局部变量表是数组,保存方法体的局部变量的数据类型,方法参数,方法返回值。
局部变量表储存单位是变量槽,1个slot占32bit,非静态方法的首位槽就是this,所以静态代码块里无法调用this。槽位可以重复利用,一个变量出了作用域过期后的槽位可以重复利用。局部变量表大小在编译器就已经确定,在运行时无法改变。
局部变量表中直接或间接引用的对象都是GC Roots
操作数栈:
操作数栈用于保存计算的中间结果,同时作为计算过程中变量的临时储存空间,解释引擎是基于操作数栈的执行引擎。栈的深度也是在编译器就确定了
栈顶缓存:由于栈架构指令多,读写次数多,所以把栈顶元素全部缓存到CPU的寄存器中,降低对内存的读写速度
绑定:绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,仅仅发生一次。
动态链接:
源文件中所有的变量和方法引用都储存在常量池中,被调用的方法在编译期间无法确定。动态链接的作用是将符号引用转换为调用方法的直接引用
静态链接
被调用的方法再运行期间保持不变,目标方法在编译期间已知,这种情况将调用方法的符号引用转换为直接引用的过程叫做静态链接
返回地址
存储该方法的PC寄存器中的值,返回的形式有return和抛出Exception两种
附加信息
其他的一些信息
额外知识
什么是符号引用?
在Java体系中,共用三种常量池。分别是字符串常量池、Class常量池和运行时常量池
所有变量和方法的引用都作为符号引用保存在在编译后的字节码文件中的常量池里
什么是虚方法?
编译确定下来的方法就是非虚方法,如静态方法,final方法,私有方法,实例构造器,父类方法,反之就是虚方法。
invokeVirtual不一定是执行虚方法的指令,因为执行final方法也是此指令。invokeInteface是执行接口的方法
什么是虚方法表?
虚方法表在链接的解析阶段生成,调用虚方法会在自身类中找,若找不到就去一层一层去父类和实现的接口中找。为了性能考虑就创建了虚方法表,子类找方法时直接去表中找,表中未被重写的方法直接指向父类,重写了就指向本类中的方法。
OOM怎么办?