目录
JVM内存共被分为五个部分:
1.程序计数器
本质:程序计数器是一块很小的内存空间,它是线程私有的,即每个线程都有属于自己的一个程序计数器。可以作为当前线程的行号指示器。
为什么需要程序计数器
一条线程中会有多条指令,但对于一个处理器,在同一个时刻,在一个确定的时刻都只会执行一条线程中的指令,为了在线程切换后,该线程可以恢复到原本运行的位置,每个线程都需要有一个独立的程序计数器。不同线程之间的程序计数器互不影响。如果执行的是java方法,那么记录的是正在执行的虚拟机字节码指令的地址的地址,如果是native方法,计数器的值为空。
2.Java 栈(虚拟机栈)
也是线程私有的。
每个方法在执行的时候,都会创建一个栈帧,栈帧存储了方法的局部变量,操作数,动态链接,方法返回地址。
线程没调用一个方法,都会把该方法对应的栈帧入栈(虚拟机栈),当该方法运行结束后,该方法的栈帧就会出栈。
2.1 局部变量表
局部变量表是栈帧重要组中部分之一。它主要用于保存函数的参数和函数的局部变量和对象引用。局部变量表中的变量的作用域是当前调用的函数,当函数调用结束后,局部变量表也会随着栈帧的销毁而消失。由于局部变量表存储函数的参数和局部变量,因此如果函数的局部变量太多,会使得局部变量表膨胀,导致每一次调用会占用更多的栈空间。最终结局就是栈空间内存一定的情况下调用的次数减少。
由于局部变量表所需的内存空间是在编译期间确定的。因此在进入一个方法时,该方法的栈帧中的局部变量表空间是完全已经确定的了,所以在方法运行期间无法通过动态方法改变。
2.2 操作数栈
1.操作数栈的栈深由编译期确定。
2.栈帧刚建立时,操作栈为空。
3.方法运行时,操作栈用于存放JVM从局部变量表中复制的常量或变量。或接收方法返回的结果。
4.操作数栈可用于运算,虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。比如,iadd指令就要从操作数栈中弹出两个整数,执行加法运算,其结果又压回到操作数栈中
2.3 动态链接
每个栈帧都保存了 一个 可以指向当前方法所在类的 运行时常量池, 目的是: 当前方法中如果需要调用其他方法的时候, 能够从运行时常量池中找到对应的符号引用, 然后将符号引用转换为直接引用,然后就能直接调用对应方法, 这就是动态链接
2.4 方法返回地址
当一个方法开始执行时,会有两种情况导致方法返回:
1. 方法正常返回(遇到返回指令):方法正常退出时,调用者的PC计数器的值可以作为栈帧的返回地址。让该方法调用结束后,能根据返回地址找到程序调用该方法之前的运行位置。
2.异常返回:当程序发生异常且没有处理时,程序也会返回,这时候的返回地址是要经过异常处理器确定的。
3.本地方法栈
本地方法栈和虚拟机栈非常相似,都是线程私有的。区别在于,虚拟机栈是为虚拟机执行Java方法服务的,而本地方法栈是为虚拟机执行Native方法服务的。
Native方法:Native方法并非由Java语言实现,而是由其他语言写的,例如C/C++写好后,让Java程序调用。
4. Java堆
堆是Java内存最大,且管理最复杂的区域。
1. Java堆的目的:存放创建(new)的所有对象。
2.不同于Java栈,Java堆是所有线程共享的。即并不是每个线程都有自己的一个堆空间。因此多线程的时候,也需要同步机制。
Java 堆 = 新生代+老年代。
新生代:存放刚刚创建的新对象。
老年代:由于在堆内存中,会触发GC(垃圾回收),如果有新生代的对象在数次GC中活了下来没被清除,那么就会进入到老年代
新生代 = 伊甸区+s0区+s1区
伊甸区:存放最新创建的对象。当发生GC时,没被清除的对象会移到s0区,然后擦除伊甸区和s1区,然后又发生GC时,会把伊甸区+s0中存活的对象移到s1区,然后擦除伊甸区和s0区,循环往复,每次存活下来的对象的年龄都会+1,年龄超过一定阈值,就会进入老年代。
5.方法区
方法区和堆都是线程共享的内存区域,为了和堆区分,方法区又叫非堆。
方法区用于存储被虚拟机加载的 类信息、常量、静态变量。如用 static 修饰的变量会被加载到方法区中。
5.1 运行时常量池
运行时常量池是方法区的其中一个区域。
Java的常量池分两种形态:静态常量池和运行时常量池
静态常量池
静态常量池即*.class文件中的常量池,里面包括 字符串字面量、类、方法信息。这种常量池一般存放两大类常量:字面量和符号引用量。
字面量:如文本字符串,声明final的常量值。
符号引用量:如 类和接口的全限定名、字段名称和描述符、方法名称和描述符。
运行时常量池
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中