很多人会误以为 Java 内存区域和内存模型是同一个货色,其实并不是。
Java 内存区域 是指 JVM 运行时将数据分区域存储,简略的说就是不同的数据放在不同的中央。通常又叫 运行时数据区域。
Java 内存模型(JMM)定义了程序中各个变量的拜访规定,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
1、Java 内存区域
1.8 之前:
JDK1.8(含)之后:
区别就是 1.8 有一个 元数据区 代替 办法区 了。
JDK 1.7 其实是并没齐全移除办法区,然而不会像 1.6 以前报“java.lang.OutOfMemoryError: PermGen space
”,而是报 java.lang.OutOfMemoryError: Java heap space
1.7 局部内容(比方 常量池、动态变量有办法区转移到了堆)
那么,Java 8 中 PermGen 为什么被移出 HotSpot JVM 了?我总结了两个次要起因(详见:JEP 122: Remove the Permanent Generation):
- 因为 PermGen 内存常常会溢出,引发宜人的 java.lang.OutOfMemoryError: PermGen,因而 JVM 的开发者心愿这一块内存能够更灵便地被治理,不要再经常出现这样的 OOM
- 移除 PermGen 能够促成 HotSpot JVM 与 JRockit VM 的交融,因为 JRockit 没有永恒代。
依据下面的各种起因,PermGen 最终被移除,办法区移至 Metaspace,字符串常量移至 Java Heap。
援用自 https://www.sczyh30.com/posts…
上面逐个介绍一下 jvm 管辖的这几种内存区域。
2、程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,因为 JVM 能够并发执行线程,因而会存在线程之间的切换,而这个时候就程序计数器会记录下以后程序执行到的地位,以便在其余线程执行结束后,复原现场继续执行。
JVM 会为每个线程调配一个程序计数器,与线程的生命周期雷同。
如果线程正在执行的是应该 Java 办法,这个计数器记录的是正在执行虚拟机字节码指令的地址。
如果正在执行的是 Native 办法,计数器的值则为空(undefined)
程序计数器是惟一一个在 Java 虚拟机标准中没有规定任何 OutOfMemoryError 状况的区域。
3、Java 虚拟机栈
虚拟机栈 形容的是 Java 办法执行的内存模型:
每个办法在执行的同时都会创立一个栈帧(Stack Frame,是办法运行时的根底数据结构)用于存储局部变量表、操作数栈、动静链接、办法进口等信息。每一个办法从调用直至执行实现的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈是每个线程独有的,随着线程的创立而存在,线程完结而死亡。
在虚拟机栈内存不够的时候会OutOfMemoryError
,在线程运行中须要更大的虚拟机栈时会呈现StackOverFlowError
。
虚拟机栈蕴含很多 栈帧,每个办法执行的同时会创立一个栈帧,栈帧又存储了办法的局部变量表、操作数栈、动静连贯和办法返回地址等信息。
在流动线程中,只有位于栈顶的栈帧才是无效的,称为 以后栈帧,与这个栈帧相关联的办法称为以后办法。
1)局部变量表
局部变量表是寄存 办法参数 和局部变量 的区域。
全局变量是放在堆的,有两次赋值的阶段,一次在类加载的筹备阶段,赋予零碎初始值;另外一次在类加载的初始化阶段,赋予代码定义的初始值。
而局部变量没有赋初始值是不能应用的。
2)操作数栈
一个先入后出的栈。
当一个办法刚刚开始执行的时候,这个办法的操作数栈是空的,在办法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作。
3)动静连贯
每个栈帧都蕴含一个指向运行时常量池中该栈帧所属办法的援用。持有这个援用是为了反对办法调用过程中的动静连贯(Dynamic Linking)。
常量池能够便于指令的辨认
public void methodA(){}
public void methodB(){methodA();//methodB()调用 methodA(), 先找到调用 methodA()的版本符号,再变为间接援用}
办法调用并不等同于办法执行,办法调用阶段惟一的工作就是确定被调用办法的版本 (即调用哪一个办法),这也是 Java 弱小的扩大能力,在运行期间能力确定指标办法的 间接援用。
所有办法调用中的指标办法在 Class 文件外面都是一个常量池中的符号援用,在类加载的解析阶段,会将其中的一部分符号援用转化为间接援用。
4)办法返回地址(办法进口)
返回分为 失常返回 和 异样退出。
无论何种退出状况,都将返回至办法以后被调用的地位,这也程序能力继续执行。
一般来说,办法失常退出时,调用者的 PC 计数器的值能够作为返回地址,栈帧中会保留这个计数器值。
办法退出的过程相当于弹出以后栈帧。
4、本地办法栈
Java 虚拟机栈是调用 Java 办法;本地办法栈是调用本地 native 办法,能够认为是通过 JNI
(Java Native Interface) 间接调用本地 C/C++ 库,不受 JVM 管制。
本地办法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异样
5、Java 堆
Java 堆是被所有 线程共享 的一块内存区域,在虚拟机启动时创立。此内存区域的惟一目标就是寄存对象实例,简直所有的对象实例都在这里分配内存。
堆是垃圾收集器治理的次要区域,又称为“GC 堆”,能够说是 Java 虚拟机治理的内存中最大的一块。
当初的虚拟机(包含HotSpot VM)都是采纳分代回收算法。在分代回收的思维中,把堆分为:新生代 + 老年代 + 永恒代(1.8 没有了);新生代 又分为 Eden + From Survivor + To Survivor 区。
6、办法区
办法区(Method Area)与 Java 堆一样,是所有 线程共享 的内存区域。
办法区用于存储曾经被虚拟机加载的类信息(即加载类时须要加载的信息,包含版本、field、办法、接口等信息)、final 常量、动态变量、编译器即时编译的代码等。
办法区逻辑上属于堆的一部分,然而为了与堆进行辨别,通常又叫“非堆”。
办法区比拟重要的一部分是 运行时常量池 (Runtime Constant Pool),为什么叫运行时常量池呢?是因为运行期间可能会把新的常量放入池中,比如说常见的 String 的 intern() 办法。
String a = "I am HaC";
Integer b = 100;
在编译阶段就把所有的字符串文字放到一个常量池中,复用同一个(比如说上述的“I am HaC”),节俭空间。
对于办法区和元空间的关系:
办法区是 JVM 标准概念,而永恒代则是 Hotspot 虚拟机特有的概念,简略点了解:办法区和堆内存的永恒代其实一个货色,然而办法区是蕴含了永恒代。
只有 HotSpot 才有“PermGen space”,而对于其余类型的虚拟机,如 JRockit(Oracle)、J9(IBM)并没有“PermGen space”
7、元空间
1.8 就把办法区改用元空间了。类的元信息被存储在元空间中。元空间没有应用堆内存,而是与堆不相连的 本地内存区域。所以,实践上零碎能够应用的内存有多大,元空间就有多大,所以不会呈现永恒代存在时的内存溢出问题。
能够通过 -XX:MetaspaceSize
和 -XX:MaxMetaspaceSize
来指定元空间的大小。
8、总结:
参考:
- https://www.cnblogs.com/czwbi…
- https://blog.csdn.net/xyh9309…
- https://www.cnblogs.com/paddi…
- 《深刻了解 Java 虚拟机》
- 《码出高效》