jvm虚拟机特性之内存区域理解

来源:互联网 发布:windows 多进程 编辑:程序博客网 时间:2024/06/06 02:37

jvm虚拟机特性之内存区域理解

jvm把他管理的内存划分为多个区域,每个区域有自己的创建销毁时间,有自己的线程间共享和独占的属性。的内存区域划分如下图所示(盗图,此图不错):



一,程序计数器

程序计数器纪录的是当前线程正在执行的字节码指令的地址,如果虚拟机正在执行native函数则程序计数器为空(undefined)。所以,它是线程私有的,随着线程的创建而创建,随着线程的销毁而释放。它占用非常小的一块内存,在计算内存占用量时几乎可以忽略不计。

二,Java虚拟机栈

Java虚拟机栈也是线程私有的,生命周期与线程相同。Java虚拟机栈是描述Java方法执行的内存模型,由一个个栈帧组成,栈帧用来存储方法的局部变量表、动态链接、方法出口等信息。当一个方法被调用的时候则创建一个栈帧,方法返回则整个栈帧出栈。

使用Java虚拟机栈内存时可能出现两种异常:StackOverflowError和OutOfMemoryError异常。前者说明当前线程的虚拟机栈深度已经超过了最大允许的栈深度;后者说明虚拟机栈的空间超过了最大允许的栈空间大小(如果栈空间可以动态扩展即大小可以动态调整,则说明已经申请不到可用的内存空间)。

栈空间的大小可以通过-Xss参数设置。当我们设置的栈空间较小时容易出现OutOfMemoryError异常,但是由于每个线程的栈空间较小却可以创建更多的线程;当设置的栈空间较大时虽然不容易出现OutOfMemoryError异常,但栈的深度超过最大允许的栈深度时还是会抛出StackOverflowError异常。

需要注意的是,当出现OutOfMemoryError异常时可能是你为每个线程设置的栈空间比较大和也可能是比较小。原因是什么呢?当栈空间较小时容易想到,有新的栈帧需要入栈,而此时栈空间却容不下这个栈帧了。若栈空间设置的比较大,所有线程占用的总的栈空间大小一定也比较大。栈空间是整个内存空间的一部分,线程的虚拟机栈占用的内存空间比较多,其他区域分配的必然少,所以可能导致其他区域出现OutOfMemoryError异常。所以需要根据自己应用的情况,合理的控制每个区域的空间大小。一般情况下,栈空间不需要设置特别大,128K左右。

三,本地方法栈(Native Method Stacks)

本地方法栈类型于Java虚拟机栈,只不过本地方法栈记录的是native方法的信息。

四,Java堆

Java堆是程序员最熟悉的内存区域,设置虚拟机参数时首先会想到堆空间大小的设置(可以通过-Xms和-Xmx设置,一般将这两个值设置成相等值,这样可以避免堆空间的动态扩展带来的性能消耗)。Java堆用来存放几乎所有的实例对象,它是被所有线程共享的一块区域。Java堆是垃圾回收面对的主要区域,具体可以细分为新生代、老年代,用于存放不同年龄(垃圾回收中的概念,可以理解为经历GC的次数)的对象。

看一个方法中非常常见的java语句:

<span style="white-space:pre"></span>Object o = new Object();
上面语句中Object o定义了一个object类型的引用,它存放到方法的局部变量表中,即刚才说到的本地方法栈的栈帧中。而object的实例对象则保存到了java堆中,方法栈中的引用指向了堆中的对象。关于object的类型数据则保存在了方法区中,下面介绍方法区。

五, 方法区

方法区用来存储一些被共享的常量信息,比如类定义、静态变量、常量等。我们习惯叫他永久代,但是它并不是Java堆空间的一部分,不是跟新生代、老年代在一个区域。

在class文件中保存了类的字段,方法,常量池等信息。常量池信息包括存放编译器生成的各种字面量和符号引用,在类加载之后存放在方法区中叫做运行时常量池的区域。运行时常量池是方法区的一部分。

六,直接内存(direct memory)

jdk1.4引入了NIO,它可以利用native函数直接分配堆外内存。直接内存不是虚拟机运行时数据区的一部分,它是使用native函数分配的堆外内存,即Java和native可以共同操作的一块内存区域。合理的使用直接内存可以提高性能。比如大文件的读取中,一般的io操作顺序为先将文件内容读到native堆,然后再复制到java堆,之后才能被java的用户线程操作。使用直接可以避免io操作中数据从native堆到java堆的复制过程,从而大幅的提高io效率。

关于直接内存涉及到JNI内存管理和操作系统相关的知识。作为java程序员关于跟系统交互相关部分的理解还是不透彻,而且资料也比较少,希望以后能理清这块得逻辑。这里先留下几个疑问:

1,native堆的概念是什么,为什么没有地方定义,是否等同于系统的内核缓冲区,还是使用的java堆内存?

2,调用io读操作是否是这样一个过程呢?首先java线程调用native io方法,此时线程栈和程序计数器都换成native函数的。然后在native函数中执行系统调用,此时进入内核态,系统内核将文件内容读到内核缓冲区。然后native函数再从内核缓冲区复制到native堆,最后再由java线程复制到java堆。

3,为什么native函数不以引用的方式将结果返回给java调用者?

4,在使用了直接内存的异步io中(比如netty),系统内核将读到的数据放到了哪里呢,可以被用户线程直接访问吗?是否做到了系统内核主动将数据放到用户空间?

0 0
原创粉丝点击