本篇内容主要讲解“Java虚拟机的内存结构是怎样的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java虚拟机的内存结构是怎样的”吧!
一:简介
内存(Memory)也被称为内存储器,其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。
Java虚拟机在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。
进程:一段程序的执行过程,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
进程有三个状态,就绪、运行和阻塞。就绪状态其实就是获取了出cpu外的所有资源,只要处理器分配资源就可以马上执行。就绪状态有排队序列什么的,排队原则不再赘述。运行态就是获得了处理器分配的资源,程序开始执行。阻塞态,当程序条件不够时候,需要等待条件满足时候才能执行,如等待i/o操作时候,此刻的状态就叫阻塞态。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
进程和线程的主要差别:在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些
二:程序计数器
程序计数器是一块较小的内存空间,它可以看作是当前线程执行的字节码行号的指示器。
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令;分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成
当线程获得时间片处于执行过程时,CPU会按照程序计数器中存储的内容依次的取出指令执行,当CPU取出当前指令时,CPU自动修改程序计数器内容,使其指向下一条指令字节码行号。由于程序计数器中存储的是数字值,因此不会随着程序运行而扩大需求空间,故不会发生溢出。
假设线程A正在执行,当执行到某个阶段,优先级更高线程B执行。此时,线程A挂起,线程B执行。当线程B执行完毕后,需要唤醒线程A继续执行,那么如何从线程A的中断位置继续执行呢,那就需要CPU访问线程A的程序计数器,从中获取下一条执行指令,保证程序继续执行。由于每个线程需要保存自身的执行位置,也就使得程序计数器为线程私有。
native本地方法大多是通过C实现并未编译成需要执行的字节码指令,所以在计数器中是undefined
这个内存区域是唯一一个在java虚拟界规范中没有规定任何OutOfMemoryError的情况的区域。
三:Java虚拟机栈
它是一个后入先出的栈,其中保存的元素是栈帧 。它也是线程私有的,它的生命周期与线程相同。
程序运行时,每调用一个方法就会生成一个栈帧,同时将当前正在执行方法的栈帧压入虚拟机栈。虚拟机栈顶的栈帧为“当前活跃栈帧”。既然栈帧是调用方法时创建,那么其保存内容必然与方法息息相关。
局部变量表:局部变量表是一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表存放了编译器可知的各种基本数据类型,对象引用和returnAddress类型。局部变量表的内存是在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
操作数栈:操作数栈是一个以字长为单位的数组。但它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。比如,如果某个指令A把一个值1压入到操作数栈中,指令B将值2压入栈中,稍后指令C就可以弹出这两个个值来进行相加计算,并将结果3压入栈中。通过操作数栈可以完成方法中的一些运算。
动态链接:每个栈帧内部都包含一个指向当前方法所在类型的运行时常量池的引用,以便对当前方法的代码实现动态链接。在class文件里面,一个方法如果要调用另外一个方法,或者访问其成员变量,则需要通过符号引用来表示,那么动态链接的作用就是在恰当的时候将这些以符号引用所表示的方法或是变量解析成直接引用。
返回地址:一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等。
其他信息:虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与高度相关的信息,这部分信息完全取决于具体的虚拟机实现。
3.如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常; 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
四:本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈是为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
五:Java堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆一般分为三大部分:新生代,老年代和永久代
新生代:主要是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。
老年代:主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
永久代:指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域. 它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。
Java堆是垃圾收集器管理的主要区域。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
可以通过-Xmx和-Xms来扩展,如果无法再扩展时,将会抛出OutOfMemoryError异常
六:方法区
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
到此,相信大家对“Java虚拟机的内存结构是怎样的”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。