设为首页 加入收藏

TOP

一点一滴探究 JVM 之内存结构(一)
2017-12-07 14:22:15 】 浏览:397
Tags:一点一滴 探究 JVM 内存 结构

前言

我一直尝试着用不一样的文字来写博客!原因很简单,你讲的知识书上都有,那么每个人为什么不选择看书而选择看你的博文来学习呢?因为书上的内容都是大片大片描述性的文字,对于jvm这块的知识,又是异常枯燥,但又不能不学习的硬骨头!这恰好也就能说明Head First系列的书籍为什么比较火的原因,每个人接收图形知识的速度往往比文字性的东西要快很多。今后我也会尝试用自己的特色来写博客,尽量能引起读者的兴趣,能从中学到东西,我就知足了!

今天的一点一滴探究JVM系列,打算复习一下jvm内存结构!至于学习这块知识的好处?一,从面试的角度来看,你了解jvm,并且java基础扎实,你才更有竞争力(因为我本人本科还没毕业,所以考虑问题经常从面试者的角度来考虑)。其二,提高你对java的理解,知道你创建的每一个对象,每一个变量,都在什么地方,如果不知道这些稀里糊涂得写代码,总会有一天会”翻车”的!好了,废话不多说了,我们开始正题吧!

开始之前

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的”墙”, 墙外的人想进去,墙内的人想出来。
或许你经常看到StackOverFlowError, OutOfMemoryError无从下手,因为你压根不知道,究竟是什么东西造成内存爆了,当然,你也无法解决!

举个简单的例子

public class test {
    private int f() {
        f();
    }
    public static void main(String[] args) {
        f();
    }
}

这个简单的递归,不对,它不算是递归,因为没有终止条件,但是你知道它最终会报什么错误,知道为什么会报这个错误吗?究竟是那块内存发生了错误?

这个问题,我们留在后面回答,是留在后面你自己解答,看完这篇博文,不用我说,这些问题你都会很清楚!相信我!

目标

你可能会好奇,你看完这篇文章你能学到什么?

  • 清楚你的对象会被分配在哪里(不绝对)
  • 理解哪些区域对线程来说是私有区,哪些区域是线程共享区域
  • 知道方法调用发生了什么?

等等等,你可能还会解释你以前遇到一些匪夷所思的问题!总之,你如果之前没了解过这些知识,那么这些东西对你来说,就是成长!

墙内的世界

你可能很好奇,墙内究竟是什么样?接下来跟着我一探究竟

上图就是jvm比较详细的内存划分,下面我们来按线程私有共享来划分jvm内存区

 

下面我们来着重介绍一下这几块内存区域

程序计数器(Program Counter Register)

什么是程序计数器呢,学过汇编的都知道,cs:ip组成的物理地址是下一条要执行的指令的地址,来吧!看图

我们可以很清楚的看到,当前cs:ip指向的内存地址恰好就是我们要执行的下一条指令的位置,前面我们图中(按线程私有共享划分jvm内存的图)又说了,程序计数器是线程私有的,再联想一下我举cs:ip的例子,我们可以很自然的想到,程序计数器其实就是记录线程当前执行到了哪一条指令,因为什么要记录这个值呢?因为,如果我们有很多个线程,线程执行顺序又是不可预料的,假如某一时刻我们在执行线程A里面的指令,然后线程B又获得了cpu的资源,去执行去线程B的指令,假如再过了一段时间之后,A又获得了cpu的资源,想吃回头草,此时回到线程A执行,它不知道要执行线程A的哪条指令!这是没有程序计数器所形成的尴尬局面,但是有了线程私有的程序计数器,这个问题就不存在了,这就是程序计数器出现的原因,以及它的用处,我想你看完这段文字,应该已经对程序计数器这个概念完全理解了!

另外,我需要说明的一点是,程序计数器是Java虚拟机规范中唯一一个没有规定任何内存错误的区域!

虚拟机栈(Vm Stack)

这块区域是干啥的?为啥也是线程私有的?

虚拟机栈描述的是Java方法执行的内存模型
我们来解读这句话,为什么说Vm Stack是描述Java方法执行的内存模型呢?其实:

每个方法执行的时候都会创建一个栈帧(Stack Frame)的东西,学过c/c++的应该都对这个概念熟悉。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口信息等。每个方法从调用开始到结束的过程,都对应这Vm Stack中的入栈出栈的过程!这也就能回答开头我们看到的那个问题了,很简单错误在单线程情况下肯定是StackOverFlowError,多线程下OutOfMemoryError(上图已经写得很清楚了)

比如

public void test() {
    String name = "stormma";
    int age = 21;
}

上面的例子的age变量和name引用都是存储在虚拟机栈的栈帧里面的(因为我们前面说过了,一个方法从开始调用到结束调用的过程都对应着一个Vm Stack出栈入栈的过程)。

我们前面说了,这块区域存储了局部变量表,操作数栈,动态链接,还有方法出口信息等,我想你应该比较好奇这几个概念。

  • 局部变量表: 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和(returnAddress)类型(它指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成计算的,即在Java程序被编译成Class文件时,就确定了所需分配的最大局部变量表的容量。当进入一个方法时,这个方法需要在栈中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
  • 操作数栈: 操作数栈又常被称为操作栈,操作数栈的最大深度也是在编译的时候就确定了。32位数据类型所占的栈容量为1, 64位数据类型所占的栈容量为2。当一个方法开始执行时,它的操作栈是空的,在方法的执行过程中,会有各种字节码指令(比如:加操作、赋值元算等)向操作栈中写入和提取内容,也就是入栈和出栈操作。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。基于栈的指令集最主要的优点是可移植性强,主要的缺点是执行速度相对会慢些;而由于寄存器由硬件直接提供,所以基于寄存器指令集最主要的优点是执行速度快,主要的缺点是可移植性差
  • 动态链接: 每个栈帧都包含一个指向运行时常量池(在方法区中,后面介绍)中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如 final、static 域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。
  • 方法返回地址: 当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇搞定所有的跨域请求问题 : jsonp .. 下一篇Redis 分布式锁的正确实现方式( ..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目