局部变量表
存储基本数据类型、对象引用、returnAddress类型。
以变量槽存储,其中long和double类型占用两个变量槽,其他的均占一个。
运行时,局部变量表大小不变
操作数栈
操作数栈(Operand Stack),每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的操作数栈,也可以称之为 表达式栈(Expression Stack)
动态链接
方法出口
native方法
Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。它是 JVM 管理的最大一块内存空间,是线程共享的。
是否所有的实例对象均在堆上创建?
以下两种方式可以实现不在堆上创建。
栈上分配
JIT编译器在编译期间根据逃逸分析的结果,发现如果一个对象并没有逃逸出方法的话,就可能被优化成栈上分配。分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就无须进行垃圾回收了。
逃逸分析的基本行为就是分析对象动态作用域:
标量替换
在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替,这个过程就是标量替换。
举例:
public static void main(String args[]) { alloc(); } class Point { private int x; private int y; } private static void alloc() { Point point = new Point(1,2); System.out.println("point.x" + point.x + ";point.y" + point.y); }
经过标量替换后,就会变成
private static void alloc() { int x = 1; int y = 2; System.out.println("point.x = " + x + "; point.y=" + y); }
当时这个个人理解为算一种内存占用的优化,因为,最终并没有创建Point对象,所以不能说标量替换导致对象不在堆上创建。
类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotateion),JVM 必须在方法区中存储以下类型信息:
域(field)信息
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
域信息包括:
域名称
域类型
域修饰符(public、private、protected、static、final、volatile、transient的某个子集)
方法信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
方法名称
方法的返回类型(或void)
方法参数的数量或者类型(按顺序)
方法的修饰符(public、private、protected、static、final、synchronized、native、abstract的一个子集)
异常表(abstract和native方法除外)
常量
即时编译器编译后的代码缓存
TLAB:Thread Local Allocation Buffer,也就是为每个线程单独分配了一个缓冲区。
由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的,为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。因此,如果每个线程均有一个私有的缓冲区域,就可以再该区域快速创建出一个对象,而避免了繁重的加锁机制。
同时,由于TLAB的存在,对于堆空间来说,并不是全部是共享的了。
公众号:慢行的蜗牛