JVM内存区域
1. 程序计数器
线程私有的,可以看作是当前线程说执行的字节码的行号指示器, 保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址) ,保证每个线程都在线程切换后能够恢复在切换之前的程序执行位置 。
在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值为空是(undefined)。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。
2. Java栈(虚拟机栈)
线程私有的,描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表:存放了编译期可知的各种基本类型(int,char,long,booblean,double,byte,short,float)、对象引用(reference类型,可能是一个指向对象起始地址的引用指针,也可能是指向一个对象的句柄或其他与此对象相关的位置)、returnAddress类型(指向了一条字节码指令的地址)。
3.本地方法栈
和虚拟机栈区别就是,虚拟机栈是为Java的方法(就是字节码)服务的,而本地方法栈是为虚拟机用到的Native方法服务的。
4.Java堆
Java堆是所有线程间共享的一块内存区域,在虚拟机启动时就创建。用于存放对象的实例,几乎所有对象实例都在这里分配内存,但随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换(11章)等优化技术导致所有对象都分配在堆上的结论也不是那么绝对了。
Java堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器基本都采用分代收集算法,所以可细分为新生代和老年代,再细一点可分为:Eden区、From Survivor区、To Survivor区和老年代。
补充:通过-Xms(最小)和-Xmx(最大堆)可控制堆的大小。
5.方法区
各个线程线程间共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时的常量池是方法区的一部分。class文件中除了有类的版本、字段、方法、接口等信息,还有常量池:用与存放编译期生成的各种字面量和符号引用。
PS:JDK8后为什么用元空间替代永久代?
元空间和永久代的区别:元空间是使用的是操作系统的内存,永久代使用的是JVM的内存。
为什么要在直接内存里拿出来一块内存作为元空间取代永久代呢?主要的说法有以下几个:
(1)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
(2)永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
即方便分配管理,因为直接内存空间比较充足;便于回收,因为永久代本来回收垃圾的事件发生概率很低,直接从拿到系统内存中可以提高回收效率。
方法区与永久代的关系
很多文章里喜欢把方法区等同与永久代,永久代既然没了,方法区也就没了。但我认为方法区只是一种逻辑上的概念,永久代指物理上的堆内存的一块空间,这块实际的空间完成了方法区存储字节码、静态变量、常量的功能等等。既然如此,现在元空间也可以认为是新的方法区的实现了。