本篇文章是自我理解,如果有错误,欢迎指出,共同学习。
C++和java的内存管理机制是不同的,C++是需要自己去调配的,而java是完全自动的,有自动销毁的机制,所以对于java的代码人来说,无疑是非常的舒服的。
那么进入正题,java在运行的时候,首先会先去内存,要求分出一块区域,那么这块区域就称为jvm(虚拟机)。
那么在jvm下又大致分为五大区域,分别是程序计数器,本地方法栈,堆,虚拟机栈,方法区。
程序计数器:也可以说是cpu寄存器,或者说是操作系统内存,用来记录当前线程字节码执行到了第几行,所以程序计数器只会记录当前的行号,而不会记录下一行的行号,就不存在程序溢出的问题了,也是java内存区域中唯一一个没有定义OutOfMemoryError的区域。值得一提的是,当执行线程的时候,每个线程都有属于自己的寄存器,这样就不会错乱掉。另外java对于系统的操作显得比较乏力,所以会调用一些系统里的方法,比如 复制,粘贴,剪切等功能。(这些方法都在本地方法栈,后面会说到)。那么这些方法是利用了C语言来实现的,那么这个时候如果运行的是java的程序,那么程序计数器记录的是当前字节码所执行的行号。如果是执行本地的方法的话,(native,由C语言完成)那么计数器就是undefined.
本地方法栈:本地方法栈,是利用C语言来完成的,用来调用系统的一些方法。这里和虚拟机方法栈是由区别的,本地方法栈是用来运行本地的方法的,而虚拟机方法栈是用来调用java程序的。
堆:是java的内存是最大的一块,由所有线程共享的,在虚拟机启用的时候创建,堆区是用来存储对象的实例和数组值,可以认为java中所有new出来的对象都在这里分配。
虚拟机栈:每个线程都有其对应的虚拟机方法栈,而且它的分配非常的高效.一个线程的每个方法在执行的时候,都会创建一个栈帧,栈帧存储的变量有局部变量表,操作站,动态连接,方法出口等当方法在被调用的时候,栈帧在jvm栈中入栈,当方法执行完后,栈帧出栈。另外虚拟机栈,定义了两种异常:1.栈溢出,当虚拟机调用的栈深度,大于了虚拟机的允许深度,就会报出这个异常。不过java虚拟机有允许动态扩展虚拟机的大小,少部分有固定长度,所以线程可以一直申请栈,直到内存不足的时候,此时就会抛出另外一种异常,那么就是2.内存溢出。
方法区:放入了类要加载的信息,如修饰符,类名。final定义的常量,和静态的变量,这些数据来源于方法区,方法区是全局共享的。当方法区超过它的内存允许大小的时候,就会抛出越界异常。在Hotspot虚拟机当中,
这块的区域又被称为持久层,因为在这个层上,需要收集的垃圾非常少,但是并不代表没有垃圾,它所收集的垃圾主要针对常量池的内存回收和对已加载类的卸载,在方法区上收集垃圾可以说是非常的艰难,和条件苛刻的。
运行时常量池也是方法区的一部分,用于存储编译时期就生成的字面常量,符号引用。运行时常量池除了存储编译期间的常量外,还可以存储运行期间所产生的常量。比如String里的retern()方法,作用是维护了String的一个常量池,如果想定义一个“abc”的方法,那么会去常量池里查找,是否定义过,如果定义过,那么就返回定义过的地址,如果没有定义过,那么就新建一个常量放在常量池中。并返回了地址。
那么java是如果运行的,当运行一个java的程序的时候,首先会去内存里要求分出一块区域,那么这块区域就是我们刚才说的jvm,虚拟机了,那么在虚拟机下面又分为5个区域。我们编译有用dos命令操作过的人都知道,我们编写的都是java文件,那么编译过的都是叫class文件,class文件里的内容是看不懂的。那么这个class编译过的文件,会先进入到方法区,进行分析,如果有final定义的常量,或者静态的变量,那么方法区会在开出一个区域,存放这些定义的常量和变量,这个区域就被称为运行时常量池,在到虚拟机方法栈,虚拟机方法栈中会在分出一块区域给这个进入线程,查找main方法,运行程序,当有数组,集合实例化方法的时候,会进入到堆,那么经过堆的实例化一些操作之后,把实例化的地址在返回给虚拟机栈相对应的值,为了下次调,能够查找到这些方法。比如我们new一个实例化的对象的时候,用System.out.println()输出一下变量,那么就是内存中的地中。当方法运行完后,虚拟机栈里的方法消息,堆内存里存放的对象也消失了。这些都不用去关心,因为java有垃圾收集的机制,会自动清理这些垃圾。
这里不懂的是,当运行完后,是怎么样回收的,哪个先消失。