赞
踩
Java内存模型,全称Java Memory Model(JMM)。
我们都知道创建一个对象需要分配内存空间并且在不需要该对象时及时回收内存。仔细回想,我们似乎并没有为我们new创建的每一个对象来编写过分配内存空间及回收内存相应的代码。但对象仍然很好的被回收了,原因是Java程序将内存的控制权交给了JVM虚拟机,所以在不了解虚拟机的内存机制的情况下,如果出现了内存泄漏与溢出,那么排查错误将是一个非常艰巨的任务。
要想知道JVM如何规划内存,首先要知道数据在内存中如何储存,当然不同类型的数据在内存中所存储的区域也不同。那么我们先带大家了解一下不同数据在内存中所储存的相应位置。
JVM在执行Java程序时会将它管理的内存划分为不同的数据区域。而这些区域在JDK1.8前后又有所变化。
线程共享 | Heap堆区、Method Area方法区 |
线程私有 | 程序计数器、虚拟机栈、本地方法栈 |
JDK1.8后:
线程私有 | Heap堆区、MetaSpace元空间 |
线程私有 | 程序计数器、虚拟机栈、本地方法栈 |
程序计数器是用于存放下一条指令所在单元的地址的地方。每执行一条指令,程序计数器就会加一。每个线程都会维护一个独立的程序计数器且各线程之间的程序计数器互不影响,在程序执行过程中,线程会不断的切换,独立的线程计数器保证了当前线程的正确执行位置。程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它随着线程的创建而创建,随着线程的结束而消亡。
Java虚拟机栈是线程私有的,它是由许多栈帧组成,而每个栈帧又包括了局部变量表、操作数栈、动态链接以及方法出口信息。每次方法调用都会将对应的栈帧压入虚拟机栈,当方法调用结束(方法调用return或者方法抛出异常)又会将该栈帧从虚拟机栈中弹出。由于栈的特性(FILO),每次操作的都是栈顶栈帧,又被称为“当前活动栈帧”,代表当前正在执行的方法。在JVM执行引擎运行时,所有指令都针对于当前活动栈帧进行操作。
Java虚拟机栈会出现的错误:
StackOverFlowError | jvm规定了虚拟机栈的最大深度,当执行当前线程时栈帧压入的深度大于了规定的深度,就会抛出该错误,一般是由于递归导致的无限嵌套调用递归方法。 |
OutOfMemoryError | JVM的内存大小可以动态扩展,如果虚拟机在扩展栈时无法申请到足够的内存空间,就会抛出该错误。 |
native关键字修饰的方法被称为本地方法,当线程调用本地方法时,会在本地方法栈中压入当前本地方法的栈帧。该栈帧中包含本地方法的局部变量表、操作数栈、动态链接、方法出口信息。当方法执行完毕时,栈帧会从本地方法栈中弹出,与虚拟机栈相同也会出现StackOverFlowError与OutOfMemoryError错误。
堆区是JVM所管理的内存中最大的一块区域,该区域被所有线程共享,堆区用于存放了大部分对象实例以及数组,随着 JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(未逃逸出去),那么这部分对象可以直接在栈上分配内存。
Heap堆区又分为新生代和老年代,Heap堆区是垃圾收集器GC管理的主要区域。从垃圾回收的角度来看,垃圾收集器基本采用分代收集垃圾的思想,所有JVM的堆区往往采用分代划分的思想。
分代划分目的:更好的回收与分配内存
元空间是用于存放类信息、常量、静态变量、JIT即时编译器编译后的机器代码等数据。在JDK1.6时,HotSpot JVM采用Method Area方法区来储存这些数据,也叫永久代(持久代)。
方法区与永久代(持久代)的区别:
JDK1.7时将字符串常量池、静态变量转移到了堆区
JDK1.8时采用MetaSpace代替了永久代(持久代)
元空间与永久代(持久代):
相同点 | 都是对JVM规范方法区的一种实现 |
不同点 | 永久代(持久代)在虚拟机中,元空间在本地内存 |
永久代(持久代)内存受永久代(持久代)的GC与老年代的内存空间限制,元空间大小受本地内存限制 |
Java8后HotSpot JVM为什么要删除永久代(持久代)?
- 由于永久代(持久代)内存受限范围较小,经常会发生内存溢出
- 由于JRockit VM没有永久代(持久代),移除HotSpot JVM的永久代(持久代)可以促进HotSpot JVM与JRockit VM的融合
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。