JVM 即:java 虚拟机。
JVM最早的设计理念是:一次编写,多次运行。即,当时是为了让一份代码能够兼容多个操作系统的初衷。比如一份业务在不同的操作系统上有不同的代码(也就是一份相同的逻辑需要写多份),而Java为了解决这个问题就设计出了JVM来能够兼容多个操作系统的代码来执行。
但是随着技术的不断发展,现在很少将Windows的程序放在Linux上,所以Java还是保留了这个特性,不算优点也不算缺点。
JVM与其他的虚拟机有什么不同呢?
随着jvm的发展现在好多语言都可以在jvm中运行,比如PHP、phython等
从JVM与操作系统的工作角度,我们可以将JVM看作这几个部分:类加载器、运行时内存、动态内存管理器、执行引擎。它们各司其职都有自己不可替代的功能:
类加载器: 负责从硬盘中查找类文件,然后将文件加载到运行时内存分配的空间中
运行时内存: Java程序再需要内存的时候jvm就会在运行时内存中发分配一片空间
动态内存管理器: 主要负责在运行时内存中为程序动态的分配/回收空间
执行引擎: 当类加载器将类文件加载到内存中,执行引擎就使用字节码文件(Java中的方法)执行或操作一些数据,在执行的过程中如果需要申请内存就通过动态内存管理器来申请内存,最终将执行结果翻译成操作系统的API在真实的操作系统上运行
运行时内存是如何申请创建的呢?
一开始jvm相当于启动了一个进程来调度操作系统申请分配一份内存
,申请到的这片内存就叫运行时内存;
在计算机中JVM、操作系统和硬件之间有着密不可分的关系。
我们根据上图来看一下他们之间的工作流程:
从上面的介绍中,我们知道JVM主要分为这四个区域:运行时内存、类加载器、执行引擎,动态内存管理器;他们都是JVM中不可或缺的角色。
对于Java中数据的存储主要是在运行时内存区,而运行时内存又分为栈区(Java虚拟机栈和本地方法栈)、程序计数器(PC)、堆区、方法区、内存常量池区。
在jdk1.8中,jdk1.7的方法区就是jdk1.8的元数据区。
jdk1.8运行内存分布:
在jdk1.8的内存区域划分中,主要可以分为两大类:
线程私有区域:程序计数器、Java虚拟机栈、本地方法栈
线程共享区域:Java堆、方法区(即:元数据区)、运行时常量池
—开始只有一个Java主线程,只会为主线程提供PC区和栈区;直到Java的main线程调用其他方法方法开辟子线程才会提供子线程的PC区和栈区。
接下来我们就这两大分类来介绍内存中的各个区域。
什么是线程私有?
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称之为"线程私有"的内存。
程序计数器: 标记该线程下一次代码行执行的位置;程序打断点也会依赖程序计数器。
程序计数器中的值: 在方法执行的时候PC区的值一直在变化,因为PC保存的是下一次代码执行的位置,所以一直在变。
程序计数器内存大小: 虽然PC区的值一直在变,但是内存大小一直是那么大保持不变。
程序计数器中的值是根据程序的执行进行变化的,但是大小是不变的,所以不会出现OOM情况(out of memoryerr:内存溢出异常)
java虚拟机栈: 也就是我们常说的JVM中的栈,在每一个线程执行时,都对应有一个虚拟机栈,生命周期与线程相同。
java虚拟机栈方法的执行:
每个java方法被调用时都会创建一个栈帧,然后入栈,方法结束后出栈
。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。栈区在方法执行的过程中会一直生成栈帧,某个方法执行结束就会销毁该方法的栈帧
之前我们一直说的栈区域实际上就是此处的虚拟机栈,再详细一点,是虚拟机栈中的局部变量表部分。
此区域一共会产生以下两种异常:
关于这两种异常将会在后续博客写出。
本地方法栈与虚拟机栈的作用完全一样
本地方法栈与虚拟机栈的区别是:
线程共享内存区域:Java堆、方法区、运行时常量池
我们在Java开发中离不开创建对象,而我们平时创建的示例对象和数组类型就是存储在Java堆中。
Java堆的作用:
如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM(Out of memoryError)。
Java堆在内存中的角色:
-------->垃圾回收角度
java 堆溢出:
1> 出现OOM的原因:
Java堆用于存储对象实例,只要不断的创建对象,并且保证GCRoots到对象之间有可达路径来避免来GC清除这些对象,那么在对象数量达到最大堆容量后就会产生内存溢出异常。
关于垃圾回收的内容见博客:link.
简单的理解:Java堆就是存放实例对象以及数组的仓库,如果一直创建对象并且也不清理没用的对象,当对象的数量达到了Java堆内存的上限,对象就存不下了,这时候就会抛出OOM异常。
Java堆内存的OOM异常是实际应用中最常见的内存溢出情况。当出现ava堆内存溢出时,异常堆栈信息"java.lang.OutOfMemoryError"会进一步提示"Java heap space"。 当出现"Java heap space"则很明确的告知我们,OOM发生在堆上。
2> 设置Java堆内存参数:
我们在上面提到了Java堆可以通过参数设置它内存的最大/小值。现在我们就来看看如何设置。
在cmd窗口中输入: Java -X + 参数
来指定JVM有关参数
上图中,我们输入Java -X 之后可以看到JVM设置有关参数的选项。
例如:java -Xms20m Main:设置Java堆初始大小为20M并运行Main程序。 见下图:
在JDK1.7的时候,有一个JVM内存区域中有一块方法区,主要存放虚拟机加载的类信息,静态变量,常量等。在JDK1.8中将方法区叫做元数据区。元数据区存放的东西和方法区相同,不过元数据区移动到本地内存中。
本地内存,又称堆外内存(Direct Memory),就是指机器内存中不是JVM管理的那部分内存,由操作系统管理。
类信息
(主要是用来存储类信息的,主要是类中的方法)、常量、静态变量、即时编译器编译后的代码等数据
。运行时常量池是方法区的一部分,在jdk1.8的时候随同方法区挪到了本地内存中。 如下图所示:
运行时常量池中存放字面量
与符号引用
。
总结:
运行时内存中的数据主要有两个来源:
方法区、常量池区为主
)栈+堆为主
)