JVM内存储器分区
JVM内存分区
总结一下JVM内存数据分区就是:
通常我们认为JVM运行时内存分为程序计数器、堆、栈三个部分。
程序计数器是线程独立的,大家可以这么想,在某一时刻、单处理器上,只能执行一条程序指令,所以某个线程的某个指令执行完之后才有可能把处理器资源释放出来处理其他线程的指令,程序计数器就是指向了某个线程下条待执行指令的地址,是基于线程的,所以是线程独立的。
栈,又分为虚拟机栈和本地方法栈,都是为方法执行服务的,只不过本地方法栈是为java底层调用的本地native方法服务的,在这里不做过多解释。我们一般而言的栈,就是指的虚拟机栈。虚拟机栈里存储的是一些局部变量、动态链接、方法出口等,这些数据存储在方法执行时创建的栈帧中。每个方法被调用到返回结束的这个过程,也是该方法生成的栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈中存储的局部变量主要包括一些基本数据类型,如int/byte/long/char/float/boolean/short/double,和对象的reference,以及方法的returnAddress。由于方式执行时,每种数据结构的类型都是确定的,所以该方法需要在虚拟机栈帧中分配的空间大小也是确定的,方法执行期间不会改变大小。
栈也是线程独立的,它的生命周期和线程相同。
堆,又分为我们一般认为的Java堆和方法区。就像大家都知道的,Java堆存储的是Java中所有对象的实例和数组,所以它一般都是虚拟机运行时内存中最大的一块,也是垃圾回收的主要场所,所以也被称为GC堆(Garbage Collected Head)。Java堆中,还可分为老年代、新生代等,具体等到学习GC的时候讲。
Java堆是被所有线程共享的一块儿区域,随着JVM的启动而创建。
方法区主要存储虚拟机加载的类的信息、常量、静态变量等数据,所以一般也被大家称之为“永久代”,但也有垃圾回收的场景存在,一般是针对常量池和装载的类的回收。
方法区包含一块叫做运行时常量池的区域,它主要存放的是在编译期生成的各种字面量和符号引用,类加载时,这部分数据就被加载到常量池中。
运行时也可以把新的常量放入池中,应用比较多的是String.intern()方法。
直接内存不是虚拟机运行时管理的内存区域的一部分,也没有在Java虚拟机规范中定义,它是向本机直接申请的内存,具体应用是java NIO类,引入了一种基于管道channel与缓冲区buffer的IO方式,可以使用native函数库直接分配内存,通过存储在java 堆中的DirectByteBuffer对象作为这块区域内存的引用进行操作,避免了Java堆和Native堆来回复制数据。
那么,对于Object obj = new Object();是如何进行内存分配的呢?
如果代码出现在方法体中,Object obj这部分反映在Java栈的本地变量表中,作为一个reference类型数据出现,而new Object()这部分则反映到Java堆中,形成一块存储了Object类型实例的属性值得结构化内存,另外实例对应的类型信息,如class信息,父类、实现的接口、方法等类型数据存储在方法区中。
那么如何将栈中的reference访问到堆中的实例呢?
JVM规范中只规定reference是对对象的引用,而对采用何种方式进行定位,则没有具体规定,所以各个jvm实现也不一样,不过一般有两种方式,句柄式和指针式。
句柄式指的是,在Java堆中划分出一部分内存做句柄池,reference中存储的是要访问的对象的句柄地址,而在句柄中在存储对象的数据类型和访问地址。
指针式指的是,reference中直接存储对象的访问地址。
这两种方式各有优点,句柄式的好处是,reference中存储的是句柄地址,当发生垃圾收集的时候,只需要修改句柄中存储的对象访问地址,而不需要修改reference中的句柄地址,指针式的优点是,少了一层句柄寻址操作,方便快捷。
总结一下JVM内存数据分区就是:
通常我们认为JVM运行时内存分为程序计数器、堆、栈三个部分。
程序计数器是线程独立的,大家可以这么想,在某一时刻、单处理器上,只能执行一条程序指令,所以某个线程的某个指令执行完之后才有可能把处理器资源释放出来处理其他线程的指令,程序计数器就是指向了某个线程下条待执行指令的地址,是基于线程的,所以是线程独立的。
栈,又分为虚拟机栈和本地方法栈,都是为方法执行服务的,只不过本地方法栈是为java底层调用的本地native方法服务的,在这里不做过多解释。我们一般而言的栈,就是指的虚拟机栈。虚拟机栈里存储的是一些局部变量、动态链接、方法出口等,这些数据存储在方法执行时创建的栈帧中。每个方法被调用到返回结束的这个过程,也是该方法生成的栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈中存储的局部变量主要包括一些基本数据类型,如int/byte/long/char/float/boolean/short/double,和对象的reference,以及方法的returnAddress。由于方式执行时,每种数据结构的类型都是确定的,所以该方法需要在虚拟机栈帧中分配的空间大小也是确定的,方法执行期间不会改变大小。
栈也是线程独立的,它的生命周期和线程相同。
堆,又分为我们一般认为的Java堆和方法区。就像大家都知道的,Java堆存储的是Java中所有对象的实例和数组,所以它一般都是虚拟机运行时内存中最大的一块,也是垃圾回收的主要场所,所以也被称为GC堆(Garbage Collected Head)。Java堆中,还可分为老年代、新生代等,具体等到学习GC的时候讲。
Java堆是被所有线程共享的一块儿区域,随着JVM的启动而创建。
方法区主要存储虚拟机加载的类的信息、常量、静态变量等数据,所以一般也被大家称之为“永久代”,但也有垃圾回收的场景存在,一般是针对常量池和装载的类的回收。
方法区包含一块叫做运行时常量池的区域,它主要存放的是在编译期生成的各种字面量和符号引用,类加载时,这部分数据就被加载到常量池中。
运行时也可以把新的常量放入池中,应用比较多的是String.intern()方法。
直接内存不是虚拟机运行时管理的内存区域的一部分,也没有在Java虚拟机规范中定义,它是向本机直接申请的内存,具体应用是java NIO类,引入了一种基于管道channel与缓冲区buffer的IO方式,可以使用native函数库直接分配内存,通过存储在java 堆中的DirectByteBuffer对象作为这块区域内存的引用进行操作,避免了Java堆和Native堆来回复制数据。
那么,对于Object obj = new Object();是如何进行内存分配的呢?
如果代码出现在方法体中,Object obj这部分反映在Java栈的本地变量表中,作为一个reference类型数据出现,而new Object()这部分则反映到Java堆中,形成一块存储了Object类型实例的属性值得结构化内存,另外实例对应的类型信息,如class信息,父类、实现的接口、方法等类型数据存储在方法区中。
那么如何将栈中的reference访问到堆中的实例呢?
JVM规范中只规定reference是对对象的引用,而对采用何种方式进行定位,则没有具体规定,所以各个jvm实现也不一样,不过一般有两种方式,句柄式和指针式。
句柄式指的是,在Java堆中划分出一部分内存做句柄池,reference中存储的是要访问的对象的句柄地址,而在句柄中在存储对象的数据类型和访问地址。
指针式指的是,reference中直接存储对象的访问地址。
这两种方式各有优点,句柄式的好处是,reference中存储的是句柄地址,当发生垃圾收集的时候,只需要修改句柄中存储的对象访问地址,而不需要修改reference中的句柄地址,指针式的优点是,少了一层句柄寻址操作,方便快捷。