本篇主要讲述java内存区域的划分。下面直接进入正题。
java虚拟机就是在真实物理机上虚拟出来的一台计算机,java语言有一个特点就是可以跨平台,其中jvm起着关键作用。这是因为它屏蔽与平台相关的信息,java源文件经过编译程序编译后生成字节码文件。然后由jvm解释执行成可以被计算机识别的机器指令。
在jvm运行过程,我们经常会遇到内存溢出或者是内存泄露的情况,所以有 必要了解jvm的内存结构。这样才能更好分析问题,解决问题。
内存溢出:程序向虚拟机(JVM)申请内存,但是虚拟机(JVM)却没有足够的内存分配给申请者,这个时候就会出现内存溢出。
内存泄露:程序向虚拟机(JVM)申请内存使用,使用完后不归还,其他程序也无法使用这块内存,最终内存泄露会变成内存溢出。
按照Java虚拟机规范的规定,JVM自动管理的内存将会包括以下几个运行时数据区域。
当执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。
程序计数器是计算机处理器中的寄存器,它包含当前正在执行的指令的地址(位置)。当每个指令被获取,程序计数器的存储地址加一。在每个指令被获取之后,程序计数器指向顺序中的下一个指令。当计算机重启或复位时,程序计数器通常恢复到 零。
简单地的说就是程序计数器是用于存放(记录)下一条指令所在单元的地址的地方。
如果说线程正在执行一个方法,那么计数器记录的就是虚拟机(JVM)正在执行字节码指令的地址。程序计数器占用的内存很小,在java运行时数据区是唯一一块不会出现内存溢出的区域。
虚拟机栈是由帧栈组成。属于线程私有。一个帧栈对应一个方法。每个方法在执行的同时,就会创建一个帧栈,用于存储局部变量表,操作数栈,动态连接,返回地址等信息。每一个方法的调用和执行完成的过程对应一个帧栈的入栈和出栈的过程(入栈时,进入栈顶,所以先入栈的会后出来,也就是先进后出)。虚拟机栈中主要的部分是局部变量表,用来存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和(returnAddress)类型(它指向了一条字节码指令的地址)。保存对象引用时,并不会保存引用对象本身,而是保存指向引用对象的指针或者引用对象的句柄或者是其他能代表引用的对象的位置的信息。局部变量表所需的内存大小在编译时期就已经确认了,不受运行时的影响。
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
个人理解本地方法栈主要是jvm用来和其他语言来打交道的或者是对java语言进行的一种扩展。本地方法栈中使用的语言,使用方式,数据结构没有强制要求。设计者可以各自的需求进行设计已满足自己的需求。
具体可以参考https://blog.csdn.net/uk8692/article/details/50680159?utm_source=blogxgwz1
对于大多数应用来说,堆是java内存区域中最大的一块,且被所有线程共有。虚拟机(JVM)在启动的时候就会创建这块区域。
几乎所有的创建的对象都是在这块区域分配内存。从内存回收的角度来说,java堆还可以进行细分为:新年代和老年代,再细分一点分为:Eden区,From Surivor区,To Survivor区等。
方法区和堆一样被所有线程共享,用于存放已经被虚拟机(JVM)加载的类信息,静态变量,常量以及编译器编译后的代码等信息。对于习惯在HotSpot虚拟机上开发和部署程序的开发人员来说,很多人把方法区称为永久代,其实这两种说法并不是等价的,因为HotSpot虚拟机设计团队仅仅是将GC分代收集从堆区扩展到了方法区,这样垃圾收集器就可以像管理堆一样来管理方法区了,而不用专门设计的程序代码来管理方法区的这块区域。
运行时常量池属方法区的一部分,用于存放编译期生成的各种字面值和符号引用,这部分内容将在类加载的时候进入方法区的运行时常量池。
Java虚拟机(JVM)对class文件的内容有严格的要求,每个字节存储什么类型的数据必须符合规范才能被虚拟机认可,加载,运行。而运行时常量池也没规范,虚拟机(JVM)供应商可以根据需求来实现这部分内存区域。除了保存class文件中的符号引用,还会把翻译出来直接引用也会保存在运行时的常量池中,而且运行时常量池与class常量池相比还具有动态性,java语言并不要求只有编译时期才能长生常量,也就是说并非只有预先放入到class文件常量池中的内容才能放入到常量池,运行期生成的常量也可以进入常量池。其中使用最多的就是java.lang.String 的intern()方法。
直接内存区域并不是运行时数据区的一部分,也不是Java虚拟机规范定义内存区域,但是这块区域的使用的也比较频繁。JDK1.4新加入了NIO类,引入了一种基于通道和缓冲的I/O方式,它可以使用本地函数库直接分配堆外内存,然后通过一个存放在堆中的DirectoryByteBuffer对象作为这块内存的引用来进行操作。这样避免了在java堆和native堆中来回的复制数据。
本篇到此结束了,如果有问题,还望大佬们不吝赐教。
下篇将讲解内存分配策略以及垃圾回收机制,点击进入——深入理解java虚拟机(二)