迦南诗选java8堆内存模型_终于搞懂了java8的内存结构,再也不纠结
⽅法区和常量池了
java8内存结构介绍
java虚拟机在jdk8改变了许多,⽹络上各种解释都有,在查阅了官⽅⽂档以及⼀下⼤佬的解释以后,我来粗浅的介绍⼀下我理解的java8的内存结构。
java8内存结构图
虚拟机内存与本地内存的区别
Java虚拟机在执⾏的时候会把管理的内存分配成不同的区域,这些区域被称为虚拟机内存,同时,对于虚拟机没有直接管理的物理内存,也有⼀定的利⽤,这些被利⽤却不在虚拟机内存数据区的内存,我们称它为本地内存,这两种内存有⼀定的区别:
JVM内存受虚拟机内存⼤⼩的参数控制,当⼤⼩超过参数设置的⼤⼩时就会报OOM
本地内存本地内存不受虚拟机内存参数的限制,只受物理内存容量的限制虽然不受参数的限制,但是如果内存的占⽤超出物理内存的⼤⼩,同样也会报OOM
java运⾏时数据区域
java虚拟机在执⾏过程中会将所管理的内存划分为不同的区域,有的随着线程产⽣和消失,有的随着java进程产⽣和消失,根据《Java虚拟机规范》的规定,运⾏时数据区分为以下⼀个区域:
程序计数器(Program Counter Register)带齐的成语
程序计数器就是当前线程所执⾏的字节码的⾏号指⽰器,通过改变计数器的值,来选取下⼀⾏指令,通过它来实现跳转、循环、恢复线程等功能。脑脊液压力正常值
在任何时刻,⼀个处理器内核只能运⾏⼀个线程,多线程是通过线程轮流切换,分配时间来完成的,这就需要有⼀个标志来记住每个线程执⾏到了哪⾥,这⾥便需要到了程序计数器。
所以,程序计数器是线程私有的,每个线程都已⾃⼰的程序计数器。
虚拟机栈(JVM Stacks)
虚拟机栈是线程私有的,随线程⽣灭。虚拟机栈描述的是线程中的⽅法的内存模型:
每个⽅法被执⾏的时候,都会在虚拟机栈中同步创建⼀个栈帧(stack frame)
每个栈帧的包含如下的内容局部变量表局部变量表中存储着⽅法⾥的java基本数据类型
(byte/boolean/char/int/long/double/float/short)以及对象的引⽤(注:这⾥的基本数据类型指的是⽅法内的局部变量)操作数栈动态连接⽅法返回地址
⽅法被执⾏时⼊栈,执⾏完后出栈
虚拟机栈可能会抛出两种异常:
如果线程请求的栈深度⼤于虚拟机所规定的栈深度,则会抛出StackOverFlowError即栈溢出
如果虚拟机的栈容量可以动态扩展,那么当虚拟机栈申请不到内存时会抛出OutOfMemoryError即OOM内存溢出
本地⽅法栈(Native Method Stacks)
本地⽅法栈与虚拟机栈的作⽤是相似的,都会抛出OutOfMemoryError和StackOverFlowError,都是线程私有的,主要的区别在于:虚拟机栈执⾏的是java⽅法
本地⽅法栈执⾏的是native⽅法(什么是Native⽅法?)
Java堆(Java Heap)
爱思想官网
java堆是JVM内存中最⼤的⼀块,由所有线程共享,是由垃圾收集器管理的内存区域,主要存放对象实例,当然由于java虚拟机的发展,堆中也多了许多东西,现在主要有:
对象实例类初始化⽣成的对象基本数据类型的数组也是对象实例
字符串常量池字符串常量池原本存放于⽅法区,jdk7开始放置于堆中。字符串常量池存储的是string对象的直接引⽤,⽽不是直接存放的对象,是⼀张string table
静态变量静态变量是有static修饰的变量,jdk7时从⽅法区迁移⾄堆中
线程分配缓冲区(Thread Local Allocation Buffer)线程私有,但是不影响java堆的共性增加线程分配缓冲区是为了提升对象分配时的效率
java堆既可以是固定⼤⼩的,也可以是可扩展的(通过参数-Xmx和-Xms设定),如果堆⽆法扩展或者⽆法分配内存时也会报OOM
⽅法区(Method Area)
⽅法区绝对是⽹上所有关于java内存结构⽂章争论的焦点,因为⽅法区的实现在java8做了⼀次⼤⾰新,现在我们来讨论⼀下:
⽅法区是所有线程共享的内存,在java8以前是放在JVM内存中的,由永久代实现,受JVM内存⼤⼩参数的限制,在java8中移除了永久代的内容,⽅法区由元空间(Meta Space)实现,并直接放到了本地内存中,不受JVM参数的限制(当然,如果物理内存被占满了,⽅法区也会报OOM),并且将原来放在⽅法区的字符串常量池和静态变量都转移到了Java堆中,⽅法区与其他区域不同的地⽅在于,⽅法区在编译期间和类加载完成后的内容有少许不同,不过总的来说分为这两部分:美丽的丝袜老师
类元信息(Klass)类元信息在类编译期间放⼊⽅法区,⾥⾯放置了类的基本信息,包括类的版本、字段、⽅法、接⼝以及常量池表
(Constant Pool Table)常量池表(Constant Pool Table)存储了类在编译期间⽣成的字⾯量、符号引⽤(什么是字⾯量?什么是符号引⽤?),这些信息在类加载完后会被解析到运⾏时常量池中
青蛙的寿命运⾏时常量池(Runtime Constant Pool)运⾏时常量池主要存放在类加载后被解析的字⾯量与符号引⽤,但不⽌这些运⾏时常量池具备动态性,可以添加数据,⽐较多的使⽤就是String类的intern()⽅法
学部委员
直接内存
直接内存位于本地内存,不属于JVM内存,但是也会在物理内存耗尽的时候报OOM,所以也讲⼀下。
在jdk1.4中加⼊了NIO(New Input/Putput)类,引⼊了⼀种基于通道(channel)与缓冲区(buffer)的新IO⽅式,它可以使⽤native函数直接分配堆外内存,然后通过存储在java堆中的DirectByteBuffer对象作为这块内存的引⽤进⾏操作,这样可以在⼀些场景下⼤⼤提⾼IO性能,避免了在java堆和native堆来回复制数据。
常见问题
什么是Native⽅法?
由于java是⼀门⾼级语⾔,离硬件底层⽐较远,有时候⽆法操作底层的资源,于是,java添加了native关键字,被native关键字修饰的⽅法可以⽤其他语⾔重写,这样,我们就可以写⼀个本地⽅法,然后⽤C语⾔重写,这样来操作底层资源。当然,使⽤了native⽅法会导致系统的可移植性不⾼,这是需要注意的。
成员变量、局部变量、类变量分别存储在内存的什么地⽅?
类变量类变量是⽤static修饰符修饰,定义在⽅法外的变量,随着java进程产⽣和销毁在java8之前把静态变量存放于⽅法区,在java8时存放在堆中
成员变量成员变量是定义在类中,但是没有static修饰符修饰的变量,随着类的实例产⽣和销毁,是类实例的⼀部分由于是实例的⼀部分,在类初始化的时候,从运⾏时常量池取出直接引⽤或者值,与初始化的对象⼀起放⼊堆中
局部变量局部变量是定义在类的⽅法中的变量在所在⽅法被调⽤时放⼊虚拟机栈的栈帧中,⽅法执⾏结束后从虚拟机栈中弹出,所以存放在虚拟机栈中
由final修饰的常量存放在哪⾥?
final关键字并不影响在内存中的位置,具体位置请参考上⼀问题。
类常量池、运⾏时常量池、字符串常量池有什么关系?有什么区别?
类常量池与运⾏时常量池都存储在⽅法区,⽽字符串常量池在jdk7时就已经从⽅法区迁移到了java堆中。
在类编译过程中,会把类元信息放到⽅法区,类元信息的其中⼀部分便是类常量池,主要存放字⾯量和符号引⽤,⽽字⾯量的⼀部分便是⽂本字符,在类加载时将字⾯量和符号引⽤解析为直接引⽤存储在运⾏时常量池;对于⽂本字符来说,它们会在解析时查找字符串常量池,查出这个⽂本字符对应的字符串对象的直接引⽤,将直接引⽤存储在运⾏时常量池;字符串常量池存储的是字符串对象的引
温暖的爱⽤,⽽不是字符串本⾝。
什么是字⾯量?什么是符号引⽤?
字⾯量java代码在编译过程中是⽆法构建引⽤的,字⾯量就是在编译时对于数据的⼀种表⽰:int a=1;//这个1便是字⾯量 String b="iloveu";//iloveu便是字⾯量 12
符号引⽤由于在编译过程中并不知道每个类的地址,因为可能这个类还没有加载,所以如果你在⼀个类中引⽤了另⼀个类,那么你完全⽆法知道他的内存地址,那怎么办,我们只能⽤他的类名作为符号引⽤,在类加载完后⽤这个符号引⽤去获取他的内存地址。例⼦:我在com.demo.Solution类中引⽤了st.Quest,那么我会把st.Quest作为符号引⽤存到类常量池,等类加载完后,拿着这个引⽤去⽅法区找这个类的内存地址。