JVM学习——内存分配

       在说明JVM内存分配之前,先说明java程序运行的过程,首先.java文件被java编译器编译成.class字节码文件,然后通过JVM中的ClassLoader类加载器,将字节码文件加载进内存 ,加载完毕后,由JVM的执行引擎执行,在执行过程中,JVM会使用一些空间来保存执行过程所用到的数据和信息,这段空间一般被称为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。在Java中的内存管理一般指的就是对这个区域的管理。

    一般JVM内存包含以下几个部分:

  

    1.程序计数器:它与物理的CPU寄存器所拥有的功能是相同的,都是用来保存下一个指令所在的存储单元的地址,可以看作当前线程指向下一个可执行字节码的行号指示器。在JVM中每个线程都是轮流获取CPU的执行权,也就是说在同一时间,只能有一个线程来执行指令,为了保证线程在CPU切换过程中能够恢复程序之前执行的位置,因此每个线程都有一个程序计数器,并且是每个线程私有的。当线程执行非native方法时,程序计数器保存的时下一条指令的存储单元的地址,当执行native本地方法时,程序计数器的值为undefined。

    2.java栈:java栈是由一个个栈帧组成的,每个栈帧对应一个方法,包括局部变量表、操作数栈、常量池引用、方法返回地址,当一个线程调用一个方法时,就会创建一个栈帧,并将栈帧压栈,当方法执行完毕,就会将栈帧出栈,由此可以看出,当前运行的方法所在栈帧一定是位于栈顶。

       经常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗 糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最 关注的、与对象内存分配关系最密切的内存区域是这两块。“栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中局部变量表部分。

       局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对 象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 returnAddress类型(指向了一条字节码指令的地址)。 其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据 类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这 个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变 量表的大小。

       在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚 拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部 分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。  

    3.本地方法栈:它与java栈非常类似,只不过java栈是为java方法提供服务的,而本地方法栈是为native本地方法提供服务的。

      在虚拟机规范中对本地方法栈中方法使用的语言、使用方式 与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚 拟机(譬如 Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法 栈区域也会抛出StackOverflowError和OutOfMemoryError异常。 

    4.堆:java堆是用来存储对象以及数组,并且是被所有线程共享的。

    5.方法区:在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量(如文本字符串)和符号引用(如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等)。

       在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

       在JVM规范中,没有强制要求方法区必须实现垃圾回收。很多人习惯将方法区称为“永久代”,是因为HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将运行时常量池从永久代移除了。

      6.直接内存。直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规 范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现。 在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓 冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储 在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。 显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制), 从而导致动态扩展时出现OutOfMemoryError异常。

       本文部分片段来自《深入理解JAVA虚拟机》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值