前言
最近在看重温JVM相关的知识点,但总感觉看书很枯燥乏味,看别人的博客知识又显得零碎,知识体系不够系统,所以有必要把所见所闻所学的点,好好地梳理成文,加强自己理解程度的同时,挖掘自身的盲点。好了,鸡血不宜多,让我们开始JVM的新篇章。
话说平时开发中的我们有曾想过写过的代码运行在哪儿,比如在类中声明了static类型的变量,在方法中new了一个对象等等……如果这些都不了解,我只能说不是个合格的码农。再一个线上环境出现的JVM性能问题,比如卡顿,吞吐量低,请求延迟高等问题时,光是会写代码完全无从下手解决问题。怎么去解决生产环境里出现的各种“奇葩”问题?今天先讲讲代码中的变量、对象、类信息等在JVM中的存在。
运行时数据区
计算机中的程序运行在硬件上,无非是数据与指令相互作用,共同协调得完成某件事情。在JVM中也不例外,所以这里我将JVM的内存区域按功能分为数据区和指令区(这里的指令区其实也是数据),看图:
- 数据区:方法区和堆
- 指令区:程序计数器、虚拟机栈和本地方法栈
咱们一个个来
程序计数器
程序计数器记录的是当前线程正在执行的字节码指令的地址,可以比作当前线程所执行的字节码的行号指示器,需要注意的地方是执行Native方法时,计数器的值就是空的。每个线程都有独立的程序计数器,所以计数器属于线程私有的一小块内存。
虚拟机栈
虚拟机栈存储的当前线程运行方法所需要的数据、指令、返回地址。即每个方法在执行的同时都会创建一个栈帧存储局部变量、操作数栈、动态链接、方法出口等信息。方法的调用到完成就对应栈帧的入栈和出栈的过程。
如果线程请求的栈深度大于虚拟机所允许的深度,程序将栈溢出(StackOverflowError)异常。
很明显,虚拟机栈也是线程独享的内存空间。
本地方法栈
本地方法栈存储调用本地方法(native)所需要的数据指令,区别于虚拟机栈的普通方法。也是线程私有的空间。
方法区
此区域JDK1.8已移除,替代的是metaspace,本文暂不纠结。
- 1
- 2
方法区用于存储虚拟机加载的类信息、常量、静态变量、JIT即时编译的代码(如动态代理生成的类信息)等数据,这些数据共同特点是难以改变,而且对象存活时间长。在GC分代中,方法区又称为永久代,后续的文章会有介绍。
显而易见,类信息等等数据都不会只属于某一个线程,所以是线程共享的区域。当方法区无法满足内存分配资源时,会抛出OutOfMemoryError异常。
堆
堆唯一的目的就是存放对象的实例,比如new产生的对象,所有的对象以及数组都要在堆上分配内存,所以堆中的内存自动管理机制尤为重要。需要注意的是堆与方法区存放对象的区别是对象的存活时期不同,堆中98%对象属于朝生熄灭的对象,至于对象是怎么被回收的,后续再讲。
堆中没有内存分配给对象实例时,也会抛出OOM异常。属于线程共享的区域。
按照是否被线程共享的角度来看,如下图:
- 上图很好的说明了线程与各区域的关系
- 程序计数器、虚拟机栈和本地方法栈属线程私有,随着线程而生,也随着线程而死
- 方法区和堆属于线程共享的区域,既然共享了就容易出现数据竞争等线程安全问题,并发安全也主要是解决堆中对象数据安全
好了,这篇文章算是简单的梳理了java对象、变量、类信息等在JVM内存区域的存储位置,但前提是在程序运行时
才有的数据区。