资讯专栏INFORMATION COLUMN

Java 内存区域详解

darry / 2834人阅读

摘要:三对象的内存布局对象在堆中的布局分为三个区域对象头,实例数据,对齐填充。总结了解内存区域是对的深入学习,以前只知道有堆和栈的区分,现在我们了解到了具体的堆栈的作用。

引言

学习Java也有一段时间了,总感觉有些东西学的不是很精通。例如Java内存区域到底是怎么样的?程序是怎么跑的?对象是怎么存放的?这些都影响了我对自己的程序运行的熟悉程度。

一. 运行时数据区域

Java虚拟机在执行java程序的过程中,会把它所管理的内存划分成若干个不同的数据区域(每当运行一个java程序都会启动一个虚拟机)。有一本书叫做《Java虚拟机规范》,讲述了Sun公司对Java虚拟机实现的相关规范,其中讲了虚拟机将所管理的内存分为以下几个部分:

程序计数器
虚拟机栈

本地方法区

方法区

其中方法区和堆是由所有线程共享的,例如使用ThreadPoolExecutor创建多个线程时,堆与方法区都可以被多个线程读取。

程序计数器 学过计算机组成原理的人都会知道在CPU的寄存器中有一个PC寄存器,存放下一条指令地址,这里,虚拟机不使用CPU的程序计数器,自己在内存中设立一片区域来模拟CPU的程序计数器。只有一个程序计数器是不够的,当多个线程切换执行时,那就单个程序计数器就没办法了,虚拟机规范中指出,每一条线程都有一个独立的程序计数器。注意,Java虚拟机中的程序计数器指向正在执行的字节码地址,而不是下一条。

虚拟机栈线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的时候都会创建一个栈帧(我觉得可以把它看作是一个快照,记录下进入方法前的一些参数,实际上是方法运行时的基础数据结构),用于存放局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直到执行完成的过程都对应着一个栈帧在虚拟机中的入栈到出栈的过程。我们平时把内存分为堆内存和栈内存,其中的栈内存就指的是虚拟机栈的局部变量表部分。局部变量表存放了编译期可以知道的基本数据类型,对象引用,和返回后所指向的字节码的地址。

本地方法区虚拟机栈 所发挥的作用很类似,但是要注意一下,虚拟机规范中没有对本地方法区中的方法作强制规定,虚拟机可以自由实现,即可以不是字节码。但是也可以是字节码,这样虚拟机栈和本地方法区就可以合二为一,事实上,OpenJDKSunJDK所自带的HotSpot虚拟机就直接将虚拟机栈和本地方法区合二为一。

这个概念应该很多人都很熟悉,例如初学C语言的时候,老师就会讲malloc方法会在堆中分配空间,这里也一样。这个区域是用来存放对象实例的,几乎所有对象实例都会在这里分配内存,虚拟机规范中讲:所有对象的实例以及数组都要在堆上分配。但是随着JIT(Just-in-time) 编译期的发展,有些时候也有可能在栈上分配(这里我也不是很明白其中的道理)。堆是java垃圾收集器管理的主要区域(很多时候会称为GC堆,不叫垃圾堆),垃圾收集器实现了对象的自动销毁。

方法区 也是各个线程共享的区域,它用于存储已经被虚拟机加载过的类信息,常量,静态变量,及时编译期编译后的代码(类方法)等数据。这里要讲一下运行时常量池,它是方法区的一部分,用于存放编译期生成的各种字面量和符号引用(其实就是八大基本类型的包装类型和String类型数据)。

最后还有一个直接内存,在JDK1.4版本中加入了NIO类,引入了基于通道(Channel)与缓冲区(Buffer)的I/O方式,也就是说通过这种方式,不会在运行时数据区域分配内存,这样就避免了在运行时数据区域来回复制数据,直接调用外部内存。

二. 对象的创建

对于面向对象的一门语言,我们无时不在通过new关键字创建对象,那么这个过程又是怎样的呢?

当虚拟机遇到一条new指令的时候,首先会去检查所new的类是否已经被加载,在哪里检查?当然在方法区,方法区存放了加载过的类信息。如果没有加载,那么先执行类的加载。

通过类加载检查后,虚拟机开始为新生对象分配内存,对象所需要的内存大小在类加载完成后已经可以确定,这时候只要在堆中分配空间即可。分配内存有两种方式,第一种,我们假设内存绝对规整,那么只要在用过的内存和没用过的内存间放置一个指针即可,每次分配空间的时候只要把指针向空闲空间移动相应距离即可。第二种,我们假设空闲内存和非空闲内存夹杂在一起,实际上就是这种情况,那么就需要一个列表,去记录堆内存的使用情况,操作系统对内存的管理就是这样的。

那么,我们还要考虑一个问题,即在多线程的情况下,只有一个指针怎么能确保一个线程分配了内存指针没修改的时候另一个线程又分配内存不会覆盖之前的内存呢?这里有一种方法,让每一个线程在堆中先预分配一小块内存(TLAB本地线程分配缓冲),每个线程只在自己的内存中分配内存。

最后,对象被成功分配内存。我们知道通过一个对象,我们可以通过getClass()方法获取类,默认比较两个对象实际比较的是对象内存的哈希值,这又是怎么实现的呢?其实在分配完内存后,虚拟机会对对象进行必要的设置,对象的类,对象的哈希码等信息都存放在对象的对象头中,所以分配的内存大小绝不止属性的总和。

三. 对象的内存布局

对象在堆中的布局分为三个区域:对象头实例数据对齐填充

对象头 包括两个部分,第一部分用于存储自身运行时的数据例如GC标志位,MonirGC次数,哈希码,锁状态,哪个线程可以拥有等被称为MarkWord(标记字)。第二部分存放指向方法区类数据的指针。在32位系统中,class指针大小为4字节,标记字大小为4字节。在64位系统中标记字大小为8字节。

实例数据 存放类的属性信息,包括父类的属性信息。数组的实例部分还包括数组的长度。实例信息按类分别4字节对齐。

对齐填充 这是虚拟机要求对象起始地址必须是8字节的整数倍,可以说对齐填充没有什么特别的含义。

四. 对象的访问定位

我们知道,引用是引用,对象实例是对象实例。引用存放在虚拟机栈中,数据类型为reference,对象实例存放在堆中。那么引用是如何指向对象实例的呢?

主流的访问方式有两种,第一种是通过句柄池,如果使用句柄池,那么java堆中将会划分出一部分内存作为句柄池,句柄包含对象类型指针指向方法区的类型信息,还有对象实例指针,指向堆中的实例地址。

第二种是reference引用直接指向堆中的对象实例,对象实例的对象头存放对象类型指针。

两种方法各有优势,第一种可以在对象实例在GC时移动的时候只改变句柄池中的对象实例指针,而不用改变reference引用本身。第二种方法就是访问速度快,减少了一次指针定位的时间开销。目前HotSpot虚拟机就采用的第二种方式。

总结

了解java内存区域是对java的深入学习,以前只知道有堆和栈的区分,现在我们了解到了具体的堆栈的作用。内存是怎么划分的,对象是怎么存储的,方法和属性的存放区别。通过对这些内容的了解,会让我们写java程序更加游刃有余,有的放矢。
更多文章:http://blog.gavinzh.com

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64374.html

相关文章

  • JVM系列(一):深入详解JVM 内存区域总结!

    摘要:一内存区域虚拟机在运行时,会把内存空间分为若干个区域,根据虚拟机规范版的规定,虚拟机所管理的内存区域分为如下部分方法区堆内存虚拟机栈本地方法栈程序计数器。前言 在JVM的管控下,Java程序员不再需要管理内存的分配与释放,这和在C和C++的世界是完全不一样的。所以,在JVM的帮助下,Java程序员很少会关注内存泄露和内存溢出的问题。但是,一旦JVM发生这些情况的时候,如果你不清楚JVM内存的...

    Aldous 评论0 收藏0
  • JVM虚拟机详解

    摘要:虚拟机包括一套字节码指令集一组寄存器一个栈一个垃圾回收堆和一个存储方法域。而使用虚拟机是实现这一特点的关键。虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。此内存区域是唯一一个在虚拟机规范中没有规定任何情况的区域。 1、 什么是JVM?   JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,...

    rottengeek 评论0 收藏0
  • JAVA GC 原理详解

    摘要:虚拟机栈区也就是通常所说的栈区,它描述的是方法执行的内存模型,每个方法被执行的时候都创建一个栈帧,用于存储局部变量表操作数栈动态链接方法出口等。每个方法被调用到完成,相当于一个栈帧在虚拟机栈中从入栈到出栈的过程。 大多数情况下我们对GC的了解都只是浅层含义上的,下面我们来详细讲解下内部的一些实现原理。讲解GC之前,我们得先了解下JVM的内存结构,才能让我们理解GC导致是干嘛的。 一.J...

    wangjuntytl 评论0 收藏0
  • JVM详解1.Java内存模型

    摘要:编译参见深入理解虚拟机节走进之一自己编译源码内存模型运行时数据区域根据虚拟机规范的规定,的内存包括以下几个运运行时数据区域程序计数器程序计数器是一块较小的内存空间,他可以看作是当前线程所执行的字节码的行号指示器。 点击进入我的博客 1.1 基础知识 1.1.1 一些基本概念 JDK(Java Development Kit):Java语言、Java虚拟机、Java API类库JRE(...

    TANKING 评论0 收藏0
  • 四年来Android面试大纲,作为一个Android程序员

    摘要:再附一部分架构面试视频讲解本文已被开源项目学习笔记总结移动架构视频大厂面试真题项目实战源码收录 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava异常详解Java抽象类和接口的区别Java深拷贝和浅拷...

    不知名网友 评论0 收藏0

发表评论

0条评论

darry

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<