如果了解java虚拟机更系统的操作。请阅读 Charlie Hunt , Binu John著《java性能优化权威指南》,James Gosling,Java之父、Steve Wilson,Oracle公司工程副总裁写序。
Java虚拟机运行时数据区:
- PC寄存器(program counter):程序计数器线程私有的较小的内存空间,保存当前线程所执行的字节码指令地址。在虚拟机概念模型里:字节码解释器工作是通过改变计数器的值来选取下一条需要执行的字节码指令。Native方法,计数器值为undefined。
- JAVA虚拟机栈:线程私有。保存局部变量与没有算好的结果。Java栈对应操作系统实际内存的堆中分配。每个方法执行时都会创建一个栈帧。方法从调用到执行完的过程对应栈帧在虚拟机栈中入栈出栈的过程。新线程请求容量(栈深度)超过允许会抛出StackOverflowError异常,如果没有足够内存分配会抛出OutOfMemoryError异常。
- 栈帧:包括本地变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用。
- 局部变量表:完成方法调用参数的传递。第0个保存所在对象this。 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序被编译成class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的最大局部变量表的容量。
- 操作数栈:从局部变量表或对象实例的字段复制值到操作数栈。
- 动态链接:指向当前方法所在类型的运行时常量池的引用。
- 本地方法栈:线程私有。对应实际的栈。与JAVA栈类似,本地方法栈用于给Native方法服务,异常情况类似。
- 堆:线程共享。保存对象实例和数组,垃圾回收器管理的主要区域。分为新生代和老年代。新生代分为Eden、From Survivor、To Survivor。JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。老年代:JDK1.8之后的字符串常量池,在老年代。
- 方法区:即永久代,线程共享。保存类结构信息:运行时常量池、字段、方法数据、构造函数和普通方法的字节码,还包括类、实例、接口初始化时用到的特殊方法。可以回收一些废弃常量和无用的类。
- 直接内存:通过java.nio.ByteBuffer.allocateDirect(capacity);来直接操作堆外内存。避免了在java堆和Native堆中来回复制数据。
运行时常量池:加载类和接口后,创建对应的运行时常量池。保存常量、编译期间赋值的数值字面量、运行期间才能获得的方法或字段引用。运行时常量池是方法区的一部分。class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
运行时常量池相对于class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。回收方法区:废弃常量和无用的类。在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGI这类频繁自定义classLoader的场景都需要虚拟机具备类卸载的功能,以保证永久带不会溢出。