[关闭]
@867976167 2014-12-26T16:21:14.000000Z 字数 2608 阅读 3352

Java内存分配

JVM


在以前翻译的一篇文章简单的介绍了Java的垃圾回收和堆内存,现在在看周志明的《深入理解Java虚拟机》,再总结一下Java内存管理。

Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁

下面的图片可以表示Java运行时内存区域分布

程序计数器


程序计数器是基于栈实现的JVM中唯一的寄存器,每一个线程都有自己的程序计数器。在任一线程当前只能有一个方法正在执行,如果执行的方法不是navite,那么程序计数器指向机正在执行的字节码指令的地址。如果执行的方法是navite,那么程序计数器就至那PC寄存器的值是undefined。程序计数器至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值。

returnAddress 类型:表示一条字节码指令的操作码(Opcode)。在所有的虚拟机支持的原始类型之中,只有 returnAddress 类型是不能直接Java语言的数据类型对应起来的,也无法在程序运行期间更改returnAddress 类型的值。

Java虚拟机栈


Java虚拟机栈也是线程私有的,与线程同时创建,主要用来存储栈帧,栈帧随着方法的创建而产生,随着方法的结束而消失。Java 虚拟机栈的作用与传统语言(例如C语言)中的栈非常类似,就是用于存储局部变量与一些过程结果的地方。另外,它在方法调用和返回中也扮演了很重要的角色。

在这个区域主要会发生两个异常。当线程请求的栈深度超过虚拟机的深度是,Java 虚拟机将会抛出一个 StackOverflowError异常。另外对于支持动态扩展的Java虚拟机,如果不能申请到足够的空间去创建栈,那 Java虚拟机将会抛出一个 OutOfMemoryError 异常。

Java堆


在 Java 虚拟机中,堆(Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。Java 堆在虚拟机启动的时候就被创建,Java垃圾回收管理的主要区域,这些受管理的对象无需,也无法显式地被销毁。

Java可以处在不连续的物理空间,逻辑上连续即可。Java虚拟机可以动态扩展堆的大小,当堆内存无法实现全部对象实例的内存分配,那 Java 虚拟机将会抛出一个OutOfMemoryError 异常。当不断使用String 的加法是可能会产生大量的对象。
下面一张图片表示Java堆内存:

Java堆内存分配参数图:

在最新的Java8对JVM有个更新:移除了堆中的持久代,详细介绍:http://it.deepinmind.com/gc/2014/05/14/metaspace-in-java-8.html

方法区


在 Java 虚拟机中,方法区(MethodArea)是可供各条线程共享的运行时内存区域。方法区与传统语言中的编译代码储存区(Storage Area Of Compiled Code)或者操作系统进程的正文段(TextSegment)的作用非常类似,它存储了每一个类的信息、常量、静态变量、还包括一些在类、实例、接口初始化时用到的特殊方法。

方法区是堆的逻辑组成部分,方法区和堆一样可以不需要连续的内存空间,可以选择固定大小或者动态扩展。Java虚拟机规范允许对方法区不进行垃圾回收。这个区域的垃圾回收主要目标是常量池和回收和类型卸载。如果方法区的内存空间不能满足内存分配请求,那 Java 虚拟机将抛出一个OutOfMemoryError 异常。

运行时常量池


运行时常量池是方法区的一部分,用于存放编译器产生的各种引用和字面常量。这部分内容将在类加载之后存放到方法区的运行时常量池。相对于Class文件的常量池,其最大的特性是支持动态性即运行期间也可以将新的常量放进去,比如String的intern()方法.

当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常。

本地方法栈


Java 虚拟机实现可能会使用到传统的栈(通常称之为“C Stacks”)来支持 native 方法(指使用 Java 以外的其他语言编写的方法)的执行,这个栈就是本地方法栈(Native MethodStack)。当Java虚拟机使用其他语言(例如C语言)来实现指令集解释器时,也会使用到本地方法栈。如果Java虚拟机不支持natvie方法,并且自己也不依赖传统栈的话,可以无需支持本地方法栈,如果支持本地方法栈,那这个栈一般会在线程创建的时候按线程分配。

当线程请求的栈深度超过虚拟机的深度是,Java虚拟机将会抛出一个StackOverflowError异常。另外本地方法栈支持动态扩展,如果不能申请到足够的空间去创建栈,那 Java虚拟机将会抛出一个 OutOfMemoryError 异常。

对象访问


在Java方法中一个简单的实例化类的语句如下

Object o=new Object();

这里涉及到了三个地方,首先在Java栈中的本地变量表会存在一个Object的reference类型的数据,new Object(),会在堆中存在一个Object的实例数据值,这个对象的类信息(父类,实现的接口,方法等)又会存储在方法区。
reference类型在JVM规范中是指向对象的引用,不同的JVM通过reference定位对象的方式不一样,主要有两种:句柄和指针

  1. 句柄的访问方式:这个时候,Java堆上会划分一块内存作为句柄池,reference中存储的是对象的句柄地址,而句柄中又包含了对象实例数据和类型数据各自的具体地址,见下图

    句柄的好处:reference中存储的是稳定的句柄地址,对象被移动的时候,只会改变句柄中实例数据指针,reference本身不需要被修改

2.指针的访问方式:这个时候,reference存放的就是对象的地址,但是Java堆上的对象的布局就必须考虑如何存放对象类型数据的信息,见下图

指针的好处:指针的好处就是速度快,因为它的地址就是对象的地址,节省了一次指针定位的时间开销,随着对象访问的频繁,这种开销积少成多很可观
部分图片来自:http://www.chinasb.org/archives/2011/09/3775.shtml

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注