开发者社区 > 博文 > 浅谈JVM内存结构
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

浅谈JVM内存结构

  • 京东科技开发者
  • 2020-09-10
  • IP归属:北京
  • 243640浏览

    内存是操作系统中不可或缺的部分,一台机器有内存才能正常稳定地运行。编程语言历来都有自己的内存管理机制,尤其是Java,它不像C一样需要自己维护内存的关系,而是通过自己的内部机制JVM来管理内存。这虽然降低了开发同学们的入手难度,但同时也使得在运行时一旦抛出内存异常,很难知道发生了什么。以下就简单地来介绍一下JVM内存的结构。

     

    废话不多说,先看下面图表:

    1.png

    ▲图表1 JVM整体结构▲

     

    此时可能有些人会一头雾水,别着急,我们一点点来看。

     

    先看第一部分:

    1.png

    图表2 类装载系统

    这个代表了一个类被装入JVM的过程。如果用更简单的比喻,它类似初学JDBC时Class.forName()所做的事情。具体来讲,会分为以下几步:

     

    1. 加载:将字节码文件按照双亲委托机制(当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作。如果上级的类加载器没有加载,自己才会去加载这个类,方块中对应的便是Bootstrap Class Loader(启动类加载器),Extension Class Loader (标准扩展类加载器),Application Class Loader(系统类加载器))进行加载;

    2. 链接字节码文件:分为三个步骤,分别是字节码验证(verify)、class类数据结构分析(prepare)以及相应的内存分配和最后的符号表的链接(resolve);

    3. 初始化操作:比如类中静态属性和初始化赋值,以及静态块的执行等。

     

    一个类就是这样被装入了内存,那么在程序中会发生什么呢?别着急,且听我慢慢道来:


    1.png

    图表3 Java内存结构

     

    这张图是运行时数据区,表示了当前JVM的所有状态,让我们一个区一个区看看:


    1. 方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。运行时常量池也是方法区的一部分,比如String w = ”hello”;中,hello就被放在了方法区里。方法区是线程共享的。有一点要注意,JDK1.8 使用元空间 MetaSpace 替代方法区,元空间并不在 JVM中,而是使用本地内存;


    2. 堆区(Heap Area):堆区是JVM中占地最大的区域,所有的实例对象全部都在堆区上,这个位置也是线程共享的;


    3. 栈区(Stack Area):存放了每一个线程的当前状态,每一个线程都有一个自己的栈,而栈中存放了以下数据组成的一个个栈帧:操作数、局部变量表、动态链接、返回地址,需要注意的是,栈中只存引用或者基本类型,而且线程不共享(并没有指内部的优化动作);


    4. 程序计数器(PC Registers): 它是当前线程执行字节码的行号指示器。在多线程中,为了让每个线程切换回来后能够恢复原来执行的指令,就需要为每个线程启动一个PC计数器,这些计数器之间是互补影响的,因为程序计数器和栈一样都是线程私有的。当然程序计数器是JVM唯一个不会出现内存溢出的组件;


    5. 本地方法栈(Native Method Statck):保存了本地方法,它是当程序调用类库(本地方法)中的方法时才会用到它,即native method。

     

    接下来就要介绍为我们勤勤恳恳工作的执行引擎啦:


    1.png

    图表4 Java执行引擎


    Java执行分为编译执行(JIT compilation)解释执行(Interpreter)

     

    首先我们要明白什么是编译执行,什么是解释执行:

     

    Bash就是属于解释执行语言,一行行解释代码来完成命令;

    C就是编译执行, 将文件编译成字节码,接着运行。

     

    那为什么Java会用两套编译手段呢?先看下面这个例子:

     

    假定你是导演,写了个剧本,让演员表演。


    一种方式是让演员把整个剧本都背下来,吃透到脑子里,然后连续表演一个小时。

    另一种方式是让演员表演两分钟,再看两分钟脚本,思考一下,再表演两分钟,再看一会脚本,思考一下…

     

    单纯从效率上来讲,第一种方式一定会比第二种方式表演起来更熟练,但是现实往往不允许,或者不必要。如果不是非常需要表演的技巧,简单地看一下剧本就好啦。在特别考验表演技巧时,才需要背下整个剧本,这样才能在表演时更好地展现自己的风采。


    Java也是如此,由JIT发现热代码后,将指令集优化(比如重排,合并),然后生成字节码供系统运行。至于其他的代码呢,简单的解释执行完就好了~

     

    在运行的过程中一定会产生很多的垃圾,因为随着系统运行,很多对象都会废弃不用,此时就需要使用垃圾回收机制Garbage Collection(垃圾回收内容太多了,以后可以单拿出来讲讲)。

     

    最后,来看下Java与系统底层交互:

    1.png

    图表5 Java与系统底层交互


    JNI(Java本地接口)通过使用Java本地接口书写程序,可以确保代码在不同的平台上方便移植。通过JNI实现与本地方法库的调用交互,使得在Java虚拟机内运行的Java代码能够与其它编程语言互相操作,包括创建本地方法、更新Java对象、调用Java方法,引用Java类,捕捉和抛出异常等,也允许Java代码调用 C/C++或汇编语言编写的库。

     

    好啦,今天就到这里,相信大家对JVM的内存模型已经有了一定的认知,希望在遇到这种问题时,本篇内容可以帮到正在阅读的你。学习技术是一个日积月累的过程,切不可急躁~如果写代码有天意,那一定是让你修炼!