程序计数器 Program Counter Register
生命周期:与线程相同
占用空间:非常小
多线程:线程私有
存储:当前栈帧的下一步操作指令的地址,及当前线程执行的字节码的行号
GC和Error:没有GC和OOM
Java的多线程是通过轮流切换并分配处理器执行时间的方式来实现,任何一个时刻,一个处理器(多核处理器指一个内核)都只会执行一条线程中的指令。为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程计数器互不影响,独立存储。栈帧为java方法,存储字节码指令的地址;执行Native方法为空(Undefined)。
图中的操作指令可由【javap -v class文件名】指令得到
Java虚拟机栈
生命周期:与线程相同
占用空间:可设置(-Xss256k)
多线程:线程私有
存储:栈帧(局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息)
GC和Error:没有GC,StackOverflowError(大小固定),OutOfMemoryError(可扩展)
局部变量表 local variables
数组,存储方法的参数和在方法中定义的局部变量,数据类型包括基本数据类型、对象引用(reference类型,不是对象本身,可能是指向对象起始地址的指针)以及returnAddress类型(指向一条字节码指令的地址)。
其中64位bite的long和double占两个局部变量空间(Slot),其余的数据占用一个。编译期可确定容量大小,且运行期不会。表中的变量只在当前方法调用中有效。方法嵌套调用的次数由栈大小决定。栈越大,方法嵌套嗲用次数越多。非静态方法在局部变量表索引位0的位置默认放this变量。
下图为jclasslib解析的图,以及javap -v LocalVariableTest.class反编译的文件信息
补充:
变量分类:
按照数据类型:1.基本数据类型 2.引用数据类型
按照在类中的声明位置:
1.成员变量:在使用之前都经历过默认初始化赋值
Static修饰类变量:linking的prepare阶段赋默认值;initial阶段显示赋值
实例变量:随着对象的创建,在对空间中分配实例变量空间,并默认赋值
2.局部变量:使用前,必须要显示赋值。否则编译不通过
在栈帧中,局部变量表与性能调优关系最密切。执行时,完成方法传递
局部变量表中的变量是重要的垃圾回收根节点,只要变量被引用则不会被回收
操作数栈 operand stack
数组实现,但是栈只有后进先出的操作。
存储计算过程中的中间结果,同时作为临时存储空间。
在编译期确定栈深度,存放与code属性中的max_stack值。一旦创建,长度不变。
32bit的类型占用一个栈深度,64bit占用两个栈深度
在方法之心过程中,根据字节码指令,往栈中写入或者提取数据,及入栈push/出栈pop。
若方法有返回值,其返回值将会被压入当前栈帧的操作数栈中。
动态链接 dynamic linking
每个栈帧都包含一个执行运行常量池中栈帧所属方法的引用。在字节码文件中,所有的变量和方法的引用都作为符号引用保存在class文件的常量池中。动态链接就是将这些符号引用转换为调用方法的直接引用。
静态链接:被调用目标方法在编译期可知,运行期保持不变。
动态链接:被调用目标方法在编译期不可知,运行期将符号引用转为直接引用。
对应的方法绑定机制:
早期绑定:被调用目标方法在编译期可知,运行期保持不变。
晚期绑定:被调用目标方法在编译期不可知,只能在运行期根据实际类型确定
动态语言:判断变量值得类型信息。Pthon,js
静态语言:判断变量的类型信息。Java
引用了运行时常量池,运行时常量池在方法区
方法返回地址 return address
存放PC寄存器的值,及上层方法的下一次操作执行的指令值。用于恢复上层方法执行。
一些附加信息
可选,不一样有。例如:支持程序调试的信息。
本地方法栈 Native Method Stack
调用本地方法,native修饰的方法为本地方法。具体的虚拟机具体实现。会抛出异常
堆 Heap Area
生命周期:虚拟机启动时创建
占用空间:java堆是Java虚拟机管理的内存中最大的。可处于物理上不连续的内存空间,逻辑上连续即可,像磁盘。在开发中,可设置固定大小,也可以是可扩展的,不过主流的虚拟机都是按可扩展来实现。参数-Xms设置堆空间(新生代加老年代)的初始化内存大小,默认值物理内存/64,-Xmx 设置最大内存大小,默认值物理内存/4。建议开发中初始和最大设置为一样的值,避免频繁扩充影响性能
多线程:线程共享
存储:存放实例对象,几乎所有的对象实例都在这里分配内存(随着JIT编译器的发展和逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化)
GC和Error:java堆是垃圾回收器管理的主要区域,也被称为“GC堆”。存在OOM
细分:从垃圾回收角度看,GC基本都采用分带收集算法,所以可分为新生代(Eden区、From Servivor区、To Servivor区)和老年代。从内存分配的角度看,还可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB,参数-XX:UserTLAB默认开启,紧占Eden区的1%)。无论如何划分,都与存放无关,存储的仍是对象实例,划分目的是为了更好的回收,挥着更快的分配内存。
查看参数设置的方式
方式一:运行时参数设置-Xms600m -Xmx600m -XX:PrintGCDetails
方式二:cmd中运行指令jsp / jstat -gc 进程id
配置新生代老年代在堆得结构占比
-XX:NewRation=2,表示新生代占1,老年代占2,新生代占整个堆得1/3,默认值
-XX:NewRation=4,表示新生代占1,老年代占4,新生代占整个堆得1/5
可在命令行用【jinfo -flag NewRation 进程id】查看当前NewRation的值
新生代细分为ede:s1:s2=8:1:1 受自适应-XX:UseAddaptiveSizePolicy参数影响
要想实现此比例,需设置-XX:SurvivorRation=8
新对象分配过程(考虑内存分配和GC)
阔值默认15(年龄计数器),可设置:-XX:MaxTenuringThreshold=<N>
YGC/Minor GC触发条件:新对象进来了,但是Eden区满了(S0/S1被动回收)。经过统计分析,80%的对象在新生代朝生夕死
触发Full GC的情况:
(1)调用System.gc()
(2)老年代空间不足:-XX:HandlePromotionFailure空间分配担保
(3)方法区空间不足
补充:
-XX:+PrintFlagsInitial 查看所有的参数的默认值
-XX:+PrintFlagsInal 查看所有的参数的最终值(可能存在修改,不在是初始值)
-XX:MaxTenuringThreshold=<N> 设置新生代垃圾的最大年龄
-XX:HandlePromotionFailure是否设置空间分配担保
执行Minor GC判断老年代可连续使用空间是否大于Eden区所有对象总空间
大于:执行Minor GC是安全的的
小于:执行Full GC
-XX:+HandlePromotionFailure查看老年代可连续使用空间是否大于晋升到老年代的平均大小(大于:执行Minor GC 小于:执行Full GC)
-XX:-HandlePromotionFailure执行Full GC
-XX:+DoEscaprAnalysis 逃逸分析。默认开启
-XX:+EliminateAllocations 标量替换。默认开启
为什么有TLAB?堆空间是线程共享的,会出现线程安全问题,需要加锁,会影响分配速度。
逃逸分析(TaoBaoVM -> GCIH)?是否在方法外部被调用,被调用则存在逃逸。
逃逸分析进行代码优化?栈上分配、同步省略、标量替换
方法区 Method Area
栈、堆、方法区的交互关系
多线程:线程共享
存储:虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
占用空间:第二大,参数设置JDK1.8前后有变化
GC和Error:有GC和异常OOM
方法区的演进
Jdk1.6及以前:有永久代(permanent generation),静态变量存放在永久代
JDK1.7 PermGenSpace:有永久代,但已经逐步‘去永久代’,字符串常量池、静态变量移除,保存在堆空间
JDK1.8及以后:无永久代,类型信息、字段、方法、常量保存在本地内存元空间,静态变量、字符串常量池仍在堆空间
空间设置变化
JDK1.7及以前:方法区的内存空间包含在虚拟机的内存中,可设置上限,比较容易出现OOM问题(-XX:PermSize=100m初始值,默认21m;-XX:MaxPermSize=100m最大值)
JDK1.8及以后 MetaSpace:元空间使用的是本地内存。-XX:Metaspacesize=100m(初始值,windows下默认21m);-XX:MaxMetaspacesize=100m(初始值,默认-1,无上限)
由于-XX:Metaspacesize会根据GC动态调整,建议设置大一些,避免多次Full GC
永久代为什么要被元空间替换?
08年收购了JRockit进行融合,所以移除了永久代。
因为永久代大小很难确定。例如某个web工程中,因为功能点多,加载的类就很多,经常出现OOM。而元空间使用的是本地内存。
堆永久代调优比较困难,回收效率很低。方法区的垃圾回收主要针对常量池的回收和对类型的卸载,尤其是堆类型的卸载,条件相当的苛刻(实例和子类实例、类加载器、该类的Class对象没有引用才允许回收)。而元空间用本地内存,可有效减少Full GC。
为什么StringTable要调整?
因为在jdk1.7中,永久代只有在Full GC的时候才会回收。但是在实际开发当中会生成很多的字符串,回收效率低,导致永久代内存不足。放到堆里,能及时回收。