本文已经收录到个人Github我的博客,欢迎大佬们光临寒舍:html
个人GIthub博客java
Java
与C++
之间有一堵由内存动态分配和垃圾回收机制所围成的高墙,墙外面的人想进去,墙里面的人出不来git
对于Java
程序员来讲,JVM
给咱们提供了自动内存管理机制,不须要既当“皇帝”,又当“人民”,不须要人为地给每个new
操做写配对的delete/free
代码,不容易出现内存泄漏和内存溢出问题。然而一旦出现内存泄漏和溢出方面的问题,若是不清楚JVM
内存的内存管理机制,那么将很难定位与解决问题。并且,JVM
的内存管理机制在面试中也是很是重要的考点之一。程序员
综上,想要更加深刻了解JVM
的奥秘,探究JVM
内存管理机制是必不可少的!!!github
JVM
运行时数据区域
JVM
执行Java
程序的过程:Java
源代码文件 (.java
) 会被Java
编译器编译为字节码文件(.class
),而后由JVM
中的类加载器加载各个类的字节码文件,加载完毕以后,交由JVM
执行引擎执行面试
在上述过程当中,JVM
会用一段空间来存储执行程序期间须要用到的数据和相关信息,这段空间就是运行时数据区,也就是常说的JVM
内存算法
JVM
会将它所管理的内存划分为若干个不一样的数据区域,划分结果如图:数组
可见,运行时数据区被分为线程私有数据区和线程共享数据区两大类:缓存
Java
堆、方法区(内部包含运行时常量池)下面将为您详细介绍各个数据区的内容安全
- 若是线程正在执行的是一个
Java
方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址- 若是线程正在执行的是一个
Native
方法,那么计数器的值则为空
字节码解释器工做时,就是经过改变这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。
Java
虚拟机规范》中,是惟一一个没有规定任何 OutOfMemoryError
状况的区域Java
虚拟机栈Java
方法执行的内存模型
每一个方法在执行的同时都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接、方法出口等信息
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
局部变量表存放了编译期可知的各类基本数据类型、对象引用类型和 returnAddress
类型,它所需的内存空间在编译期间完成分配
Java
内存区分为堆内存(Heap
)和栈内存(Stack
),其中『栈』指的是虚拟机栈,『堆』指的是 Java
堆Java
虚拟机规范中,对这个区域规定了两种异常情况:
- 若是线程请求的栈深度大于虚拟机所容许的深度,将抛出
StackOverflowError
异常- 若是虚拟机栈可动态扩展且扩展时没法申请到足够的内存,将抛出
OutOfMemoryError
异常
Native
方法服务想要了解
Native
方法的读者,能够看下这篇文章:Java中native方法
StackOverflowError
和 OutOfMemoryError
异常在
Java
堆中,可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB
),但不管哪一个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存
GC 堆
”(可别叫作垃圾堆orz)Java
虚拟机所管理的内存中最大的一块Java
虚拟机规范中,若是在堆中没有内存完成实例分配,且堆也没法再扩展时,将会抛出 OutOfMemoryError
异常定义:与 Java
堆同样,是各个线程共享的内存区域
做用:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
人们更愿意把这个区域称为 “永久代”,它还有个别名叫作 Non-Heap
(非堆)
在 JDK7
的 HotSpot
中,已经把本来放在永久代的字符串常量池,静态变量移出;
在JDK8
中,废弃永久代的概念,改用元空间;
对用元空间替换永久代的缘由感兴趣的话,能够看下这篇文章:一文读懂 - 元空间和永久代
永久代/元空间
和方法区的区别:
永久代/元空间
可看做是方法区的实现
Java
堆同样不须要连续的内存和能够选择固定大小或可扩展外,还可选择不实现 GC
Java
虚拟机规范中,当方法区没法知足内存分配需求时,将抛出 OutOfMemoryError
异常
Class
文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入方法区的运行时常量池中存放
Q1:字面量是什么
能够理解为字面意思的常量。
int a; //变量
const int b = 10; //b为常量,10为字面量
string str = “hello world!”; // str 为变量,hello world!为字面量
复制代码
由例子可知,字面量就是如此容易理解
Q2:符号引用是什么
能够是任意类型的字面量。只要能无歧义的定位到目标。在编译期间因为暂时不知道类的直接引用,所以先使用符号引用代替。最终仍是会转换为直接引用访问目标
好比:java/lang/StringBuilder
Q3:运行时常量池是什么
Class
文件常量池的一个重要特征是具有动态性,体如今并不是只有预置入 Class
文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中Java
虚拟机规范中,当常量池没法再申请到内存时会抛出 OutOfMemoryError
异常Java
虚拟机规范》中定义的内存区域,可是这部份内存也被频繁地调用JAVA
堆和Native
堆中来回复制数据,所以在一些场景下能显著提升性能
JDK1.4
中新加入了NIO
类,引入了基于通道与缓冲区的IO
方式,可使用Native
函数库直接分配直接内存(堆外内存),而后经过DirectByteBuffer
做为这块内存的引用进行操做
HotSpot
虚拟机内存对象探秘在熟悉虚拟机内存划分及其具体内容以后,为详细了解虚拟机内存中数据的其余细节,以经常使用的虚拟机
HotSpot
和经常使用的内存区域Java
堆为例,探讨HotSpot
虚拟机在Java
堆中对象分配、布局和访问的全过程
遇到一个
new
指令后建立过程分三步
1.类加载检查
检查 new
指令的参数是否能在常量池中定位到一个类的符号引用且该符号引用表明的类是否已被加载、解析和初始化,若没有则需先执行相应的类加载,反之下一步
2.分配内存
- 由
Java
堆中的内存是否规整决定如何给新生对象分配可用空间- 由堆所采用的垃圾收集器是否带有空间压缩整理的能力决定
Java
堆中的内存是否规整
- 过程:将用过和空闲的内存放在两边,中间以一个指针做为分界指示器。当分配内存时,就把指针向空闲一边挪动与对象大小相等的距离便可
- 应用:Serial、ParNew 等带 压缩过程的收集器
- 过程:维护一个记录可用内存块的列表。当分配内存时,就从列表中找到一块足够大的空间划分给对象实例并更新记录
- 应用:基于
Mark-Sweep
算法的CMS
收集器
保证内存分配是线程安全的解决方案:
- 对内存分配的动做进行同步处理
- 每一个线程在
Java
堆中预先分配一块内存(本地线程分配缓冲TLAB
),在本线程的TLAB
上进行分配,当TLAB
用完须要分配新的TLAB
时再同步锁定
3.设置对象头
将对象的所属类、找到类的元数据信息的方式、对象的哈希码、对象的 GC
分代年龄等信息存放在对象的对象头中
分为三块区域
Mark Word
:用于存储对象自身的运行时数据,如哈希码、GC
分代年龄、锁状态标志、线程持有的锁、偏向线程ID
、偏向时间戳等- 类型指针:用于肯定这个对象的所属类
Java
源码中定义顺序这两个因素影响。两种主流的访问方式
经过句柄访问对象
在 Java
堆中划分出一块内存来做为句柄池,reference
存储的是对象的句柄地址,在句柄中包含了对象实例数据与类型数据各自的具体地址信息
好处:reference
中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference
自己不须要修改
经过直接指针访问对象
在 Java
堆对象的布局中考虑如何放置访问类型数据的相关信息,reference
存储的直接就是对象地址
好处:速度更快,节省了一次指针定位的时间开销
OutOfMemoryError
异常这部分的内容能够看下这篇文章:JVM内存溢出详解(栈溢出,堆溢出,持久代溢出、没法建立本地线程)
恭喜你!已经看完了前面的文章,相信你对
JVM
内存管理机制已经有必定深度的了解,下面,进行一下课堂小测试,验证一下本身的学习成果吧!
Q1:在JVM
中,为何要把堆与栈分离?栈不是也能够存储数据吗?
从软件设计的角度看,栈表明了处理逻辑,而堆表明了数据,分工明确,处理逻辑更为清晰体现了“分而治之”以及“隔离”的思想。
堆与栈的分离,使得堆中的内容能够被多个栈共享(也能够理解为多个线程访问同一个对象)。这样共享的方式有不少收益:提供了一种有效的数据交互方式(如:共享内存);堆中的共享常量和缓存能够被全部栈访问,节省了空间。
栈由于运行时的须要,好比保存系统运行的上下文,须要进行地址段的划分。因为栈只能向上增加,所以就会限制住栈存储内容的能力。而堆不一样,堆中的对象是能够根据须要动态增加的,所以栈和堆的拆分,使得动态增加成为可能,相应栈中只需记录堆中的一个地址便可。
堆和栈的结合完美体现了面向对象的设计。当咱们将对象拆开,你会发现,对象的属性便是数据,存放在堆中;而对象的行为(方法)便是运行逻辑,放在栈中。所以编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。
Q2:为啥说堆和JVM
栈是程序运行的关键
若是文章对您有一点帮助的话,但愿您能点一下赞,您的点赞,是我前进的动力
本文参考连接: