Java虚拟机在执行时会把内存分成不同的区域,这些区域被称为虚拟机内存。对于虚拟机没有直接管理的物理内存,我们称为本地内存,但这两种内存有一定的区别:
受限于虚拟机内存大小的参数控制,当大小超过参数的设置时就会报OOM异常
本地内存不受虚拟机内存参数限制,只受物理机内存容量的限制。当超过物理机内存大小依然会报OOM异常。
java虚拟机在执行过程中会将所管理的内存划分为不同的区域,有的随着线程产生和消失,有的随着java进程产生和消失,根据《Java虚拟机规范》的规定,运行时数据区分为以下一个区域:
程序计数器就是当前线程所执行的字节码的行号指示器,通过改变计数器的值,来选取下一行指令,通过它主要实现跳转、循环、恢复线程等功能。
在任何时刻,一个处理器内核只能运行一个线程,多线程是通过抢占CPU,分配时间完成的。这时就需要有个标记,来标明线程执行到哪里,程序计数器便拥有这样的功能,所以,每个线程都已自己的程序计数器。
虚拟机栈是线程私有的,虚拟机栈主要描述的是线程中方法的内存模型,每个方法被执行的时候,都会在虚拟机栈中同步创建一个栈帧
每个栈帧包含的内容如下:
存储着java基本数据类型(byte/boolean/char/int/long/double/float/short)以及对象的引用,这里的基本数据类型指的是方法内的局部变量
方法被执行时入栈,执行完后出栈
线程请求的栈深度大于虚拟机规定的栈深度
如果虚拟机栈的容量能够动态扩展,那么当虚拟机栈申请不到内存时会报OOM
###】 本地方法栈(Native Method Stacks)
本地方法栈与虚拟机栈的作用是相似的,都会报OOM和StackOverFlowError,都是线程私有的,区别在于:
java堆是JVM内存中最大的一块,由所有线程共享是垃圾收集器管理的主要区域,主要存储的内容是:
字符串常量池原本存放在方法区,jdk8开始放置于堆中,字符串常量池存储的是string对象的直接引用,而不是直接存放的对象,是一张string table
静态变量
线程私有,但是不影响java堆的共性,增加线程分配缓冲区是为了提升对象分配时的效率
java堆既可以是固定大小的,也可以是可扩展的(通过参数-Xmx和-Xms设定),如果堆无法扩展或者无法分配内存时也会报OOM。
方法区是所有线程共享内存,在java8以前是放在JVM内存中的,由永久代实现,受JVM内存大小参数限制,Java8移除了永久代和方法区,引入元空间,直接用物理内存实现。如果物理内存被占满了,元空间也会报OOM),该区域不同地方在于,方法区在编译期间和类加载完成后的内容有少许不同,不过总的来说分为这两部分:
类元信息在类编译期间放入元空间,里面放置了类的基本信息:版本、字段、方法、接口以及常量池表
常量池表,主要存放了类编译期间生成的字面量、符号引用,这些信息在类加载完后会被解析到运行时常量池中
运行时常量池主要存放在类加载后被解析的字面量与符号引用,但不止这些
运行时常量池具备动态性,可以添加数据,比较多的使用就是String类的intern()方法
直接内存位于本地内存,不属于JVM内存,但是也会在物理内存耗尽的时候报OOM,所以也讲一下。比如NIO,它可以使用native函数直接分配堆外内存,然后通过存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以在一些场景下大大提高IO性能,避免了在java堆和native堆来回copy数据。
由于java是一门高级语言,离硬件底层比较远,无法操作底层的资源,于是,java添加了native关键字,被native关键字修饰的方法可以用其他语言重写,这样,我们就可以写一个本地方法,然后用C语言重写,这样来操作底层资源。但是,使用native方法会导致系统的可移植性差。
类变量是用static修饰符修饰,定义在方法外的变量,随着java进程产生和销毁
在java8之前存放在方法区,在java8时存放在堆中
成员变量是定义在类中,但是没有static修饰符修饰的变量,随着类的实例产生和销毁,是类实例的一部分,所以存放在堆中
局部变量是定义在类方法中的变量,存放在虚拟机栈中。
final关键字并不影响在内存中的位置
类常量池与运行时常量池都存储在方法区,而字符串常量池在jdk7时就已经从方法区迁移到了java堆中。(JDK8时,方法区就是元空间)
在类编译过程中,会把类元信息放到元空间,类元信息其中一部分便是类常量池,主要存放字面量和符号引用,而字面量一部分便是文本字符,在类加载时将字面量和符号引用解析为直接引用存储在运行时常量池;
对于文本字符来说,它们会在解析时查找字符串常量池,查出这个文本字符对应的字符串对象的直接引用,将直接引用存储在运行时常量池;字符串常量池存储的是字符串对象的引用,而不是字符串本身。
java代码在编译过程中是无法构建引用的,字面量就是在编译时对于数据的一种表示:
int a=1;//这个1便是字面量
String b="iloveu";//iloveu便是字面量
另外,关注G众号「Java编程大本营」,在后台回复:面试,可以获取我整理的 Java/ JVM 系列面试题和答案,非常齐全。
由于在编译过程中并不知道每个类的地址,因为可能这个类还未加载,所以如果在一个类中引用了另一个类,那么你完全无法知道内存地址,那怎么办,我们只能用他的类名作为符号引用,在类加载完后用这个符号引用去获取他的内存地址。
比如:com.javabc.Solution类中引用了com.javabc.Quest,那么我会把com.javabc.Quest作为符号引用存到类常量池,等类加载完后,拿着这个引用去元空间找此类的内存地址
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。