发布日期:2023-03-31来源:武汉北大青鸟武汉校区作者:武汉宏鹏
北大青鸟java培训技术是其核心技术,北大青鸟武汉宏鹏光谷校区下面为大家介绍关于java内存管理的基础知识,请关注。
平常工作中,发现有蛮多日常细节与内存管理有关,一直想要停下来总结总结,未果。这两天和一朋友沟通时,虚拟地址与物理地址的mapping方式这个问题,让平常一直考虑的关于、mmap、ringbuffer、DirectByteBuffer等细节点在脑海中翻腾,竟然一时语塞。所以今天在家写了点测试代码,让自己把思路理顺,整理出来,希望这些基础知识对大家有用。
1.硬件层面和物理内存
物理内存概要
大家都知道,物理内存就是RAM。处理器通过内存总线连接到物理内存,总线位数(比如32位或者48位)决定了可寻址的物理内存大小。这里提到48位这个值,是提醒不要与CPU的寄存器带宽混淆。X86_64的寄存器带宽是64,但是物理地址位数可能是48。(物理地址扩展后,物理地址位数也可能大于寄存器带宽)。
物理内存分页寻址,每页4K。对于32位的地址,第0页从0x00000000到0x00001000。可以看到,只需要前20位用来寻址物理页,而后12位用来标示页内地址。
思考一:这样内存分页使用有什么优点呢?在本文后讲到ringbuffer时会分析这个问题。
物理内存和磁盘的互惠交易
在linux协调下,物理内存和磁盘间有“惠国待遇条约”:
1) 物理内存充裕时:
linux会把一些物理内存用于io的buffer及cache,提升系统运行效率。
Linux下的sar -r命令结果中,总体可用的物理内存应该为:kbmemfree+kbbuffers+kbcached。
思考二:这里使用的物理内存,它所mapping到的虚存会归属什么进程呢?后面会有讨论。
2) 物理内存不够时:
linux会把物理内存的一部分数据放入磁盘swap区存储,以腾出内存给程序使用。
Vmstat命令结果中,swap下的si是每秒从磁盘读到内存的数据量,so是从内存写到磁盘的数据量。
创建Swap时(mkswap命令)可以用swap分区,也可以用普通文件。我实验了一下,用swap分区方式,在swapon /dev/hdc7后,used、free、buffer、cache内存都有增长;而用文件方式,这四个量都不变。
思考三:这里说的把文件映射到物理内存,与下文提到的MappedByteBuffer映射到虚拟内存场景是不一样的。
2.操作系统和虚拟内存
虚拟内存一对多映射
进程的虚拟地址空间中的区域可被映射到物理内存、文件或任何其他可寻址存储。这里的区域也使用分页机制,当一个程序尝试使用虚拟地址访问内存时,操作系统连同硬件会将该分页的虚拟地址映射到物理位置,这个位置可以是物理RAM、一个文件或页面文件(交换分区)。
下面是从IBM网站找来的概要图,pagefile是windows的说法,对应linux下的swap。
图一:虚拟内存映射概要
上图中的进程可以是用户进程,也可以是内核进程。每个进程都拥有自己的虚拟地址空间(逻辑地址区域)其大小由该系统上的地址大小规定。比如32位windows的单进程可寻址空间是4G,但是物理地址扩展后可能有64G。
思考四:我们开发中用的MappedByteBuffer其实说的通俗一点就是Map把文件的内容映像到计算机虚拟内存的一块区域,这样就可以直接操作内存当中的数据,而无需每次都通过I/O去物理硬盘读取文件,所以效率上有很大的提升。MappedByteBuffer主要使用场景有:需要用文件共享来实现进程间通信(使用同一个文件inode);需要写内存同时自动持久化到文件。
虚拟内存多对一映射
思考五:在图一中,虚拟内存地址可以指向同一个物理内存地址吗?一些嵌入式OS中,程序的确直接使用部的物理内存;但是windows和linux是具有虚拟内存的操作系统,虚拟内存允许多个进程共享物理内存。
尽管每个进程都有其自己的地址空间,但程序通常无法使用所有这些空间。地址空间被划分为内核空间和用户空间。大部分操作系统将每个进程地址空间的一部分映射到一个通用的内核内存区域。被映射来供内核使用的地址空间部分称为内核空间,其余部分称为用户空间,可供用户应用程序使用。
在思考二中,物理内存被用于buffer与cache,大都是内核空间的行为。默认情况下,32 位 Windows 拥有 2GB 用户空间和 2GB 内核空间;而linux分别是3G和1G。
内核是主要的操作系统程序,包含用于连接计算机硬件、调度程序以及提供联网和虚拟内存等服务的逻辑。作为计算机启动序列的一部分,操作系统内核运行并初始化硬件。
如果用户程序需要来自操作系统的服务,它可以执行一种称为系统调用的操作与内核程序交互。系统调用通常是读取和写入文件、联网和启动新进程等操作所必需的。Mmap系统调用实现时
3 Java进程使用的内存
在 Linux 和 Windows 上,进程是一个由受操作系统控制的资源(比如文件和套接字信息)、一个典型的虚拟地址空间(在某些架构上不止一个)和至少一个执行线程构成的集合。
Java是单进程应用,和普通进程没有本质区别。
有了上面的分析,可以很容易明白为什么我们用看到的java进程消耗的内存有时候会大于-Xmx 与-XX:MaxPermSize的和。java进程消费的内存包括JVM内存和java应用消费的JVM之外的物理内存,我们一般情况下不能用来判断多少是JVM消耗的、多少是JVM外的内存。
Java进程使用的内存一般有如下这些:
1) java堆和代。
2) 线程堆栈。-Xss可以调整,栈深度不够时抛出StackOverflowException,无内存可以分配于新线程创建时抛出OutOfMemoryError。
3) JIT编译、JNI代码、GC。
4) Socket缓冲区。每个Socket连接的Receive缓存区约37KB,Send缓存区约25KB,在连接数多的情况下也是很可观的。
5) DirectMemory。平常开发用的一些框架(比如CometD),会有大量的NIO操作使用到DirectByteBuffer,它通过native库直接分配堆外内存,这里使用的空间也在虚拟内存地址范围内,受进程可访问空间的限制,也可能导致OutofMemoryError。
写了一个简单的程序来测试DirectByteBuffer对和jstat的结果的影响:
1) DirectByteBuffer在堆中的引用清空后,gc,也可以释放堆外物理内存。
2) 程序启动时,-Xms的空间只是被分配地址空间,不计入使用的内存。
3) 使用过内存后,即使堆内存GC,还是会计入使用的内存。这个可能与新生代使用“复制算法”而不是“标记-整理算法”来实现GC有关系。
4 分页机制与ringbuffer
Ringbuffer也使用了mmap技术,但是这里我们要讨论的是思考一中的问题:它借鉴内存分页技术,可以用来做什么?
前面的4K分页技术,可以前20位用来寻址物理页,而后12位用来标示页内地址;其实可以再演化,比如用前10位表示第几个4M的文件,后22位表示是4M文件中的第几个。这样可以在Ringbuff中设置不同的slot大小,用来解决内存碎片、快取、文件转内存等问题。
了解更多java技术请继续关注武汉北大青鸟官网。
Copyright (c) 2006-2023 武汉宏鹏教育咨询有限公司 版权所有 All Rights Reserved.