jvm系列(一)运行时数据区
C++程序员肩负着每一个对象生命周期开始到终结的维护责任。Java程序员则可以借助自动内存管理机制,不需要自己手动去释放内存。由虚拟机进行内存管理,不容易出现内存泄漏和内存溢出的问题,但是一旦出现这些问题,就需要我们了解虚拟机的原理,才能排查解决这些内存问题。另外,jvm也是面试中常问的问题,因此我希望在者一系列的博客中进行相关内容的梳理。
jvm的知识主要分成三大板块:运行时数据区与垃圾回收,类的加载机制(重点是双亲委派模型等),Java内存模型与线程。
对于第一部分运行时数据区与垃圾回收,下面这个脑图总结的不错(来自朋友分享,未知出处,如果作者介意请在下方评论,我回删掉它)。
本篇是第一篇,首先来看一下什么是内存溢出?什么是内存泄漏?它们之间有什么区别和联系?
内存溢出和内存泄漏
内存溢出:程序运行过程中无法申请到足够内存,导致的错误。内存溢出通常发生与OLD段(老年代)或Perm段(永久代)垃圾回收后,仍然无内存空间容纳新的java对象的情况。
内存泄漏:程序中动态分配内存给一些临时对象,但是对象不会被GC回收,所以始终占用内存。及被分配的对象可达,但已经无用(由于主流的java虚拟机是通过可达性分析算法来标记垃圾对象的,因此如果是可达的对象,就不会被标记为垃圾,也不会被回收)
内存溢出与内存泄漏的关系:内存泄漏是内存溢出的一种原因。大量的内存泄漏会导致内存溢出。当然,内存溢出也可能是其他原因所导致的。
这里先简单介绍这两个概念,后续会有专门的博客阐述。
程序计数器(Program Counter)
注意:下文中凡是线程私有的运行时数据区,都不会涉及线程同步的问题。
程序技术器是线程私有的(为了线程切换后能恢复正确的执行位置,因此它必须是线程私有的)
作用:指示和记录线程的执行情况,字节码解释器工作时通过改变这个计数器的数值来选取下一条需要执行的字节码指令。它是当前线程所执行字节码的行号指示器。
注意:是唯一一个不会出现OOM异常的区域。
如果线程正在执行一个java方法,则这个计数器记录的是正在执行的虚拟机字节码的指令地址。如果是native方法,则该计数器为空。
虚拟机栈和本地方法栈 :
特点:线程私有
虚拟机栈的功能:每个方法在执行的时候都会在虚拟机栈中创建一个栈帧。栈帧存储内容:局部变量表,操作数栈,动态链接,方法出口等信息。每个方法执行到完成就对应了栈帧的压栈和出栈
注意,由于局部变量表是存放在虚拟机栈中,而虚拟机栈是线程私有的,所以局部变量都是线程安全的,局部变量不存在同步问题。
本地方法栈与虚拟机栈的区别:本地方法栈为native方法服务,虚拟机栈为java方法(也就是字节码)服务,有的虚拟机直接把这两个栈合二为一。
可能出现的异常:StackOverflowError和OutOfMemoryError
Java堆
特点:线程共享,java虚拟机管理内存中的最大一块内存。是垃圾收集器管理的主要区域。
作用:所有的对象实例和数组都在堆上分配内存。
现代收集器都采用分代收集算法,从内存回收角度看,可以分成新生代和老年代。
java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。
可能出现的异常:在堆中没有多余内存完成实例分配,而且堆也无法扩展时,将会抛出OutOfMemoryError异常。
方法区与运行时常量池
特点:线程共享。
功能作用:存放已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
java虚拟机规范对方法区的限制非常宽松。可以和堆一样不需要连续内存和可以选择固定大小和可扩展。,还可以选择不实现垃圾收集。方法区内存回收目标是针对常量池的回收和对类型的卸载。
方法区和运行时常量池可能出现的异常:OutOfMemoryError
运行时常量池:
功能:运行时常量池是方法区一部分,用于存放编译期生成的各种字面量和符号引用。翻译出来的直接引用也存储在运行时常量池中
特点:动态性。常量不一定只有编译期才能产生,运行期间也可能将新的常量放入池中。
直接内存
不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。
jdk1.4引入了NIO,可以通过基于通道和缓冲区的IO方式,使用native函数库直接分配堆外内存(NIO也是一个重点,后续会有专门博客阐述)。这里我们只需要知道NIO是可以在堆外创建直接内存的。
可能出现的异常:OutOfMemoryError。
参考:https://blog.csdn.net/u012813201/article/details/73793668
深入理解jvm