jvm内存模型
jvm内存模型
-
虚拟机栈
栈优化:默认为1M,(-Xss)
线程私有、生命周期同线程,每个方法运行都对应帧栈的建立及在虚拟机栈的入栈出栈过程。
-
局部变量表
非静态方法会在index[0]存该方法的实例引用(4字节),随后存放方法参数和局部变量(须显示初始化)。
-
操作栈
桶式结构栈,被各种指令写入和提取信息。
i++和++i的区别: i++先从局部变量表取i并压栈,然后局部变量表i自增1,再从栈顶取出i使用,因此读到的是自增前的值;++i:局部变量表i先自增1,然后取出并压栈,再从栈顶取出i使用,因此读到的是自增后的值。
i++ 这种操作不是原子操作,即使是volatile修饰也非线程安全,因为多CPU有多寄存器。三步走:1取:从局部变量表(内存)读到寄存器;2增:寄存器中自增;3更新: 写回局部变量表,这3步可能被其他线程打断而导致数据相互覆盖。
两个线程里各i++ 100次,值范围是?
2-200;线程1取i,线程2取i,都为0,1增了99次,i=99写回内存,此时线程2写回内存,内存i=1,线程1第100次读i=1,自增完为2还没写回,轮到线程2增100次,写回i=100并结束,此时线程1才把他的2写回给内存,那么最终结果就是2。 如果俩线程轮流操作,最终就是200。
-
动态链接
链接指的是一些C++、C程序,调用的方法无法在编译时确定下来,程序运行时,将方法的符号引用转为直接引用,这个过程具备动态性,所以是动态链接。(在编译时确定下来直接转为直接引用的叫静态链接)。
-
方法返回地址
-
-
本地方法栈
hotspot直接把本地方法栈和虚拟机栈合二为一。本地方法可以通过JNI(Java Native Interface)访问虚拟机运行时数据,甚至调用寄存器,如JNI著名的本地方法System.currentTimeMillis()
功能类似虚拟机栈,调用本地方法时,储存本地方法的局部变量表、操作数栈、动态链接、返回地址等。JVM调用java方法会创建帧栈并压入虚拟机栈,但调native方法(大部分是c/c++实现)帧栈不变只会简单的动态链接调用。生亡随线程的线程私有栈、也会抛出OOM、StackOverflow等错误
-
程序计数器(Program Counter Register)
线程私有,较小的内存空间,指向当前程序运行指令的地址。若执行native方法则为空,执行java方法才不为空。是JVM内存区域唯一一个不会OOM的区域。
-
方法区(也叫non-heap)
java8前Hotspot实现方法区是永久代(Perm),java8以后就移至元空间(Metaspace),原永久代的字符串常量池移至堆,其他移至元空间,元空间直接在本地分配内存。好处有:可以减少OOM的出现几率;提高GC收集效率;类信息等难以确定大小,故对于永久代大小也难以指定,太小太大都不好,移至元空间无需考虑这个问题。
线程共享,储存已被加载的类信息、常量、静态字段等,此区垃圾收集行为较少甚至不实现。
-
堆
JVM 默认堆大小:初始值为物理内存的1/64(Xms);最大值为物理内存的1/4(Xmx)
设置非堆 初始/最大 大小(默认和堆一样的规则):-XX:PermSize / -XXMaxPermSize。
线程共享,是内存最大的一块(-Xmx,-Xms来指定堆大、小),所有对象实例都在这分配内存,是GC主要的工作区域,可以分为新生代和老年代,新生代又分为Eden、To survivor、from survivor,还可以划分为多个线程私有的分配缓存区(Thread Local Allocation Buffer,TLAB)
-
直接内存
不适于JVM内的内存区域,如NIO允许使用直接内存用于数据缓存区。
JVM架构模型
JVM是运行于操作系统之上的一个进程,负责将java字节码通过JIT执行引擎翻译成各个平台的机器指令执行,因为有了JVM,所以java语言的特性就是一次编写到处运行。
JVM功能模块主要包括 类加载器 和 执行引擎,执行引擎包括即时编译器JIT、垃圾回收器GC、解释器。JVM运行java程序的(类的加载、执行)过程:写的JAVA源程序通过编译器编译(javac xxx.java)为字节码,在JVM中经过类装载器(java xxx.class)将字节码加载到内存中,由字节码校验器进行检查验证,验证通过后由解释器翻译成对应平台的机器码进行逐行解释执行,解释到哪就执行到哪。为了优化解释器执行效率,就有了JIT编译器,JIT能检测热点代码,一次性转为机器码缓存起来,避免逐行执行低效率问题。HotSpot默认用解释器+C2编译器(也可以分层编译)。
java编译器输入指令流是基于栈的指令集架构,其优点是 实现简单、避免了寄存器分配难题、操作都是依赖于栈的零地址指令(不设地址字段的指令)、指令集较小、不需要硬件支持、可移植性好。
基于寄存器指令集的架构: 如x86二进制指令集: PC和Davilik虚拟机(android),依赖于硬件、可移植性差,但性能更高、完成同一操作的指令更少(以一/二/三指令指令地址为主)。
考虑到java的可移植性,选用基于栈的指令集架构