[深入理解JVM] Java内存区域

6 篇文章 0 订阅

Java内存区域

JVM具有自动内存管理机制,Java不需要像c/c++一样,为每一个new操作写配对的delete/free代码,不容易出现内存泄露和溢出。JVM所管理的内存区域主要包括以下几个运行时数据区域部分:程序计数器Java虚拟机栈本地方法栈Java堆方法区

VMåå­åºå

                                                          图 JVM运行时数据区域

Java 虚拟机的内存结构可以分为公有和私有两部分。公有指的是所有线程都共享的部分,私有指的是每个线程的私有数据。

  • 公有:Java 堆、方法区、常量池。
  • 私有:PC寄存器、Java 虚拟机栈、本地方法栈。

程序计数器

程序计数器可以视为当前线程所执行的字节码的行号指示器,如果当前执行的是Native方法,计数器的值为空(Undefined)。在JVM的概念模型中,字节码解释器通过改变计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程回复等都依赖程序计数器完成。

在任何一个确定的时刻,一个处理器都只会执行一条现场中的指令。每条线程都有独立的计数器,保证线程切换恢后能恢复正确的执行位置,因此程序计数器这一块内存区域是线程隔离的。

该区域是唯一一个没有规定任何OutOfMemoryError的区域。


Java虚拟机栈

Java虚拟机栈也是线程隔离的,一个线程一个栈,生命周期与线程相同。

它内部由栈帧构成,一个栈帧代表一个调用的方法,线程在每次方法调用执行时创建一个栈帧然后压栈,栈帧用于存放局部变量、操作数、动态链接、返回地址等信息。方法执行完成后对应的栈帧出栈。我们平时说的栈内存就是指这个栈。

一个线程中的方法可能还会调用其他方法,这样就会构成方法调用链,而且这个链可能会很长,而且每个线程都有方法处于执行状态。对于执行引擎来说,只有活动线程栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧关联的方法称为当前方法(Current Method)。

æ å¸§ç»æ]

                                           图 Java虚拟机栈

虚拟机栈的2种异常:

  • StackOverFlowError:调用链过长,线程请求深度大于JVM所允许的深度。
  • OutOfMemoryError:虚拟机栈动态扩展时无法申请到足够内存。

本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈的所用很相似,区别在于,虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机线程调用Native方法服务。Java可以通过java本地接口JNI(Java Native Interface)来调用其它语言编写(如C)的程序,在Java里面用native修饰符来描述一个方法是本地方法。

虚拟机规范中没有对本地方法栈作强制规定,虚拟机可以自由实现,所以可以不是字节码。如果是以字节码实现的话,虚拟机栈本地方法栈就可以合二为一,事实上,OpenJDK和SunJDK所自带的HotSpot虚拟机就是直接将虚拟机栈和本地方法栈合二为一的。
 


Java堆

Java堆是JVM管理的最大一块内存,是线程共享的,在JVM启动时创建。堆存放所有对象实例以及数组。不过在JIT(Just-in-time)和逃逸情况下,栈上分配、标量替换有可能在栈上分配对象实例。

堆是java垃圾收集器管理的主要区域(所以很多时候会称它为GC堆)。从GC回收的角度看,由于现在GC基本都是采用的分代收集算法,所以堆内存结构还可以分块成:新生代和老年代、永久代;再细一点的有Eden空间、From Survivor空间、To Survivor空间等。值得注意的是,从JKD1.7开始,永久代Perm逐渐被移除,最新的JDK1.8中已经使用元空间(MetaSpace)代替永久代。

                                     

Java堆可以像磁盘空间一样,允许逻辑上连续而物理不连续。如果堆中没有内存完成实例分配并且无法扩展,将会抛出OutOfMemoryError异常。


方法区

Java虚拟机规范将方法区描述为堆的逻辑部分,但是却称为非堆(Non-Heap),在Sun的HotSpot虚拟机中,可以将方法区理解为堆内存块中的永久代(Permanent Generation)。它是线程共享的。

方法去用于存储在加载类文件时,用于存放加载过的类信息,常量,静态变量,及JIT编译后的代码(类方法)等数据。
 


运行时常量池

常量池(Constant Pool Table),用于存放编译期生成的各种字面量、符号引用,文字字符串、final变量值、类名和方法名常量,这部分内容将在类加载后存放到方法区的运行时常量池中。它们以数组形式访问,是调用方法、与类联系及类的对象化的桥梁。

运行时常量池除了存放编译期产生的Class文件的常量外,还可存放在程序运行期间生成的新常量,比较常见增加新常量方法有String类的internd()方法。String.intern()是一个Native方法,它的作用是:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用。不过JDK7的intern()方法的实现有所不同,当常量池中没有该字符串时,不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录堆中首次出现的该字符串的引用,并返回该引用。

但是,JDK1.7之前运行时常量池是方法区的一部分,JDK1.7及之后版本已经将运行时常量池从方法区中移了出来,在堆(Heap)中开辟了一块区域存放运行时常量池。


总结

类和对象在运行时的内存里是怎么样的?以及各类型变量、方法在运行时是怎么交互的?

  • 在程序运行时类是在方法区,实例对象本身在堆里面。
  • 方法字节码在方法区。
  • 线程调用方法执行时创建栈帧并压栈,方法的参数和局部变量在栈帧的局部变量表。
  • 对象的实例变量和对象一起在堆里,所以各个线程都可以共享访问对象的实例变量。
  • 静态变量在方法区,所有对象共享。字符串常量等常量在运行时常量池。
  • 各线程调用的方法,通过堆内的对象,方法区的静态数据,可以共享交互信息。
     

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值