java内存结构
之前一直是在学习c++,所以对c++的内存结构比较了解。但是目前由于工作需要从事Java开发,而自己对这方面的知识比较欠缺,所以从网络上阅读查看别人的学习总结,希望能从中总结出自己的理解,也帮助自己在需要时进行翻阅。
Java内存区域
首先是java内存区域的分析,当Java程序运行时,主要是将运行时数据区划分为五个部分,分别是堆、方法区、虚拟机栈、本地方法栈、程序计数区:
堆
对于对大多数应用来说,Java堆(Java Heap)是Java虚拟机中所管理内存的最大一块,他被所有的线程所共享。
在Java虚拟机被启动的时候进行创建。几乎所有的对象实例以及数组都要在堆上进行内存分配。
在实际的使用过程中既可以实现固定大小,也可以是扩展的。其在物理上是不连续的存储空间。如果堆中的内存无法支持实例分配,也无法扩展时,将抛出OutOfMemoryError。
Java中的堆是垃圾收集器管理的主要区域。作为一直学习c++,要对内存进行各种管理操作的人来说,对Java的印象也是可以不用操作管内存的释放。垃圾收集器是Java的特点之一,这点放在之后进行总结。
方法区
对于方法区https://blog.csdn.net/A_art_xiang/article/details/118568601这篇博客讲解的非常清楚,这里只做简单的介绍。
方法区是各个线程共享的内存区域,它用于存储class二进制文件。主要包含虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
所有的方法区在逻辑上都属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
方法区的在JVM启动时被创建,且不要求内存连续,它的大小取决与系统可以保存多少个类,加载大量第三方jar包、timcat部署的工程过多、大量动态生成反射类),导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMemoryError: PermGen space(jdk7以前)或者java.lang.OutOfMemoryError: Metaspace(jdk8以后)
Java虚拟机栈
参考此文章
Java虚拟机栈是线程私有的,栈使用的内存不需要保证是连续的,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。
Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。
在运行时的线程中,只有当前栈帧有效(Java虚拟机栈中栈顶的栈帧),与当前栈帧相关联的方法称为当前方法。每调用一个新的方法,被调用方法对应的栈帧就会被放到栈顶(入栈),也就是成为新的当前栈帧。当一个方法执行完成退出的时候,此方法对应的栈帧也相应销毁(出栈)。
本地方法栈
Java虚拟机栈用来管理Java方法的调用,而本地方法栈用来管理本地方法的调用。
Java方法:由Java语言编写,编译成字节码,存储在class文件中。Java方法与平台无关。
本地方法:由本地方法是由其他语言(如C、C++ 或其他汇编语言)编写,编译成和处理器相关的代码。本地方法保存在动态连接库中,格式是各个平台专用的,运行中的java程序调用本地方法时,虚拟机装载包含这个本地方法的动态库,并调用这个方法。
通过本地方法,java程序可以直接访问底层操作系统的资源,但是这么用的话,程序就变成了平台相关了,因为本地方法的动态库是与平台相关的,此外,使用本地方法还可能把程序变得和特定的java平台实现相关。
本地方法栈是线程私有的,允许被实现成固定或者是可动态扩展的内存大小。如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个stackoverflowError异常。
如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个outofMemoryError 异常。
程序计数器
程序计数器是当前线程正在执行的字节码地址。程序计数器是线程隔离的,所谓线程隔离是指请求线程和服务执行线程分割开来,每一个线程在工作的时候都有一个独立的计数器。
字节码的执行原理:编译后的字节码在没有经过JIT(实时编译器)编译前,是通过字节码解释器进行解释执行。其执行原理为:字节码解释器读取内存中的字节码,按照顺序读取字节码指令,读取一个指令就将其翻译成固定的操作,根据这些操作进行分支,循环,跳转等动作。
从字节码的执行原理来看,单线程的情况下程序计数器是可有可无的。因为即使没有程序计数器的情况下,程序会按照指令顺序执行下去,即使遇到了分支跳转这样的流程也会按照跳转到指定的指令处继续顺序执行下去,是完全能够保证执行顺序的。
但是现实中程序往往是多线程协作完成任务的。JVM的多线程是通过CPU时间片轮转来实现的,某个线程在执行的过程中可能会因为时间片耗尽而挂起。当它再次获取时间片时,需要从挂起的地方继续执行。在JVM中,通过程序计数器来记录程序的字节码执行位置。程序计数器具有线程隔离性,每个线程拥有自己的程序计数器。
程序计数器的特点:
(1)程序计数器具有线程隔离性
(2)程序计数器占用的内存空间非常小,可以忽略不计
(3)程序计数器是java虚拟机规范中唯一一个没有规定任何OutofMemeryError的区域
(4)程序执行的时候,程序计数器是有值的,其记录的是程序正在执行的字节码的地址
(5)执行native本地方法时,程序计数器的值为空。原因是native方法是java通过jni(JDK 在我们的 JVM 中运行的字节码和原生代码(通常用 C 或 C++ 编写)之间搭建了一座桥梁。该桥梁就称为Java Native Interface。)调用本地C/C++库来实现,非java字节码实现,所以无法统计。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280469.html