JVM之自动内存管理机制(一)
前言
这段时间准备整理下JVM的知识,了解Java就必须了解java虚拟机的特性,本节先从Java的自动内存管理机制来讲起,有一句话:Java与C++之间有一堵由内存动态分配和垃圾手机技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。
概述
在c和c++中的内存管理领域,程序员拥有最高权力的皇帝,拥有每一个对象的所有权,又担负着每一个对象生命开始到结束的责任。对于java程序员来说,在虚拟机的自动内存管理机制的帮助下,不再需要为每一个new对象配对delete/free代码,而且不太容易出现内存泄漏和溢出。正是因为这样,Java程序员没有了掌控权,排除问题的时候将是异常艰难的工作。了解内存的各个区域,是翻越这个高墙的第一步。
运行时数据区域
虚拟机在执行java程序的时候会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途、创建和销毁的时间。如下图所示:

程序计数器
- 是一个较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。
- 字节码解释器工作时就是通过改变这个计数起的值来选取下一条需要执行的字节码指令。
- 分支、循环、跳转、异常处理、线程恢复等基础功能都依赖计算器。
- 多线程时通过线程轮流切换并分配处理器执行时间的方式来实现的。
- 由于在任何一个确定的时刻,一个处理器只能处理一个线程,所以每个线程应该需要一个独立的程序计数器,所以它被称为线程私有。
- 如果正在执行native方法,程序计数器为空。
虚拟机栈
- 也是线程私有,生命周期和线程一样。
- 虚拟机栈描述的是Java方法执行的内存模型:每个方法一行的时候都会产生一个栈帧,它用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法调用直到完成就对应一个栈帧在虚拟机栈入栈道出栈的过程
- 局部变量表存放了编译器可知的各种数据类型,包括基本类型和引用类型(这里只是一个地址)和retrunAddress类型(指向一条字节码指令的地址)。
- 在这个区域定义了两个异常:StackOverflowError异常,栈深度大于虚拟机所允许的栈深度,栈溢出,和OutOfMemoryError,即OOM,表示动态扩展的虚拟机无法申请到足够内存时会抛出的异常。
本地方法栈
其实只不过是区别于虚拟机栈所执行方法的不同,虚拟机栈执行Java方法,本地方法栈执行native方法。比如hotspot虚拟机就把本地方法栈和虚拟机合二为一。
Java堆
- 是虚拟机所管理的内存中的最大一块,Java堆是被所有线程共享的一块区域,在虚拟机启动时创建。
- 存放的都是对象的实例,几乎所有的对象实例都在这里分配内存。随着及时动态编译器jit的发展和逃逸分析技术的逐渐成熟,所有的对象都分配在堆上也不是那么绝对。
- Java堆事垃圾回收的主要区域,也被称为GC堆。垃圾回收一般采用分代收集算法,堆中细分的话可分:新生代、老生代或者、Eden、From Survivor、To Survivor等。
- 线程共享的Java堆可能划分出多个线程私有的分配缓冲区,不过存放内容都是对象,进一步划分事为了更好的回收对象。
方法区
- 和Java堆一样,是各个线程共享的区域,用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。是堆的一个逻辑部分,但是别名NonHeap,非堆。
- 垃圾收集行为在此区域较少发生,但并非永久代,对常量池的回收和类的卸载。
- 此区域无法满足内存分配时,会抛出OOM异常。
- 运行时常量池时方法区的一部分,编译期已经确定的字面量和符号引用,类加载完之后就存放到方法区的运行时常量池中。
- 不一定都在编译的时候放入常量池,比如string的intern()方法,可把新的常量放入池中。常量池也会报OOM异常。
直接内存
- 直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范汇总的内存区域,但是被频繁使用,而且可能导致OOM异常,比如NIO,引用了一种基于通道与缓冲区的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能显著的提高性能,因为避免了在Java堆和native堆中来回复制数据。
- 显然直接内存不会受到堆大小的限制,但是内存都收到系统内存限制。也可能出现OOM。
对象访问
我们分析一下下面这个简单代码的运行过程:
Object obj=new Object();
这句代码中,Object obj 这部分的语义将会反映到java栈的本地变量表中,作为一个ref引用类型数据出现,而new Object()这部分语义反映到堆中,形成一块存储了object类型所有实例数据值的结构内存,具体类型以及虚拟机实现的对象内存分布不同。另外,对象类型、父类、实现的接口、方法等信息存储在方法区中,保证能查找到对象类型数据的地址信息。
主流的访问方式有两种:
- 使用句柄方式,堆中还会划分出一个内存来作为句柄池,ref中存储的就是对象的句柄地址,句柄中又包含对象实例和对象类型的指针。好处是存储的是稳定的地址,在对象移动时只改变句柄中的,不用改变ref的。
- 使用直接指针访问方式,ref中直接存储就是对象地址。最大好处就是速度更快,节省了一次指针定位的时间。hotspot就是采用这种。
说明
文中出现的图片,文字描述有些来自互联网,但是出处无法考究,如果侵犯您的相关权益,请联系我,核实后我会马上加上转载说明。谢谢!!!