1、Java内存模型与常量池
1.1、Java内存模型
-
程序计数器
多线程时,当线程数超过CPU数量或CPU内核数量,线程之间就要根据时间片轮询抢夺CPU时间资源。因此,每个线程要有一个独立的程序计数器,记录下一条要运行的指令,其为线程私有的内存区域。如果执行的是JAVA方法,计数器记录正在执行的java字节码地址,如果执行的是native方法,则计数器为空。
-
Java虚拟机栈
Java虚拟机栈是线程私有的,与线程在同一时间创建,是管理JAVA方法执行的内存模型。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象引用。每个方法执行时都会创建一个栈祯来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出stackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。使用jclasslib工具可以查看class类文件的结构。下图为栈帧结构图:
-
本地方法栈
和Java虚拟机栈功能相似,但管理的不是JAVA方法,是本地方法,本地方法是用 C 实现的。
-
Java堆
线程共享的,存放所有对象实例和数组,是垃圾回收的主要区域。堆是一个运行时数据区,类的对象从中分配空间,这些对象通过new、newarray、 anewarray 和 multianewarray等指令建立,它们不需要程序代码来显式的释放。堆可以分为新生代和老年代(tenured)。新生代用于存放刚创建的对象以及年轻的对象,如果对象一直没有被回收,生存得足够长,老年对象就会被移入老年代。新生代又可进一步细分为eden(伊甸园)、survivorSpace0(s0,from space)、survivorSpace1(s1,tospace)。刚创建的对象都放入eden,s0和s1都至少经过一次GC并幸存。如果幸存对象经过一定时间仍存在,则进入老年代(tenured)。
-
方法区
线程共享的,用于存放被虚拟机加载的类的元数据信息:如常量、静态变量、即时编译器编译后的代码,也成为永久代。如果hotspot虚拟机确定一个类的定义信息不会被使用,也会将其回收。回收的基本条件至少有:所有该类的实例被回收,而且装载该类的ClassLoader被回收。
1.2、常量池
常量池属于类信息的一部分,而类信息反映到 JVM 内存模型中对应于方法区,也就是说,常量池位于方法区。常量池主要存放两大常量:字面量(Literal) 和 符号引用(Symbolic References)。其中,字面量主要包括字符串字面量,整型字面量 和 声明为final的常量值等;而符号引用则属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名;
- 字段的名称和描述符;
- 方法的名称和描述符。
2、常量和变量
- 我们一般把内存地址不变,值可以改变的东西称为变量,换句话