JVM内存区域如何划分

PS:(1)非线程共享区域的生命周期与所属线程相同,而线程共享区域与JAVA程序运行生命周期相同,GC只发生在线程共享的区域。

(2)程序计数器无内存溢出异常,其他四个区域会抛出OutofMemoryError异常。

1)程序计数器

    程序计数器的功能类似于计算机组成原理中的PC寄存器,用于存放下一条指令所在单元的地址,当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。虽然JVM的程序计数器跟PC有所区别,但是在概念上是等同的,JVM中的PC存放的是程序正在执行的字节码的行号,字节码解释器的工作就是通过改变程序计数器的值来选择下一条需要执行的字节码指令。
    在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复到切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
    如果线程执行的是一个JAVA方法,那么寄存器里面记录的就是正在执行的虚拟机字节码指令的地址,如果线程执行的是一个native方法,那么寄存器记录的值为undefined
    由于程序计数器是固定宽度的存储空间,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的

2)虚拟机栈

    虚拟机栈中存放每个方法执行时创建的栈帧,对于执行引擎来讲,活动线程中,只要栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧用于存放局部变量表,操作数栈,动态链接,方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入方法表的Code属性之中,因此,一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于虚拟机实现。

   局部变量表:存放了各种编译器已知的各种基本数据类型,对象引用等;程序员关注的栈内存一般是指的局部变量表的内存,局部变量表所需的内存空间在编译时期完成分配,在方法运行期间不会改变局部变量表的大小。

  操作数栈:程序中的计算是通过操作数栈来完成的额,操作数栈的最大深度也是在编译的时候就确定了。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。基于栈的优点是可移植性强,缺点是速度相对比较慢。

动态链接:每个栈帧都包含一个指向运行时常量池的引用,持有这个引用是为了支持方法调用过程中的动态链接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final,static域等),称为静态解析,另一部分将在每一次的运行期间转换为直接应用,这部分称为动态链接。

方法返回地址:方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数据栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。

-Xss128k:设置每个线程的栈大小,JDK1.5以后每个线程的栈大小为1M,减少这个值能生成更多的线程,但同时可能会带来OutOfMemoryError。

在Java虚拟机规范中针对这个区域规定了两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

3)本地方法栈

 该区域与虚拟机栈所发挥的作用非常相似,这是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。在JVM规范中,并没有对本地方法发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

4)方法区

在方法区中,存储了每个类的信息(包括类的名称,方法信息,字段信息),静态变量,常量以及编译器编译后的代码等。

在方法区中有一个非常重要的部分就是运行是常量池,常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据,除了包含代码中所定义的各种基本类型(如:int,long等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括直接常量(String,integer和float常量)和对其他类型字段和方法的符号引用。

5)堆区

堆区存由年轻代和老年代组成,其中年轻代又分为一个Eden区和两个Survivor区(使用复制收集算法);所有新建的Object一般都会存储在新生代中,如果新生代数据在一次或多次GC后存活下来,那么将被转移到Old Generation中。

新建的对象也有可能在老年代上直接分配内存,这主要有两种情况:一种为大对象,可以通过启动参数设置-XX:PretenureSizeThreshold=1024,表示超过多大时就不在年轻代分配,而是直接在老年代分配,此参数在年轻代采用Parallel Scavenge GC时无效,因为其会根据运行情况自己决定什么对象直接在老年代上分配内存;另一种为大的数组对象,切数组对象中无引用外部对象。

当老年代满了就需要对老年代进行回收,老年代的垃圾回收称为Full GC。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值