文档章节

JVM内存结构以及字节码

Oscarfff
 Oscarfff
发布于 2015/05/14 21:59
字数 3421
阅读 397
收藏 25

一、JVM内存结构

1.1 下面总体说说内存

        Java虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法。总体分为下面几个部分:

程序计数器(Program Counter Register)、JVM虚拟机栈(JVM Stacks)、本地方法栈(Native Method Stacks)、堆(Heap)、方法区(Method Area)

  jvm 管理的内存大致包括三种不同类型的内存区域:

Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。

其中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被 Load 的时候被放入 PermGen space区域,Class需要存储的内容主要包括方法和静态属性。堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。而Java栈跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:Permanent Generation space和Heap space。

1.2 下面说说具体各个结构的功能

1.2.1、程序计数器(Program Counter Register)

      这是一块比较小的内存,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它,它的作用是:JVM在解释字节码文件(.class)(每次执行class里面的方法其实就是调用JVM方法区里面的字节码指令),存储当前线程所执行的字节码的行号,只是一种概念模型,各种JVM所采用的方式不同,字节码解释器工作时,就是通过改变程序计数器的值来选取下一条要执行的指令,分支、循环、跳转、等基础功能都是依赖此技术区完成的。

       还有一种情况,就是我们常说的Java多线程方面的,多线程就是通过现程轮流切换而达到的,同一时刻,一个内核只能执行一个指令,所以,对于每一个程序来说,必须有一个计数器来记录程序的执行进度,这样,当现程恢复执行的时候,才能从正确的地方开始,所以,每个线程都必须有一个独立的程序计数器,这类计数器为线程私有的内存。如果一个线程正在执行一个Java方法,则计数器记录的是字节码的指令的地址,如果执行的一个Native方法,则计数器的记录为空,此内存区是唯一一个在Java规范中没有任何OutOfMemoryError情况的区域。

1,2.2、JVM虚拟机栈(JVM Stacks)

      JVM虚拟机栈就是我们常说的堆栈的栈(我们常常把内存粗略分为堆和栈),和程序计数器一样,也是线程私有的,生命周期和线程一样,每个方法被执行的时候会产生一个栈帧,用于存储局部变量表、动态链接、操作数、方法出口等信息。方法的执行过程就是栈帧在JVM中出栈和入栈的过程。局部变量表中存放的是各种基本数据类型,如boolean、byte、char、等8种,及引用类型存放的是指向各个对象的内存地址),因此,它有一个特点:内存空间可以在编译期间就确定,运行期不在改变。这个内存区域会有两种可能的Java异常:StackOverFlowError和OutOfMemoryError。

注意:Java 栈堆的区别与联系

      当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。   

      堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。   

1.2.3、本地方法栈(Native Method Stacks)

      从名字即可看出,本地方法栈就是用来处理Java中的本地方法的,Java类的祖先类Object中有众多Native方法,如hashCode()、wait()等,他们的执行很多时候是借助于操作系统,但是JVM需要对他们做一些规范,来处理他们的执行过程。此区域,可以有不同的实现方法,向我们常用的Sun的JVM就是本地方法栈和JVM虚拟机栈是同一个。

1.2.4、堆(Heap)

      堆内存是内存中最重要的一块,也是最有必要进行深究的一部分。因为Java性能的优化,主要就是针对这部分内存的。所有的对象实例及数组都是在堆上面分配的(随着JIT技术的逐渐成熟,这句话视乎有些绝对,不过至少目前还基本是这样的),可通过-Xmx和-Xms来控制堆的大小。JIT技术的发展产生了新的技术,如栈上分配和标量替换,也许在不久的几年里,即时编译会诞生及成熟,那个时候,“所有的对象实例及数组都是在堆上面分配的”这句话就应该稍微改改了。堆内存是垃圾回收的主要区域,所以在下文垃圾回收板块会重点介绍,此处只做概念方面的解释。在32位系统上最大为2G,64位系统上无限制。可通过-Xms和-Xmx控制,-Xms为JVM启动时申请的最小Heap内存,-Xmx为JVM可申请的最大Heap内存。

1.2.5、JVM 方法区(Method Area)

      方法区是所有线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变量等数据,一般来说,方法区属于持久代(关于持久代,会在GC部分详细介绍,除了持久代,还有新生代和旧生代),也难怪Java规范将方法区描述为堆的一个逻辑部分,但是它不是堆。方法区的垃圾回收比较棘手,就算是Sun的HotSpot VM在这方面也没有做得多么完美。此处引入方法区中一个重要的概念:运行时常量池。主要用于存放在编译过程中产生的字面量(字面量简单理解就是常量)和引用。一般情况,常量的内存分配在编译期间就能确定,但不一定全是,有一些可能就是运行时也可将常量放入常量池中,如String类中有个Native方法intern()<关于intern()的详细说明,请看另一篇文章:http://blog.csdn.net/zhangerqing/article/details/8093919>

此处补充一个在JVM内存管理之外的一个内存区:直接内存。在JDK1.4中新加入类NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,即我们所说的直接内存,这样在某些场景中会提高程序的性能。

1.2.6 JVM 垃圾回收(GC)

       在我们上面介绍的五大区中,有三个是不需要进行垃圾回收的:程序计数器JVM栈本地方法栈。因为它们的生命周期是和线程同步的,随着线程的销毁,它们占用的内存会自动释放,所以只有方法区和堆需要进行GC。具体到哪些对象的话,简单概况一句话:如果某个对象已经不存在任何引用,那么它可以被回收。通俗解释一下就是说,如果一个对象,已经没有什么作用了,就可以被当废弃物被回收了。

       垃圾回收算法:

       根据一个经典的引用计数算法,每个对象添加一个引用计数器,每被引用一次,计数器加1,失去引用,计数器减1,当计数器在一段时间内保持为0时,该对象就认为是可以被回收得了。但是,这个算法有明显的缺陷:当两个对象相互引用,但是二者已经没有作用时,按照常规,应该对其进行垃圾回收,但是其相互引用,又不符合垃圾回收的条件,因此无法完美处理这块内存清理,因此Sun的JVM并没有采用引用计数算法来进行垃圾回收。而是采用一个叫:根搜索算法,如下图:

       基本思想就是:从一个叫GC Roots的对象开始,向下搜索,如果一个对象不能到达GC Roots对象的时候,说明它已经不再被引用,即可被进行垃圾回收(此处 暂且这样理解,其实事实还有一些不同,当一个对象不再被引用时,并没有完全“死亡”,如果类重写了finalize()方法,且没有被系统调用过,那么系统会调用一次finalize()方法,以完成最后的工作,在这期间,如果可以将对象重新与任何一个和GC Roots有引用的对象相关联,则该对象可以“重生”,如果不可以,那么就说明彻底可以被回收了),如上图中的Object5、Object6、Object7,虽然它们3个依然可能相互引用,但是总体来说,它们已经没有作用了,这样就解决了引用计数算法无法解决的问题。


1.3 字节码基础:JVM字节码初探

欢迎来到“Under The Hood“第三期。前两期我们分别介绍了JVM的基本结构和功能Java类文件的基本结构,本期的主要内容有:字节码所操作的原始类型、类型转换的字节码,以及操作JVM栈的字节码。

字节码格式

字节码是JVM的机器语言。JVM加载类文件时,对类中的每个方法,它都会得到一个字节码流。这些字节码流保存在JVM的方法区中。在程序运行过程中,当一个方法被调用时,它的字节码流就会被执行。根据特定JVM设计者的选择,它们可以通过解释的方式,即时编译(Just-in-time compilation)的方式或其他技术的方式被执行。

方法的字节码流就是JVM的指令(instruction)序列。每条指令包含一个单字节的操作码(opcode)和0个或多个操作数(operand)。操作码指明要执行的操作。如果JVM在执行操作前,需要更多的信息,这些信息会以0个或多个操作数的方式,紧跟在操作码的后面。

每种类型的操作码都有一个助记符(mnemonic)。类似典型的汇编语言风格,Java字节码流可以用它们的助记符和紧跟在后面的操作数来表示。例如,下面的字节码流可以分解成多个助记符的形式。

// 字节码流: 03 3b 84 00 01 1a 05 68 3b a7 ff f9
// 分解后:
iconst_0      // 03
istore_0      // 3b
iinc 0, 1     // 84 00 01
iload_0       // 1a
iconst_2      // 05
imul          // 68
istore_0      // 3b
goto -7       // a7 ff f9

字节码指令集被设计的很紧凑。除了处理跳表的2条指令以外,所有的指令都以字节边界对齐。操作码的总数很少,一个字节就能搞定。这最小化了JVM加载前,通过网络传输的类文件的大小;也使得JVM可以维持很小的实现。

JVM中,所有的计算都是围绕栈(stack)而展开的。因为JVM没有存储任意数值的寄存器(register),所有的操作数在计算开始之前,都必须先压入栈中。因此,字节码指令主要是用来操作栈的。例如,在上面的字节码序列中,通过iload_0先把本地变量(local variable)入栈,然后用iconst_2把数字2入栈的方式,来计算本地变量乘以2。两个整数都入栈之后,imul指令有效的从栈中弹出它们,然后做乘法,最后把运算结果压入栈中。istore_0指令把结果从栈顶弹出,保存回本地变量。JVM被设计成基于栈,而不是寄存器的机器,这使得它在如80486寄存器架构不佳的处理器上,也能被高效的实现。


本文转载自:http://blog.csdn.net/zhangerqing/article/details/8214365

Oscarfff
粉丝 73
博文 816
码字总数 97116
作品 0
崇明
后端工程师
私信 提问
JVM系列第6讲:Java 虚拟机内存结构

看到这里,我相信大家对于一个 Java 源文件是如何变成字节码文件,以及字节码文件的含义已经非常清楚了。那么接下来就是让 Java 虚拟机运行字节码文件,从而得出我们最终想要的结果了。在这个...

陈树义
2018/11/16
0
0
JVM规范系列开篇:为什么要读JVM规范?

许多人知道类加载机制、JVM内存模型,但他们可能不知道什么是《Java虚拟机规范》。对于Java开发来说,《Java虚拟机规范》才是最为官方、准确的一个文档,了解这个规范可以让我们更深入地理解...

陈树义
2018/12/19
0
0
深入理解JVM内幕:从基本结构到Java 7新特

摘要:许多没有深入理解JVM的开发者也开发出了很多非常好的应用和类库。不过,如果你更加理解JVM的话,你就会更加理解Java,这样你会有助于你处理类似于我们前面的案例中的问题。 每个Java开...

开源中国驻成都办事处
2012/12/06
296
1
JVM(二)Java虚拟机组成详解

导读:详细而深入的总结,是对知识“豁然开朗”之后的“刻骨铭心”,想忘记都难。 Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我们对jvm有了大体的认识,进入本文之后我们将具体...

王磊的博客
01/14
135
0
Jvm与字节码——类的方法区模型

从一个类开始 我们从一个简单类开始说起: 这是一段平凡得不能再平凡的Java代码,稍微有点编程语言入门知识的人都能理解它表达的意思: 创建一个名为SimpleClass的类; 定义一个入口main方法...

溜达向日葵
2018/08/31
0
0

没有更多内容

加载失败,请刷新页面

加载更多

rime设置为默认简体

转载 https://github.com/ModerRAS/ModerRAS.github.io/blob/master/_posts/2018-11-07-rime%E8%AE%BE%E7%BD%AE%E4%B8%BA%E9%BB%98%E8%AE%A4%E7%AE%80%E4%BD%93.md 写在开始 我的Arch Linux上......

zhenruyan
今天
5
0
简述TCP的流量控制与拥塞控制

1. TCP流量控制 流量控制就是让发送方的发送速率不要太快,要让接收方来的及接收。 原理是通过确认报文中窗口字段来控制发送方的发送速率,发送方的发送窗口大小不能超过接收方给出窗口大小。...

鏡花水月
今天
9
0
OSChina 周日乱弹 —— 别问,问就是没空

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享容祖儿/彭羚的单曲《心淡》: 《心淡》- 容祖儿/彭羚 手机党少年们想听歌,请使劲儿戳(这里) @wqp0010 :周...

小小编辑
今天
959
11
golang微服务框架go-micro 入门笔记2.1 micro工具之micro api

micro api micro 功能非常强大,本文将详细阐述micro api 命令行的功能 重要的事情说3次 本文全部代码https://idea.techidea8.com/open/idea.shtml?id=6 本文全部代码https://idea.techidea8....

非正式解决方案
今天
5
0
Spring Context 你真的懂了吗

今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识。 1. context 是什么 我们经常在编程中见到 context 这个单词,当...

Java知其所以然
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部