Java虚拟机在运行Java程序的过程中,会把总的内存划分为多个内存数据区域,包括:程序计数器、虚拟机栈、本地方法栈、堆、方法区。每个内存数据区域功能、特点各不相同。
程序计数器是内存占用较小的一块区域,属于线程私有,可以看做是字节码执行的行号指示器。
●字节码解释器工作是就是通过改变程序计数器的值来获取下一条需要执行的字节码指令。
●程序计数器属于线程私有,故每个线程都有自己的计数器,在线程上下文切换时可通过保存计数器的值实现对线程的恢复执行。
●线程私有,且内存占用小。
●是Java内存区域中唯一一个不会出现OutOfMemoryError异常的区域。
●线程执行Java方法时,计数器保存的是字节码指令地址;执行Native方法时,计数器的值为空。
虚拟机栈属于线程私有,生命周期和线程一致。虚拟机栈描述的是Java方法执行的内存模型:每个Java方法在执行时会在虚拟机栈中生成一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个Java方法从调用到执行完毕,就意味着一个栈帧在虚拟机栈中的入栈和出栈。
●局部变量表所需的内存空间在编译期就已完成分配,Java方法在执行时,局部变量表在栈帧中所需的空间是确定的,在不会再发生改变。
●虚拟机栈可能出现的两种错误情况:
本地方法栈描述的是本地方法的内存模型,其与虚拟机栈的作用非常相似。
●与虚拟机栈一样,本地方法在执行时也会在本地方法栈中创建栈帧。
●HotSpot虚拟机将本地方法栈和虚拟机栈合二为一。
●本地方法栈也会抛出StackOverFlowError、OutOfMemoryError异常。
Java堆的唯一目的就是存放对象实例,绝大多数对象实例都在这里分配内存。
●堆是Java内存区域中占用内存最大的一块区域。
●堆是垃圾收集的主要区域,因此也被称为“GC堆”。
●由于内存回收的需要,Java堆还被细分为新生代和老年代,新生代又可细分为:Eden、From Survivor、To Survivor。
●堆是线程共享的,即所有的线程都可以访问堆里的数据。
●堆可处于不连续的物理内存中,只要逻辑上是连续即可。
●堆的大小既可固定,也可扩展,但当堆中内存不足分配,且堆无法再扩展时,将抛出OutOfMemoryError异常。
●方法区用于存储加载过的类、常量、静态变量、即时编译器编译过的代码。
●方法区被描述为堆的一个逻辑部分,又因为方法区垃圾回收的条件苛刻(即方法区的内存不易被回收),故有“永久代”的说法。
●方法区是线程共享的。
●垃圾收集行为在方法区较少出现。
运行时常量池是方法区的一部分,class文件中的public static final类型的常量在编译时就被存放在方法区中。此外方法区也可动态扩展,比如String.intern()方法在运行时可讲字符串常量入池。
●直接内存并不属于Java内存区域的组成,但也可被Java程序使用。
●NIO引入了一种基于通道和缓存的I/O方式,它可以通过本地方法直接分配Java内存区域之外的内存,并且通过Java堆中的DirectByteBuffer对象进行操作,在某些场景可提高效率。
●直接内存不足也会抛出OutOfMemoryError异常。