ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

深入理解Java虚拟机:深入理解JVM内存区域

2021-07-24 22:34:37  阅读:116  来源: 互联网

标签:Java 常量 对象 虚拟机 理解 深入 内存 线程


什么是Java虚拟机

这里只给出我自己的理解,Java设计的初衷就是为了能让程序能够忽视操作系统的差别,在任何一台机器上都能运行,为此有了虚拟机。

我们写出的.java文件托管给虚拟机程序,虚拟机程序负责加载类,分配内存,加载必要的class文件。虚拟机程序负责把class文件翻译成cpu可以执行的指令,这才在电脑

的CPU上运行。

虚拟机其实是一个进程,包括内存管理,垃圾回收等等知识,

运行时数据区域

Java虚拟机把管理的内存划分为不同的区域,包括每个线程独立的区域与共享区域。

线程私有区域:程序计数器,虚拟机栈,本地方法栈。

共享区域:方法区,堆。

接下来简要介绍一下这些区域,只是带大家了解一下这些区域的功能,具体细节以后都会介绍到。

程序计数器

熟悉CPU的人应该都对程序计数器有印象。在虚拟机中,程序计数器可以看作当前线程执行字节码的行号,每个线程有独立的程序计数器。

Java虚拟机栈

虚拟机栈和操作系统的栈差不多,负责Java方法执行的线程内存模型。每个方法执行时,虚拟机会创建出一个栈帧,用于储存局部变量表,操作数栈,动态链接等待。

每个方法被调用到执行完毕,对应一个栈帧从出栈到入栈的过程。

局部变量表保存方法的各种基本数据类型,对象的引用。局部方法表空间大小在编译时就确定了,进入一个方法时,只需要分配相应大小的空间,不会改变该大小。

如果栈深度大于虚拟机允许的深度,就抛出StackOverflowError,如果栈可以动态拓展(以后会介绍)就抛出 OutOfMemoryError 异常。

本地方法栈

本地方法栈和虚拟机栈差不多,只不过虚拟机栈是为了Java方法,本地方法是为了 Native 方法,

Native 方法是用其他语言编写的方法,很多Java API底层的核心都是Native 方法,读者可以自行查看。

Java堆

Java堆是所有线程共享的,也是虚拟机管理内存中最大的一块区域,用于存放对象实例。对象引用放于虚拟机栈中。

Java堆是垃圾收集器管理的内存区域,垃圾收集器(不是虚拟机)把Java堆分成新生代,老年代等区域,用于更方便的管理回收对象。

为了提升分配对象的效率,Java堆中又划分出多个线程私有的分配缓冲区。

如果没有内存可以给实例分配,且堆无法再拓展时,就会抛出OutOfMemoryError

方法区

方法区和Java堆一样,都是所有线程共享的,用于保存虚拟机加载的类型信息,常量,静态变量。

但方法区无法满足新内存分配的需求时,将抛出OutOfMemoryError。

运行时常量池

运行时常量池是方法区的一部分,当类被加载入虚拟机时,类的常量池表会被存放入运行时常量池中。

运行时常量池是动态的,常量不一定只有运行时才能产生,运行期间也可以把新的常量放入池中。(如String类的intern方法)

当常量池无法再申请到内存时会抛出OutOfMemory异常。

直接内存

直接内存不是虚拟机运行时数据区的一部分,当因为被频繁的使用所以也需要介绍。

直接内存是用于在Java的堆外分配对象的内存,不受Java虚拟机的控制,受操作系统的控制。

为什么有时要使用直接内存而不是Java堆来保存对象实例呢?

首先因为堆内存由JVM管理,属于“用户态”,堆外内存由操作系统管理,属于“内核态”。当要把实例写入磁盘时,要先从堆中复制到堆外内存,然后由操作系统负责写入。如果使用直接内存显然能省去复制这一步。

其次因为直接内存不受虚拟机管理,就没有垃圾回收,减少了垃圾回收对程序的影响。

探秘Java堆

在了解虚拟机运行的数据区域后,我们来探讨一下Java堆中对象分配,布局,访问全过程。

对象的创建

对我们来说创建对象只是一个new关键字,而在虚拟机中是怎么样的呢。

1.加载类

当虚拟机遇到一条new的指令时,首先检查这个new指令的参数能否在常量池(常量池是用于保存类信息的,运行时常量池保存常量)定位到一个类的符号引用。检查这个类是否已经被加载,解析,初始化过。如果没有,先执行相应类的加载过程。

2.分配内存

当加载类结束后,要为对象分配内存。所要内存大小在类加载完全后可确定,分配内存有两种方式。

第一种“指针碰撞”,假如Java堆中内存是规整的,用一个指针当分界点,一边是使用的内存,另一边是空闲的内存。那分配内存只是把该指针往空闲方向移动所要大小的距离。

第二种“空闲列表”,如果Java堆中内存不是规整的,那虚拟机必须维护一个列表,用于指示那些内存是可用的,从中挑出一块适合的内存分配,并更新该列表。

堆是否规则用采用的垃圾收集器是否带有空间压缩整理决定。

创建对象除了完成上面的步骤外,还要考虑3.安全问题。即使是修改一个指针的位置在并发的情况下也不是安全的。假如正给A分配内存,指针还没修改,B又使用该指针分配内存。

解决并发安全问题有两种,一:使用CAS配上失败重试保证操作原子性。二:用先前提到的堆中私有分配缓存区,因为是线程私有的,所以不存在并发安全问题。只有缓冲区用完了,分配新的缓冲区才需要同步锁定。

4.初始化空间

内存分配完成后,虚拟机把分配到的空间(不包括对象头初始化为零值),这就是Java字段默认值的由来。

5.必要设置

接下来,虚拟机对对象进行必要的设置,如这个对象是哪个类的实例,如何找到类的元数据信息等(相信大家肯定看的一知半解,但没关系,了解就行)。

执行完上述步骤,从虚拟机的角度看对象已经分配完了,但从Java程序的角度看,才刚刚开始。对象的构造函数还没有执行,所有字段都为零值。

对象的内存布局

了解完对象是如何创建的,接下来我们看看对象具体内存细节。

对象在堆内的储存布局可以划分为三个部分:对象头,实例数据,对齐填充。

对象头:对象头一部分用于保存运行时数据,如哈希码,锁等,另一部分用于保存类型指针,既对象指向它的类型元数据的指针。这些都对应上面内存分配的知识。

实例数据:实例数据用于保存对象真正的有效信息。

对齐填充:起到占位符的作用,用来保证虚拟机规定的对象起始地址必须是8字节的整数倍。

标签:Java,常量,对象,虚拟机,理解,深入,内存,线程
来源: https://www.cnblogs.com/hitsz-yc/p/15013268.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有