JVM总结-JVM内存区域

来源:互联网 发布:我国大数据市场规模 编辑:程序博客网 时间:2024/05/16 18:58

人人都知道的java的一大优点就是不需要程序员去显示的分配内存和回收内存,这是由于虚拟机的自动内存管理机制帮我们搞定了这一切。下面我们就来看看虚拟机是如何划分内存的。 
我们将java虚拟机划分的内存区域叫做运行时数据区域。因为只有在JVM启动后才会出现这些区域,所以叫做运行时数据区域。先来看张经典的图 
JVM运行时数据区域

程序计数器

程序计数器是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。由于虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。为了在线程切换后能恢复到正确的位置,每条线程都需要有一个独立的程序计数器,相互不影响,独立存储,线程私有的。 
如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的本地方法,这个计数器为空

此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

虚拟机栈

栈的结构

每当一个方法执行完后,栈帧就会弹出栈帧的元素作为这个方法的返回值,并清除这个栈帧,java栈的栈顶就是当前执行的活动栈,也是正在执行的方法,PC寄存器里的地址也执行此。只有这个活动栈的本地变量可以被操作栈使用,当这个栈帧中又调用另外一个方法时,与之对应的一个新的栈帧又被创建,这个新的栈帧又被放到栈顶,变为活动栈帧,当这个栈帧中所有的指令执行完成后,这个栈帧移除栈,刚才的栈帧又称为活动栈帧,前面的栈帧的返回值又变为这个栈帧的操作栈中一个操作数。如果没有返回值,当前栈帧的操作栈的操作数没有变化。

堆是存储java对象的地方,是JVM管理java对象的核心区域,每个存储在堆中的java对象都是这个对象类的一方副本,它会复制包括继承自父类的所有非静态属性。堆是被所有java线程所共享的,对它的访问要注意同步问题,方法和属性都需要保持一致。

每一个java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用程序所有的线程共享。java中分配堆内存的是自动化的,所有对象的内存空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配的。所有在建立一个对象时两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。

方法区

JVM方法区是用于存储类结构信息的地方,一个class文件被解析成jvm能识别的几个部分,这些不同的部分在这个class被加载到JVM时,会被存储在不同的数据结构中,其中常量池,域,方法数据,方法体,构造函数,类中的专有方法,实例初始化,接口初始化都存在这个区域。 
其实方法区也属于堆区,我们通常称为java堆中的永久区(Permanent Generation),这个区域被所有线程共享,这个区域一般在程序启动后的一段时间内就是固定的了,JVM运行一段时间后,需要加载的类通常都已经加载到JVM中了。 
方法区的特殊点在于它不像java堆那样会被频繁地被GC回收器回收,它存储的信息比较稳定。但是它存在于java堆中,依然会被GC回收器管理。

运行时常量池

运行时常量池是方法区的一部分,class文件中除了有类的版本,字段,方法等信息外,还有一项就是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。

本地方法栈

本地方法栈是为JVM运行本地方法准备的空间,作用和java栈类似。 
以上就是虚拟机运行时数据区的分区情况。下面还要简单介绍下直接内存。直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的部分,但是这部分内存也被频繁地使用,同样会导致OutOfMemoryError异常出现。 
在java1.4后新加入了NIO(New Input/Output)类,引入了一种基于通道与缓冲区的I/O方法,可以直接使用本地函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象最晚这块内存的引用进行操作。可以显著提高性能,因为避免了java堆和Native堆中来回复制数据。

堆和栈的比较

  • 堆主要存放对象,而栈主要用来执行程序
  • 堆的优势是可以动态的分配内存大小,生存期不收编译器控制。
  • 栈中数据大小与生存期有关,缺乏灵活性
  • 堆的内存动态分配内存,灵活性好、但这也导致了存取速度较慢。
0 0
原创粉丝点击