Java虚拟机在执行的时候会把管理的内存分配成不同的区域,这些区域被称为虚拟机内存,同时,对于虚拟机没有直接管理的物理内存,也有一定的利用,这些被利用却不在虚拟机内存数据区的内存,我们称它为本地内存,这两种内存有一定的区别:
受虚拟机内存大小的参数控制,当大小超过参数设置的大小时就会报OOM
java虚拟机在执行过程中会将所管理的内存划分为不同的区域,有的随着线程产生和消失,有的随着java进程产生和消失,根据《Java虚拟机规范》的规定,运行时数据区分为以下一个区域:
程序计数器就是当前线程所执行的字节码的行号指示器,通过改变计数器的值,来选取下一行指令,通过他来实现跳转、循环、恢复线程等功能。
虚拟机栈是线程私有的,随线程生灭。虚拟机栈描述的是线程中的方法的内存模型:
每个方法被执行的时候,都会在虚拟机栈中同步创建一个栈帧(stack frame)。
每个栈帧的包含如下的内容
局部变量表
操作数栈
动态连接
方法返回地址
方法被执行时入栈,执行完后出栈
虚拟机栈可能会抛出两种异常:
本地方法栈与虚拟机栈的作用是相似的,都会抛出OutOfMemoryError和StackOverFlowError,都是线程私有的,主要的区别在于:
java堆是JVM内存中最大的一块,由所有线程共享,是由垃圾收集器管理的内存区域,主要存放对象实例,当然由于java虚拟机的发展,堆中也多了许多东西,现在主要有:
对象实例
类初始化生成的对象
基本数据类型的数组也是对象实例
字符串常量池
字符串常量池原本存放于方法区,jdk7开始放置于堆中。
字符串常量池存储的是string对象的直接引用,而不是直接存放的对象,是一张string table
静态变量
静态变量是有static修饰的变量,jdk7时从方法区迁移至堆中
线程分配缓冲区(Thread Local Allocation Buffer)
线程私有,但是不影响java堆的共性
增加线程分配缓冲区是为了提升对象分配时的效率
java堆既可以是固定大小的,也可以是可扩展的(通过参数-Xmx和-Xms设定),如果堆无法扩展或者无法分配内存时也会报OOM。
方法区绝对是网上所有关于java内存结构文章争论的焦点,因为方法区的实现在java8做了一次大革新,现在我们来讨论一下:另外,Java 8 系列教程全部整理好了,微信搜索Java技术栈,在后台发送:Java,可以在线阅读。
方法区是所有线程共享的内存,在java8以前是放在JVM内存中的,由永久代实现,受JVM内存大小参数的限制,在java8中移除了永久代的内容,方法区由元空间(Meta Space)实现,并直接放到了本地内存中,不受JVM参数的限制(当然,如果物理内存被占满了,方法区也会报OOM),并且将原来放在方法区的字符串常量池和静态变量都转移到了Java堆中,方法区与其他区域不同的地方在于,方法区在编译期间和类加载完成后的内容有少许不同,不过总的来说分为这两部分:
类元信息(Klass)
运行时常量池(Runtime Constant Pool)
直接内存位于本地内存,不属于JVM内存,但是也会在物理内存耗尽的时候报OOM,所以也讲一下。另外,JVM 系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。
在jdk1.4中加入了NIO(New Input/Putput)类,引入了一种基于通道(channel)与缓冲区(buffer)的新IO方式,它可以使用native函数直接分配堆外内存,然后通过存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以在一些场景下大大提高IO性能,避免了在java堆和native堆来回复制数据。
由于java是一门高级语言,离硬件底层比较远,有时候无法操作底层的资源,于是,java添加了native关键字,被native关键字修饰的方法可以用其他语言重写,这样,我们就可以写一个本地方法,然后用C语言重写,这样来操作底层资源。当然,使用了native方法会导致系统的可移植性不高,这是需要注意的。
类变量
成员变量
局部变量
由final修饰的常量存放在哪里?
final关键字并不影响在内存中的位置,具体位置请参考上一问题。
类常量池、运行时常量池、字符串常量池有什么关系?有什么区别?
类常量池与运行时常量池都存储在方法区,而字符串常量池在jdk7时就已经从方法区迁移到了java堆中。
在类编译过程中,会把类元信息放到方法区,类元信息的其中一部分便是类常量池,主要存放字面量和符号引用,而字面量的一部分便是文本字符,在类加载时将字面量和符号引用解析为直接引用存储在运行时常量池;
对于文本字符来说,它们会在解析时查找字符串常量池,查出这个文本字符对应的字符串对象的直接引用,将直接引用存储在运行时常量池;字符串常量池存储的是字符串对象的引用,而不是字符串本身。
字面量
java代码在编译过程中是无法构建引用的,字面量就是在编译时对于数据的一种表示:
int a=1;//这个1便是字面量 String b="iloveu";//iloveu便是字面量
符号引用
由于在编译过程中并不知道每个类的地址,因为可能这个类还没有加载,所以如果你在一个类中引用了另一个类,那么你完全无法知道他的内存地址,那怎么办,我们只能用他的类名作为符号引用,在类加载完后用这个符号引用去获取他的内存地址。
例子:我在com.demo.Solution类中引用了com.test.Quest,那么我会把com.test.Quest作为符号引用存到类常量池,等类加载完后,拿着这个引用去方法区找这个类的内存地址。另外,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Java/ JVM 系列面试题和答案,非常齐全。