JVM实战:JVM运行时数据区包含哪几部分? JVM的作用是啥JVM有2个特别有意思的特性语言无关性和平台无关性。语言无关性是指实现了Java虚拟机规范的语言对可以在JVM上运行如Groovy和在大数据领域比较火的语言Scala因为JVM最终运行的是class文件只要最终的class文件复合规范就可以在JVM上运行。平台无关性是指安装在不同平台的JVM会把class文件解释为本地的机器指令从而实现Write OnceRun AnywhereJVM运行时数据区Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途以及创建和销毁的时间有的区域随着虚拟机进程的启动而存在有些区域则依赖用户线程的启动和结束而建立和销毁。Java虚拟机所管理的内存将会包括以下几个运行时数据区域其中方法区和堆是所有线程共享的数据区。程序计数器虚拟机栈本地方法栈是线程隔离的数据区画一个逻辑图程序计数器程序计数器是一块较小的内存空间它可以看作是当前线程所执行的字节码的行号指示器为什么要记录当前线程所执行的字节码的行号直接执行完不就可以了吗因为代码是在线程中运行的线程有可能被挂起。即CPU一会执行线程A线程A还没有执行完被挂起了接着执行线程B最后又来执行线程A了CPU得知道执行线程A的哪一部分指令线程计数器会告诉CPU。虚拟机栈虚拟机栈存储当前线程运行方法所需要的数据指令返回地址。虚拟机栈描述的是Java方法执行的内存模型每个方法在执行的同时都会创建一个栈帧用于存储局部变量表操作数栈动态链接方法出口等信息。每个方法从调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。局部变量表存储存储局部变量是一个定长为32位的局部变量空间。其中64位长度的long和double类型的数据会占用2个局部变量空间Slot其余的数据类型只占用一个。引用类型new出来的对象如何存储?看下图publicintmethodOne(inta,intb){ObjectobjnewObject();returnab;}如果局部变量是Java的8种基本基本数据类型则存在局部变量表中如果是引用类型。如String局部变量表中存的是引用而实例在堆中。假如methodOne方法调用methodTwo方法时 虚拟机栈的情况如下当虚拟机栈无法再放下栈帧的时候就会出现StackOverflowError演示一下publicclassJavaVMStackSOF{privateintstackLength1;publicvoidstackLeak(){stackLength;stackLeak();}publicstaticvoidmain(String[]args)throwsThrowable{JavaVMStackSOFoomnewJavaVMStackSOF();try{oom.stackLeak();}catch(Throwablee){System.out.println(stack length: oom.stackLength);throwe;}}}在idea中设置运行时的线程的堆栈大小为如下-Xss 参数的作用是设置每个线程的堆栈大小运行输出为-Xss参数的值越大打印输出的深度越大操作数栈接着解释一下操作数栈还是比较容易理解的有如下一个Test类publicclassTest{publicintcalc(){inta100;intb200;intc300;return(ab)*c;}publicintgetSum(inta,intb){returnab;}}用javap反编译一下看一下getSum的字节码文件内容javap-vTestpublicintgetSum(int,int);descriptor:(II)Iflags:ACC_PUBLICCode:stack2,locals3,args_size3//操作数栈大小为2本地变量表大小为3入参有3个0:iload_1// 局部变量1压栈1:iload_2// 局部变量2压栈2:iadd// 栈顶2个元素相加计算结果压栈3:ireturnLineNumberTable:// 指令与代码行数的偏移关系line17:0LocalVariableTable:// 局部变量表// 作用域开始位置作用偏移长度槽位变量名类型描述StartLengthSlotNameSignature040thisLcom/javashitang/jvm/Test;041aI042bI当Java类编译完成时操作数栈本地变量表的大小就已经确定了。操作数栈的大小为2本地变量表有3个参数thisab。其中this对象是jvm隐式传递的哈入参有3个thisjvm隐式传递abLineNumberTable和LocalVariableTable我用jclasslib Bytecode viewer插件查看字节码比较方便我一般不用javap命令来解释一下可以看到Test类的第17行代码对应的是getSum方法指令的第一行getSum方法有3个局部变量this作用范围在[Start PC, Start PCLength]在局部变量表的第0个位置类型为Test类图示如下假如getSum方法的入参是long则局部变量表如下64位长度的long和double类型的数据会占用2个局部变量空间Slot其余的数据类型只占用一个publiclonggetSum(longa,intb){returnab;}注意看局部变量表b的位置从2变为3了因为a变量从原来占用一个slot变为占用2个slot以calc方法的执行演示一下程序计数器操作数栈局部变量表是如何协同工作的publicintcalc(){inta100;intb200;intc300;return(ab)*c;}calc方法的字节码如下执行流程图示如下可能有小伙伴们对指令的作用不太熟悉我就简单介绍一下一般情况下指令的格式有如下2种形式操作指令操作指令 操作数istore 100 将一个数值从操作数栈存储到局部变量表中的第100位istore_1 将一个数值从操作数栈存储到局部变量表中的第1位为什么istore_1不写成istore 1或者将istore 100写成istore_100?因为将一个数值从操作数栈存储到局部变量表中的第1位这个操作经常发生如果用istore_1则会占用1个字节如果用istore 1会占用2个字节所以用istore_1可以节省空间。同时一个字节表示的种类数有限128个Java中各种操作指令占用1字节所以istore_n这种形式不能表示所有操作类型只能一少部分指令用istore_n其余的用istore n这种形式所以你现在理解了上图中指令的偏移量不是连续的原因了吧与操作类型相关的指令会在最开头表明操作的类型bipush将一个常量加载到操作数栈istore将一个数值从操作数栈存储到局部变量表iload将一个局部变量加载到操作栈iadd对操作数栈栈顶的2个只进行加法运算并将结果重新存入操作数栈栈顶动态链接Java有些方法类加载的过程中就能知道具体执行的逻辑而有些需要在运行的过程中才能确定具体执行的逻辑多态这就是动态链接在起作用具体的实现没太看懂就不过多分析了。本地方法栈本地方法栈Native Method Stack与虚拟机栈锁发挥的作用是非常相似的他们之间的区别不过是虚拟机栈为虚拟机执行Java方法也就是字节码服务而本地方法栈则为虚拟机使用到的Native方法服务。Java堆对于大多数应用来说Java堆Java Heap是Java虚拟机锁管理的内存中最大的一块。Java堆是所有线程共享的一块内存区域在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例几乎所有的对象实例都在这里分配内存方法区方法区Method Area与Java堆一样是各个线程共享的内存区域它用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。JVM内存模型由颜色可以看出jdk1.8之前堆内存被分为新生代老年代永久带jdk1.8及以后堆内存被分成了新生代和老年代。新生代的区域又分为eden区s0区s1区默认比例是8:1:1元空间可以理解为直接的物理内存