虚拟机篇 之「运行时数据区域及虚拟机对象」

温馨提示:本系列博文(含示例代码)已经同步到 GitHub,地址为「java-skills」,欢迎感兴趣的童鞋StarFork,纠错。

运行时数据区域

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁,这些区域被称之为运行时数据区域,其划分大致如下图所示:

runtimedata

  • 程序计数器:它是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的 Native 方法,这个计数器的值则为空。
  • 虚拟机栈:与程序计数器一样,虚拟机栈也是线程私有的,它的声明周期与线程相同。虚拟机栈描述的是 Java 方法的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、动态链接、方法出口等信息。每一个方法从调用到执行完成,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress,其中 64 位长度的longdouble类型的数据会占用 2 个局部变量空间(Slot),其余的数据类型只占用 1 个。
  • 本地方法栈:本地方法栈与虚拟机栈的用途一样,只不过虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。此区域并不是虚拟机规范中强制必须实现的,因此如 HotSpot 就像虚拟机栈和本地方法栈合二为一。
  • :对于大多数应用来说, Java 堆是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称作“GC堆”。根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可,就像我们的磁盘空间一样。目前主流的虚拟机都是可以支持堆扩展的,具体可以通过-Xms-Xmx来控制,其中-Xms为设置最小堆内存,-Xmx为设置最大堆内存。
  • 方法区:方法区与 Java 堆一样,都是线程共享的,它用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。在 HotSpot 中,方法区也被称之为“永久代”,并且可以通过-XX:PermSize-XX:MaxPermSize来控制方法区的大小,其中-XX:PermSize为设置方法区最小可用内存,-XX:MaxPermSize为设置方法区最大可用内存。

实际上,除了上述的运行时数据区域之外,还有一个内存区域值得我们了解,即

  • 直接内存:它并不是虚拟机运行时数据区的一个部分,也不是 Java 虚拟机规范中定义的内存区域,但在实际应用中,这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常的出现。在 JDK 1.4 中新加入了NIO类,引入了一种基于通道与缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配对外内存,然后通过一个存储在 Java 堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景汇总显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是既然是内存,肯定还是会收到本机总内存大小以及处理器寻址空间的限制。因此,如果我们在分配内存的时候,忽略了直接内存,很可能使得各个内存区域总和大于物理内存限制,从而导致动态扩展的时候出现OOM异常。

HotSpot 虚拟机对象

对象的创建

在虚拟机中创建对象,大致经过以下这些步骤,分别为:

  • 加载类:当虚拟机遇到一个new指令时,首先将去检查这个指令的参数是否能在常量池汇总定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
  • 分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。分配内存的方式主要有“指针碰撞”和“空闲列表”两种,而采用哪种内存分配方式则取决于堆内存是否规整,而堆内存是否规整则又取决于垃圾收集器是否带有压缩整理功能决定。此外,由于对象的创建操作非常频繁,因此导致对象的内存分配操作也非常频繁,为了保证线程安全,常见的解决方案主要有“CAS加失败重试”和“本地线程分配缓冲”两种。
  • 设置必要信息:在对象的内存分配完成后,虚拟机将对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息,这些信息存放在对象的对象头之中。

在上面的工作都完成后,从虚拟机的角度来看,一个新的对象已经产生了,但是从 Java 的角度来看,对象的创建才刚刚开始,<init>方法还没有执行,所有的字段都还未零。所以,一般来说(由字节码中是否跟随着invokespecial指令所决定),执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全创建出来。

对象的内存布局

在 HotSpot 虚拟机中,对象的内存中存储的布局可以分为 3 块区域:对象头、实例数据和对其填充。

  • 对象头:它大致包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等;第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 实例数据:它存储了对象真正有效的信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继续下来的,还是在子类中定义的,都需要记录起来。这部分的存储顺序会受到虚拟机分配策略参数和字段在 Java 源码中定义顺序的影响。
  • 对齐填充:它不是必然存在的,也没有特别的含义,仅仅起着占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说,就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数,因此当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

对象的访问定位

创建对象是为了使用对象,我们的 Java 程序需要通过栈上的reference数据来操作堆上的具体对象。由于虚拟机规范仅规定了reference是一个引用对象,具体如何实现、如何定位、如何访问并没有做限制,因此对象访问方式也是取决于虚拟机实现而定的。目前主流的访问方式有使用“句柄”和“直接指针”两种。

  • 句柄:如果使用句柄访问的话,那么 Java 堆中将会划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包括了对象实例数据与数据类型各自的具体地址信息。使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普通的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
  • 直接指针:如果使用直接指针访问的话,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。使用直接指针访问的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在 Java 中非常频繁,因此这类开销极少成多以后也是一项非常可观的执行成本。HopSpot VM 就是使用直接指针的方式进行对象访问的。

———— ☆☆☆ —— 返回 -> 那些年,关于 Java 的那些事儿 <- 目录 —— ☆☆☆ ————

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安正勋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值