若想使自己编写的Java程序高效运行,以及进行正确、高效的异常诊断,JVM是不得不谈的一个话题。本”JVM进阶“专栏大部分内容均来源于经典书籍《深入理解Java虚拟机》。
栈
下面言归正传,本文重点从虚拟机内存模型(运行时数据区域)入手。先看图:
这是一张比较官方的虚拟机模型图,今天讲的就是虚线框中栈的部分。
栈是我们最常用的内存区域。它主要用来存放基本类型变量,局部变量以及对象的引用。例如:User user = new User();这里的user就是对象的引用也可以理解为地址,指引着虚拟机要去哪里找user这个对象。 他们的基本关系如图:
由图可知,当我们将一个对象作为方法的参数时,我们在方法中改变对象的值,也会影响到原来的对象的值,因为我们只是改变了图中内存区域的值,他的指引(地址)还是一样的。同时也可以看出,栈的内存区域是连续的,有大小限制的,如果超过了就会抛出栈溢出的异常StackOverflowError。
在每个方法执行的时候,都会创建一个个的栈帧,用于保存局部变量表,操作数栈,动态链接等信息(以后都会详细讲解)。每次方法的调用都会对应着一个栈帧,因此可以解释有我们在写递归程序的时候会不小心报栈溢出的异常,因为栈是有限的,方法调用太多次导致栈帧堆满了栈,所以溢出。看下面代码:
在参数-Xss128k的情况下的报错。(eclipse中设置参数:右键代码选择Run As–>Run Configurations,在Arguments栏下的VM arguments中填入参数,再Apply,再run)
每次在方法执行完毕的时候,虚拟机会自动释放掉为该栈所分配的空间,在栈中,对应着一个栈帧的出栈。虚拟机会自动分配与回收内存,因此效率比较高。
最后做一下栈的总结:
- 存放基本类型变量,局部变量,对象的引用;
- 系统自动分配与回收内存,效率较高,快速,存取速度比堆要快;
- 是一块连续的内存的区域,有大小限制,如果超过了就会栈溢出,并抛出栈溢出的异常StackOverflowError;
Java会自动释放掉为该变量所分配的内存空间;
栈又分为java栈和本地方法栈。顾名思义,本地方法栈自然就是为本地方法提供服务的,java栈是为java服务的。
注意,JVM栈是每个线程私有的!
堆
对象就存在图中的内存区域,在JVM中,那片区域叫做堆!
由图中可以看到堆的存储结构和栈是不同的,堆在内存中并不是一块连续的区域,他是分散的(物理上是分散,但逻辑上是连续的,大家好好体会一下);虚拟机通过栈中引用的指引在堆中找到所需要的对象。
在虚拟机遇到一条new的指令的时候,经过一系列的操作过后(现在讲的话会看不懂)虚拟机就要为该新生对象分配内存空间了,那么问题来了,这么散,虚拟机要怎么知道如何分配呢?分配的方式有两种:指针碰撞和空闲列表。
指针碰撞是将内存逻辑上分为两边,一边是空闲的,一边是在用的,指针指向分界点,当需要分配内存的时候只要移动指针即可。但这种只适用于内存规整的情况下,也就是刚刚说的分两边。一般用在Serial,PaeNew等垃圾收集器中,也就是堆中的新生代中。(最后一句话会在后面分几章讲,道路遥远着!)
那么空闲列表说的就是在内存不是规整的情况下,虚拟机必须维护一个列表,用于记录哪些内存是可用的,在需要进行分配的时候就从列表中找到一块足够大小的空间进行分配,并且更新列表。又要讲一句看不懂的话:该方法适用于像CMS这种基于Mark-Sweep的垃圾收集器,适用于堆中的年老区!
上两段都提到了垃圾收集器,也就是GC。写过java的都知道,java程序很少需要我们去自己释放资源,原因就是这个GC机制了。
好啦,今天就不多说啦,好好理解一下上文,只要知道一下几点就OK啦!
- 存放new创建的对象和数组;
- 在运行时动态分配内存(比如 new()),较慢,但灵活;
- 是不连续的内存区域,在发出申请的时候,会干嘛干嘛的。。。。
- 由Java虚拟机的自动垃圾回收器来管理。