PC 寄存器用来存储指向下一条指令的地址,也就是将要执行的指令代码。由执行引擎读取下一条指令。
CPU 需要不停的切换各个线程,当切换到某个线程的时候,CPU 得知道上次执行到哪儿了,从哪儿继续开始执行。CPU 通过改变 PC 寄存器的值来明确下一条要执行的指令是什么。
每个线程都有自己的虚拟机栈,栈中的数据都是以栈帧的格式存在,线程上执行的每个方法各自对应一个栈帧。栈帧中存储这方法执行过程中的各种数据信息。某个时间点上只能执行某一个方法,此方法我们可以称作当前方法,其对应的栈帧称为当前栈帧。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。如果该方法中调用了另一个方法,那么另一个方法对应的栈帧将被压栈到栈的顶端,成为当前栈帧。
局部变量表定义为一个数字数组, 主要用于存储方法参数和定义在方法中的局部变量(基本数据类型、对象引用),以及返回值(returnAddress 类型)。局部变量表所需的内存大小是编译期确定的,其在方法运行期间不会改变。
操作数栈,在方法执行过程中,根据字节码指令,往栈中写入或提取数据,即入栈 push 和出栈 pop。其是由数组或链表来实现的。
栈顶缓存技术,JVM 将栈顶的数据缓存在物理CPU的寄存器中,依此降低内存的读写次数,提升执行引擎的执行效率。
在 Java 源文件被编译为字节码文件时,所有的变量和方法引用都会作为符号引用(Symbolic Reference)保存在 class 文件的常量池里。动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
存放调用该方法的 PC 寄存器的值。( 在方法执行结束时 PC 寄存器的值,其对应的是调用该方法的指令的下一条指令的值)
Java 中用 native 关键字修饰的方法为本地方法,主要用于调用 Java 以外的语言,如 C/C++ 语言的程序。Java 在要调用底层操作系统提供的功能时,即需要这类方法。比如 Object 类中的 getClass 方法即为本地方法
public final native Class<?> getClass();
本地方法栈用于管理本地方法,由线程私有,可以被设定为固定大小或者动态扩展的内存,本地方法在本地方法栈中记录,由执行引擎调用。
堆空间在 JVM 启动时确定,我们可以通过 -Xmx -Xms 来设置大小
当堆被使用的空间超过 -Xmx 设置的大小时,就会出现 OOM 异常(OutOfMemoryError)
一般情况下 -Xms 和 -Xmx 设置为同一大小,是为了在 Java GC 之后不再重新计算堆区的大小,以提高性能。
默认情况下 -Xms 为 电脑内存大小的 1/64。-XmX 为电脑内存的 1/4。
-Xms 和 -Xmx 设置的堆空间大小是 年轻代 + 老年代的总和,不包括持久代(元空间)
-XX:NewRatio=2,表示老年代空间大小 : 新生代空间大小 = 2;实际就是新生代占1/3,老年代占2/3。这是默认比率,通过 -XX:NewRatio 参数来设置。其官方说明如下:
-XX:SurvivorRatio=8,默认为 Eden:From:To = 8:1:1。与其对应的还有一个参数 -XX:InitialSurvivorRatio 设置初始化幸存者空间比率。但默认情况下,此比率受自适应大小策略影响,也就是说,实际情况下,默认的幸存者空间比率不一定是8。官网在 InitialSurvivorRatio 参数中说明如下:
默认情况下,通过使用-XX:+UseParallelGC和-XX:+UseParallelOldGC选项,吞吐量垃圾收集器会启用自适应大小调整,并且根据应用程序行为从初始值开始调整生存空间的大小。如果禁用了自适应大小调整(使用-XX:UseAdaptiveSizePolicy选项),则应使用-XX:SivitorRatio选项为应用程序的整个执行设置幸存者空间的大小。
官方说明如下:
-XX:InitialSurvivorRatio=ratio
Sets the initial survivor space ratio used by the throughput garbage collector (which is enabled by the -XX:+UseParallelGC and/or -XX:+UseParallelOldGC options). Adaptive sizing is enabled by default with the throughput garbage collector by using the -XX:+UseParallelGC and -XX:+UseParallelOldGC options, and survivor space is resized according to the application behavior, starting with the initial value. If adaptive sizing is disabled (using the -XX:-UseAdaptiveSizePolicy option), then the -XX:SurvivorRatio option should be used to set the size of the survivor space for the entire execution of the application.
The following formula can be used to calculate the initial size of survivor space (S) based on the size of the young generation (Y), and the initial survivor space ratio (R):
S=Y/(R+2)
The 2 in the equation denotes two survivor spaces. The larger the value specified as the initial survivor space ratio, the smaller the initial survivor space size.
By default, the initial survivor space ratio is set to 8. If the default value for the young generation space size is used (2 MB), the initial size of the survivor space will be 0.2 MB.
The following example shows how to set the initial survivor space ratio to 4:
-XX:InitialSurvivorRatio=4
# 禁用自适应,需要和 -XX:SurvivorRatio 选项一起使用
-XX:+UseAdaptiveSizePolicy
Enables the use of adaptive generation sizing. This option is enabled by default. To disable adaptive generation sizing, specify -XX:-UseAdaptiveSizePolicy and set the size of the memory allocation pool explicitly (see the -XX:SurvivorRatio option).
public class PrintMemory {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
// 初始化内存大小
long initMem = runtime.totalMemory();
// 最大内存大小
long maxMem = runtime.maxMemory();
// 剩余内存大小
long freeMem = runtime.freeMemory();
System.out.printf("initMem:%dM,maxMem:%dM,freeMem:%dM",toM(initMem),toM(maxMem),toM(freeMem));
}
public static final long toM(long size){
return size / 1024 / 1024;
}
}
对象被 new 之后,首先放在 Eden 区。
当 Eden 区内存空间不足(满了),没有空间存放新 new 的对象时,JVM 会进行垃圾回收(此时的垃圾回收称为 YGC或 Minor GC),此时会将 Eden 区不再使用的对象(有多种判断方式,这个我们后面再说)进行销毁,还在使用的对象移动到 Survivor 0 区,并为其标记年龄(初次放入幸存者区的对象年龄标记为 1)
如果按 From 区和 To 区来区别,那么当前哪个幸存者区为空,谁就是 To 区。
每次 YGC s0 或 s1 都会被清空,等待下次 YGC 时存入对象,然后清空另一个。
特殊情况:
当进行 YGC 时,如果s0或s1区无法存放下所有存活对象,此时,这些对象即便年龄没到15,也会被直接申请放入老年代(元空间)
当新new的对象太大,导致整个Eden 区都放不下时,对象也会直接被申请放入老年代(元空间)
当对象被申请放入老年代时,如果老年代空间不足,则会触发 FGC ,如果 FGC 之后,老年代依然放不下这些对象,则会报 OOM。
Hotspot 虚拟机的 GC,一种为部分回收(Partial GC),一种为整堆回收(Full GC)
Major GC 时 Stop The world 的时间比 Minor GC 长很多。如果Major GC 后内存还不足,就报OOM。
不同的垃圾回收器,可能有所不同,可根据具体情况而定。如我们上面的图中,FGC 可以认为是 Major GC。因为 FGC 和 Major GC 在很多地方都是混同使用的。Full GC 对服务器资源的消耗是很大的,在实际使用中,我们会尽量的避免出现 Full GC,下面几种情况下会触发 Full GC
可通过 -XX:+PrintGCDetails 参数开启程序 GC 日志打印。默认此设置为关闭。
TLAB包含在Eden区内,是JVM 为每一个线程分配的线程私有的内存区域,多线程情况下,TLAB 可以避免一系列的非线程安全问题,同时还能提升内存分配的吞吐量,这种内存分配方式称为快速分配策略。
-XX:+DoEscapeAnalysis 开启逃逸分析,-XX:-DoEscapeAnalysis 关闭。默认为开启。
基于逃逸分析和栈上分配,我们应该尽可能的不让局部变量发生逃逸。
public void test(){
Object obj = new Object();
synchronized (obj){
System.out.println("abc");
}
}
此方法中的 synchronized 的锁为 obj,obj又是局部变量,那么此时 synchronized 块其实是无意义的,实际使用中也不能这样写。
标量:一个无法再分解成更小的数据单元的数据。Java 中的原始数据类型即是标量。
聚合量:与标量相对,还能再分解为更小的数据单元的数据。
基于此,如果我们在运行时生成过多的类,即可能造成方法区 OOM,比如cglib动态代理生成过多的类字节等。
字节码文件中有常量池结构,其通过 ClassLoader 加载到内存后,就称为运行时常量池。常量池中包含:数值、字符串值、类引用、字段引用、方法引用。
JDK 7 及以前
JDK 8 及以后
设置元空间初始化大小,当空间用尽时,会触发一次 GC,GC后会根据元空间中的数据来增加(GC 后可用空间任然较小)或减少(GC 后可用空间剩余很多)大小。默认大小取决于当前系统的可用内存。
设置元空间最大可用大小。默认:不限制。虽然不限制,可用空间也取决于当前系统本身的可用大小。所以在实际应用中,我们一般不设置最大可用大小。