JVM系列--虚拟机的内存管理

简介: JVM系列--虚拟机的内存管理

Java语言和其他语言在内存管理的区别



对比其他语言,例如C语言,在内存管理方面,Java要做得更加“智能”一些。主要是因为Java语言提供了相关的虚拟机进行内存管理。


通常在C语言里面,创建一个对象之后需要手动进行对象内存的delete,free处理。例如这段代码:


#include <iostream>
using namespace std;
int main() {
    cout << "free begin " << endl;
    void* p = malloc(1024 * 1024 * 10 * sizeof(int));
    free(p);
    cout << "free end ";
}
复制代码


代码内部需要手动执行free函数。


而在Java程序中却没有这类操作对存在,关于内存对分配和释放对于开发人员来说是完全透明的,主要工作交给了Java虚拟机去完成。但是这样的设计也有弊端:一旦出现了内存泄漏排查也比较困难。


内存管理比对图


网络异常,图片无法展示
|


JVM的内存布局–程序计数器



在Java虚拟机里面(这里主要是讲解jdk1.8版本),主要分为了以下部分,如下图所示:


程序计数器其实就是当前程序所运行的字节码行号指令器。字节码指令在工作的时候通过程序计数器的值来获取下一条指令值,程序计数器的值相当于不同指令所在的内存地址。


下边我们通过一段代码来查看分析。


public class TestDemo {
    private int addOne(int a){
        return a+1;
    }
    public static void main(String[] args) {
        TestDemo testDemo = new TestDemo();
        int p = 1;
        int j = 2;
        int result = testDemo.addOne(p) + j;
        System.out.println(result);
    }
}
复制代码


这样的一段代码,通过Javac 命令先进行编译为class文件,然后再通过Javap -c 查看字节码内容,就会得出这么一份内容:


public class org.idea.netty.framework.server.test.TestDemo {
  public org.idea.netty.framework.server.test.TestDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class org/idea/netty/framework/server/test/TestDemo
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: iconst_1
       9: istore_2
      10: iconst_2
      11: istore_3
      12: aload_1
      13: iload_2
      14: invokespecial #4                  // Method addOne:(I)I
      17: iload_3
      18: iadd
      19: istore        4
      21: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      24: iload         4
      26: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      29: return
}
复制代码


看到最左边的字节码序列号,在14号的位置,执行了一次addOne操作,在该函数执行之后,需要重新回到之前的调用方位置,继续执行之前剩下的操作:


int result = testDemo.addOne(p) + j;
复制代码


这个时候就需要提前使用程序计数器(PC)记录下后续需要返回的程序指令所在的内存地址。


为什么需要这一设计?


因为在操作系统中CPU是轮流切换不同的线程,所以当某个程序执行到一半,CPU去执行其他程序了,此时就需要有一个中间介质将之前执行的程序运行到的地址给记录下来,方便后续调用的时候直接提取使用。


为了最大化减少CPU来回切换过程中,对每个线程执行下一指令的影响,程序计数器被设计存放在了栈当中。


JVM的内存布局–栈



比较多的书籍里买呢,关于虚拟机栈总有不同的说法,这里我推荐以周志明老师的《深入理解Java虚拟机》一书中的说法为准。早期的虚拟机里面关于栈的说法主要有两大门派,分别是本地方法栈虚拟机栈


本地方法栈 当调用的是原生native方法的时候,需要寄存到本地方法栈当中


虚拟机栈 专门为调用jvm内部方法所提供的一个栈


但是在主流的Hotspot虚拟机中本地虚拟栈和虚拟机栈已经被融合成了一体,所以并没有过多的区别。


Java的栈是属于线程私有的一个模块,生命周期和线程一样。 每个栈里面都会存储一定的栈帧,栈帧包含了操作数栈,动态链接,局部变量表,方法出口。


操作数栈 执行指令的时候,需要将指令加入到操作数栈当中,而此时执行的每一条指令都需要压入到操作数栈里面。


动态链接 可以理解为将一些栈上边的信息和堆里面的数据做关联的一个操作。


局部变量表 每个函数的局部变量,参数列表。


方法出口 每个子方法执行完毕之后,都需要回到之前调用方的入口模块。


由于栈帧是属于线程私有的内存区域,所以有的时候如果在一个私有函数中包含了过多的临时变量,或者某些函数的递归层数过深都会导致栈帧空间不足,从而报出stackoverflowerror异常。


ps:注意hotspot的虚拟机是不允许栈空间不足继续扩容的,但是早期的classic虚拟机却允许。


JVM的内存布局–堆



在JVM中的堆区域,这是属于一个公共部分的区域,算是jvm里面的最大的一块内存区域了,几乎所有的对象都是存储在堆这个模块中,(也有特殊情况会将对象分配到栈上)堆设计的初衷其实就是为了给对象存储所使用的。


网上经常会有一些文章或者传言说,堆分为了年轻代,老年代,年轻代又分为eden区,survivor区域。其实这种说法是不严谨的描述,因为大部分程序员采用的jdk都是hotspot的相关产品,该类虚拟机在早期的时候主要是采用了分代回收的思路来进行内存管理,而如今虚拟机早已提供了更多优秀的垃圾收集技术,所以这种说法准确来讲应该换成:采用分代回收思路来进行收集的主流java虚拟机中,堆主要分为了年轻代,老年代…

关于对象存储的位置,其实只能说大部分存储在堆中,但是少数情况下,堆可以额外开辟一个空间用于给线程存储一些属于它们专有的buffer。这种技术叫做TLAB,属于栈上分配技术。


为什么要发明TLAB技术?


学习一门技术的过程中,弄清楚其发展的原因其实是非常重要的,不然很容易就变得知其然而不知其所以然,知道有这么一种技术,知道该怎么熟练运用,其背后的原理,但是却不了解为什么要这么设计。


首先我们来思考一下这么一个问题,当一个全新的对象需要分配内存的时候需要考虑哪些情况?


计算对象所需要的空间大小


寻找合适的内存区域


将对象分配到指定的内存区域


分配对象的过程主要包含了上述的这几个步骤,那么假设在多线程的环境下,情况就变得复杂了。分配对象的过程中还需要考虑加锁控制,内存空间中的一些指针碰撞问题等等问题,因此通过堆来分配内存其实还是一件非常繁琐的事情。在早期jdk1.5出来之后,java语言的市场日渐庞大,企业级的大型系统应用开始渐渐增多,于是在hotspot jvm 1.6 推出的时候,出现了tlab技术,专门用于优化这种堆频繁分配内存造成的性能损耗问题。


tlab全称为:ThreadLocalAllocBuffer 在下一篇文章中我会介绍到关于hotspot中使用tlab技术的一些细节点。


JVM的内存布局–方法区



在jvm里面,还有一个公共的内存区域被叫做为方法区,主要是用于存储一些常量池的数据信息和jvm初始化过程中加载的类文件信息。


网络异常,图片无法展示
|


很多时候我们都容易产生一个知识误区,误以为永久代就是方法区,但是这种说法是不完善的,需要有所调整,因为大部分的时候我们都是使用了hotspot虚拟机,而其他的例如说j9,jrockit虚拟机,它们并没有永久代这么一个说法。


在早期的时候,hotspot虚拟机采用了分代收集的思路来进行垃圾回收,才会将方法区这个部分归拢为了永久代(full gc的时候是会回收的),但是当后续演进过程中,hotspot团队发现使用永久代在垃圾回收的时候并不高效率,甚至在jdk8的时候将其进行了废弃。

方法区是和堆属于两个不同的存储区域,永久代是属于hotspot系列专有的一种说法。


JVM的内存布局–直接内存



直接内存的这个模块其实并不是java虚拟机规范所定义的内存区域,但是在使用的时候,因为这块内存是使用了操作系统中的内存空间,所以如果使用中超过了机器限制的内存大小,也会有oom发生。


网络异常,图片无法展示
|


在jdk1.4的时候出现了nio技术,这里面引入了channel和buffer的概念,比较经典的代表就是使用directbytebuffer对象来作为直接内存引用的相关操作,从而避免了java堆和native堆的来回复制数据问题。

目录
相关文章
|
24天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
30 0
|
21天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
18小时前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
23天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
272 1
|
29天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
1月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
22 3
|
1月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
49 1
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
86 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS