jvm内存空间
程序计数器
每个线程启动的时候,都会创建一个PC(Program Counter,程序计数器)寄存器。PC寄存器里保存有当前正在执行的JVM指令的地址。 每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。保存下一条将要执行的指令地址。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
- 线程私有。是一块较小的内存,是当前线程所执行的字节码的行号指示器。是Java虚拟机规范中唯一没有规定OOM(OutOfMemoryError)的区域。
- 如果执行的是一个Native方法,那这个计数器是空的。
使用pc寄存器存储字节码指令地址有什么用?
因为cpu需要不停的切换各个线程,这个时候切换回来以后,就得知道接着从哪开始继续执行。
pc寄存器为什么设定为线程私有的呢?
因为需要给每个线程单独的记录它们各自的下一条指令的地址
Java栈
线程私有。生命周期和线程相同。是Java方法执行的内存模型。执行每个方法都会创建一个栈帧,用于存储局部变量和操作数(对象引用)。局部变量所需要的内存空间大小在编译期间完成分配。所以栈帧的大小不会改变。存在两种异常情况:若线程请求深度大于栈的深度,抛StackOverflowError。若栈在动态扩展时无法请求足够内存,抛OOM。
栈帧的结束存在两种情况,第一种是正常的返回return,第二种是抛出异常,如果在方法里面没有try catch操作,会将异常抛给前一个帧。直到最后的main方法。
栈内存
主管程序的运行,声明周期和线程同步:线程结束,栈内存也就释放了,对于栈而言不存在垃圾回收机制。
栈帧
- 局部变量表
长度在编译阶段就已经确定,而且运行过程中不会改变。成员变量(类变量、实例变量)在使用前都经历过默认初始化赋值(linking的prepare阶段默认赋值.initial阶段显示赋值)局部变量在操作前,必须显示赋值才能使用。 - 操作数栈
如果被调用的方法具有返回值,其返回值将会被压入当前栈帧的操作数栈中 栈顶缓存技术:将栈顶元素全部缓存在寄存器中 - 动态链接
将符号引用转换为调用方法的直接引用。动态链接和静态链接:前者编译期间确定不了,只有在运行期间才能确定。后者在编译阶段就能确定绑定。 - 方法返回地址
存放调用该方法的pc寄存器的值
Java堆
所有线程共享。虚拟机启动时创建。存放对象实力和数组。所占内存最大。分为新生代(Young区),老年代(Old区)。新生代分Eden区,Servior区。Servior区又分为From space区和To Space区。Eden区和Servior区的内存比为8:1。 当扩展内存大于可用内存,抛OOM。
(1)新生代:所有新 new 出来的对象都会最先出现在新生代中,当新生代这部分内存满了之后,就会发起一次垃圾收集事件,这种发生在新生代的垃圾收集称为 Minor collections。 这种收集通常比较快,因为新生代的大部分对象都是需要回收的,那些暂时无法回收的就会被移动到老年代。全局暂停事件(Stop the World):所有小收集(minor garbage collections)都是全局暂停事件,也就是意味着所有的应用线程都需要停止,直到垃圾回收的操作全部完成。当然对象进入老年代的方式不只有这一种,在此之外我们还会给每个对象设置一个age属性,当age大于某个设定值后就放入老年区。又或者如果一个对象十分巨大,我们会直接将对象放入老年代中,防止其将新生代占用过大而导致频繁gc。
(2)老年代:老年代用来存储那些存活时间较长的对象。 一般来说,我们会给新生代的对象限定一个存活的时间,当达到这个时间还没有被收集的时候就会被移动到老年代中。随着时间的推移,老年代也会被填满,最终导致老年代也要进行垃圾回收。这个事件叫做大收集(major garbage collection)。大收集也是全局暂停事件。通常大收集比较慢,因为它涉及到所有的存活对象。所以,对于对相应时间要求高的应用,应该将大收集最小化。此外,对于大收集,全局暂停事件的暂停时长会受到用于老年代的垃圾回收器的影响。
(3)永久代:永久代存储了描述应用程序类和方法的源数据,JVM 运行应用程序的时候需要这些源数据。 永久代由 JVM 在运行时基于应用程序所使用的类产生。 此外,Java SE 类库的类和方法可能也存储在这里。如果 JVM 发现有些类不在被其他类所需要,同时其他类需要更多的空间,这时候这些类可能就会被垃圾回收。
TLAB
jvm在进行对象分配的时候一般都是以下步骤
其中在栈上分配的优点主要有快,不需要gc,当函数执行完了就退出。但是栈内存有限,而且适合在栈中分配的对象需要通过逃逸分析,简单说就是其作用域应该局限在特定范围。然后就只能在共享区域堆进行分配。而堆是线程共用的,因此可能会有多个线程在堆上申请空间,而每一次的对象分配都必须线程同步,会使分配的效率下降。考虑到对象分配几乎是Java中最常用的操作,因此JVM使用了TLAB这样的线程专有区域来避免多线程冲突,提高对象分配的效率。TLAB就是每个线程私有的一部分空间。但是这个空间比较小,所以较大的对象不能存放进入。
方法区
所有线程共享。用于存储已被虚拟机加载的类信息、常量、静态变量等数据。又称为非堆(Non – Heap)。方法区又称“永久代”。GC很少在这个区域进行,但不代表不会回收。这个区域回收目标主要是针对常量池的回收和对类型的卸载。当内存申请大于实际可用内存,抛OOM。
本地方法栈
线程私有。与Java栈类似,但是不是为Java方法(字节码)服务,而是为本地非Java方法服务。也会抛StackOverflowError和OOM。而且native和abstract关键字是不兼容的。