作为java程序员,因为有虚拟机的自动内存管理,所以不需要再向C和C++程序员那样灾区写delete和free方法,但是java中是不是就不存在内存泄露问题呢,答案是否定的,java中一样存在内存泄漏的问题。所以我们需要了解虚拟机是怎样使用内存的。
Java虚拟机在执行Java程序的过程中会将管理的内存划分不同的区域,以作不同的用途,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。
通常许多人只把java的内存简单的划分为堆区,栈区,静态区,这样的分法十分不准确,java内存区域的划分远比这复杂。但这两个内存区往往就是程序员最关注的内存区
根据《Java虚拟机规范(第2版)》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域。
1.方法区
2.堆区
3.虚拟机栈区
4.地方方法栈
5.程序计数器
方法区:
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
Java虚拟机栈区:
用于存储局部变量(java中的八种基本数据类型boolean、byte、char、short、int、float、long、double),对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置),操作栈,动态链接,方法出口等信息,每一个方法被调用直接执行至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈道出栈的过程。
本地方法栈:
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
堆区(Java Heap):
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。对象的引用都存放于栈中,对象的实例都存在于堆中。Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
为了方便java实现对不同分代采取垃圾收集算法,也为了垃圾收集算法的需要,堆区的内存也有很细的划分。
Heap区可以分为三代(不同虚拟机也有不同的划分):
新生代YoungGeneration
老年代OldGeneration
永久代PermanentGeneration
YoungGeneration代由一个Eden(存放新生代的区)区和两个Survivor(属于新生代,为了复制算法的需要)区所组成。新创建的对象的内存都分配自Eden。两块Survivor Space总有会一块是空闲的,用作copying collection的目标空间。。Minor collection的过程就是将eden和在用survivor space中的活对象copy到空闲survivor space中。
OldGeneration代由一个Tenured组成,它和YoungGeneration代一样,都是为了保存由java类而生成的内存对象。每个对象有“对象年龄计数器”。对象由Eden收集到Survivor区后,年龄+1。进行新生代GC后,年龄+1。依次,当年龄>=15后进入老年代。
PermanentGeneration代由一个Permanent区组成,用于存放不变对象,如类、方法、字符串等。
需要注意的:在java8中已经没有PermGen其中的某些部分,如被intern的字符串,在Java 7中已经移到了普通堆里。
java中垃圾回收分多级,0级为全部的垃圾回收,会回收OLD段位中的垃圾;1级或以上为部分垃圾回收,只会回收Young中的垃圾,内存溢出通常发生于Old段或者Perm段垃圾回收后,仍然无内存空间
程序计数器:
是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。