浅谈JVM内存结构

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

 

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

▲图表1 JVM整体结构▲

 

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

 

先看第一部分:

▲图表2 类装载系统▲

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

 

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

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

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

 

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

▲图表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。

 

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

▲图表4 Java执行引擎▲

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

 

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

 

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

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

 

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

 

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

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

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

 

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

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

 

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

 

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

▲图表5 Java与系统底层交互▲

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

 

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值