1. Java内存结构(6)
Java内存结构即运行时数据区域,Java虚拟机在执行Java程序的过程中,会把它管理的内存划分为几个不同的数据区域,这些区域都有各自的用途、创建时间、销毁时间
1.1 PC寄存器/程序计数器
程序计数器用于保存当前正在执行的程序的内存地址,以便线程的中断和恢复,每个线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储
1.2 栈
- 每当创建一个线程,JVM就会为该线程创建对应的Java栈,Java栈中包含多个栈帧,栈帧是与每个方法关联起来的,每运行一个方法就创建一个栈帧,每个栈帧会含有一些局部变量、操作栈和方法返回值等信息
- 每当一个方法执行完成时,该栈帧就会弹出栈帧的元素作为这个方法的返回值,并且清除这个栈帧,Java栈的栈顶的栈帧就是当前正在执行的活动栈,也就是当前正在执行的方法,PC寄存器也会指向该地址
- 只有活动的栈帧的本地变量可以被操作栈使用,当在这个栈帧中调用另外一个方法时,与之对应的一个新的栈帧被创建,这个新创建的栈帧被放到Java栈的栈顶,变为当前的活动栈。同样现在只有这个栈的本地变量才能被使用,当这个栈帧中所有指令都完成时,这个栈帧被移除Java栈,刚才的那个栈帧变为活动栈帧,前面栈帧的返回值变为这个栈帧的操作栈的一个操作数
- Java栈是与线程对应起来的,Java栈数据不是线程共有的,所以不需要关心其数据一致性,也不会存在同步锁的问题
- 在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverflowError
异常;如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError
异常
1.3 堆
- 堆是JVM所管理的内存中最大的一块,是被所有Java线程锁共享的,不是线程安全的,在JVM启动时创建
- 堆是存储Java对象的地方,Java虚拟机规范中,所有的对象实例以及数组都要在堆上分配
- Java堆是GC管理的主要区域,从内存回收的角度来看,由于现在GC基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代再细致一点有Eden空间、From Survivor空间、To Survivor空间等
1.4 方法区
- 方法区存放了要加载的类的信息(名称、修饰符等)、类中的静态常量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当在程序中通过Class对象的
getName
、isInterface
等方法来获取信息时,这些数据都来源于方法区 - 方法区是被Java线程锁共享的,不像Java堆中其他部分一样会频繁被GC回收,它存储的信息相对比较稳定,在一定条件下会被GC,当方法区要使用的内存超过其允许的大小时,会抛出
OutOfMemory
的错误信息 - 方法区也是堆中的一部分,就是我们通常所说的Java堆中的永久区
Permanet Generation
1.5 常量池
- 常量池本身是方法区中的一个数据结构
- 常量池中存储了如字符串、final变量值、类名和方法名常量
- 常量池在编译期间就被确定,并保存在已编译的.class文件中
- 一般分为两类:字面量和应用量;字面量就是字符串、final变量等;类名和方法名属于引用量;引用量最常见的是在调用方法的时候,根据方法名找到方法的引用,并以此定为到函数体进行函数代码的执行;引用量包含:类和接口的权限定名、字段的名称和描述符,方法的名称和描述符
1.6 本地方法栈
- 本地方法栈和Java栈所发挥的作用非常相似,区别不过是Java栈为JVM执行Java方法服务,而本地方法栈为JVM执行Native方法服务;本地方法栈也会抛出
StackOverflowError
、OutOfMemoryError
异常
2. Java内存模型(JMM)
2.1 PC硬件缓存机制
- 高速缓存(cache)来作为内存与处理器之间的缓冲,将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中;在多处理器系统中,每个处理器都有自己的高速缓存,并且共享同一主存
- Java虚拟机内存模型中定义的内存访问操作与硬件的缓存访问操作是具有可比性的
22. Java内存模型
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与Java编程里面的变量有所不同,它包含了实例字段、静态字段和构成数组对象的元素,但不包含局部变量和方法参数,因为后者是线程私有的,不会共享,当然不存在数据竞争问题(如果局部变量是一个reference引用类型,它引用的对象在Java堆中可被各个线程共享,但是reference引用本身在Java栈的局部变量表中,是线程私有的)
JMM规定了所有的变量都存储在主内存(Main Memory)中,每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)
不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。
2.1 主内存与工作内存
在Java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享。局部变量(Local variables),方法定义参数和异常处理器参数不会在线程之间共享。JMM决定一个线程对共享变量的写入何时对另一个线程可见,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量
3. Java对象模型
Java对象自身的存储模型称之为Java对象模型。HotSpot虚拟机中,设计了一个OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象实例的具体类型。每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass
,保存在方法区,用来在JVM层表示该Java类。当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc
对象,这个对象中包含了对象头以及实例数据。