JVM那些事儿之内存空间管理(三)

来源:互联网 发布:mac口红火鹤鸟 编辑:程序博客网 时间:2024/05/29 21:29

JVM那些事儿之内存空间管理(三)

我们前面说过,JVM是个虚拟机,它没有硬件意义上的内存管理器,所以必须自己虚拟一个内存管理器。这篇文章带大家去了解一下JVM的内存空间管理。

在操作系统中,每次启动一个Java应用时,就会先启动一个JVM进程,Java应用就以一个线程的存在形式运行在这个进程中。JVM会按照应用配置的-VM参数设置需要申请的内存空间和最大内存空间。并且把这个内存按照不同功能区划分出来,方便Java应用模拟使用。

为了节省系统资源,JVM支持同时运行多个Java应用,也就是说,打开新的Java应用时,将它挂载到已经存在的JVM进程中,多个应用共享一个JVM。此时,JVM申请的系统资源就分为:自身需要的内存区、应用间共享的内存区、应用1的内存区、应用2的内存区……

在Java程序运行过程中,JVM定义了各种区域用于存储运行时数据。其中的有些数据区域在JVM启动时创建,并只在JVM退出时销毁。其它的数据区域与每个线程相关。比如说以下这些数据区域,在线程创建时创建,在线程退出时销毁。

程序计数器寄存器(The pc Register)

JVM支持多个线程同时运行。每个JVM都有自己的程序计数器。在任何一个点,每个JVM线程执行单个方法的代码,这个方法是线程的当前方法。如果方法不是native的,程序计数器寄存器包含了当前执行的JVM指令的地址,如果方法是 native的,程序计数器寄存器的值不会被定义。 JVM的程序计数器寄存器的宽度足够保证可以持有一个返回地址或者native的指针。

所有指令都预先存放在内存中,所谓的程序计数器存放的就是现在指令运行到哪了。知道就存着,不知道就空着。注意不要以为它存放的是程序运行了多少行。

1)栈与线程

JVM是基于栈的虚拟机。JVM为每个新创建的线程都分配一个栈。也就是说,对于一个Java程序来说,它的运行就是通过对栈的操作来完成的。栈以栈帧为单位保存线程的状态,换句话说,就是栈里面存了一堆栈帧。

前面我们已经讲过,JVM在执行Java应用的时候,是以方法为单位的,从main方法开始,从上到下依次执行,每当遇到一个方法的时候,程序会创建一个栈帧,并把相应的局部变量表、操作数栈、常量池指针、返回地址存放在这个栈帧中,然后把这个栈帧做压栈操作,即可开始执行该方法内的程序了,在此方法执行期间,这个栈帧将用来保存参数、局部变量、中间计算过程和其他数据等。执行完毕后,再将栈帧做出栈操作,继续执行下面的代码,依次类推,直到程序执行完毕为止。

综上所述,我们大概能知道:
1、栈是先进后出的
2、栈的内存是有上限的,所以方法嵌套调用不能太多层

2)栈中的方法调用
当嵌套方法调用时,嵌套越深,stack的内存就越晚才能释放,因此,在实际开发过程中,不推荐大家使用递归来进行方法的调用,递归很容易导致stack flow。

每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享。

跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。

所以,我们发现,Java是面向对象的语言,Java中的所有对象都是存放在堆中的,在栈里只存放一个引用(链接地址)而已。

方法区

基本上都放在堆中实现,保存的是当前装载的类的一些信息。


进阶

线程间的内存共享

对于堆和方法区来说,线程之间的内存理论上来说是共享的,但是在具体编写JVM的厂家来说,大多数都没有实现共享,不共享其实更加容易管理。会有一种堆外内存的说法,这也就是导致堆外内存溢出的元凶。

堆和栈的区别

  • 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方 。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
  • 栈的优势是,存取速度比堆要快 ,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
  • 堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
原创粉丝点击