06 内存(上)划分与组织内存

Posted xuan01

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了06 内存(上)划分与组织内存相关的知识,希望对你有一定的参考价值。

分段分页的问题:

表示方式和状态确定角度:段的长度和大小不一,页大小固定,只需用位图就能表示页的分配与释放;

内存碎片的利用:段的内存碎片会难以利用,页碎片可以利用修改页表的方式,让连续的虚拟页面映射到非连续的物理页面;

内存和硬盘的数据交换效率:遇到内存不足时系统会将一部分数据写回硬盘,释放内存,段的交换时间不一,页没有这个问题 ;

段最大的问题就是是的虚拟内存地址空间难于实施;因此选择页管理内存;4KB,也正好对应长模式下MMU 4KB的分页方式;

如何表示一个页:

 真实的物理页内存布局信息来源于:e820map_t 结构数组,在初始化时已经转换为phymmarge_t结构数组了;

若用位图表示页的话:低效,且信息能表示的少;我们还需要页的状态,页的地址,分配模式,类型,链表等,所以考虑用结构体实现;

msadsc_t 结构体;包括 内存空间地址描述符标志 msadflgs_t结构体, 物理地址和标志 phyadrflgs_t结构体;

内存区:

将多个页面划分为几个内存区,如硬件区、内核区、应用区;方便管理;

  硬件区:0~32MB范围, 常见的网卡、AHCI、DMA方式,外部设备直接和内存交换数据,不通过CPU的MMU,且只能访问低于24MB的物理内存,所以就将这部分规定用作硬件区分配页;

  内核区:内核也使用内存,运行在虚拟地址空间,需要有一段物理内存和虚拟地址空间是 线性映射关系;内核使用内存需要更大、连续的物理内存空间,就在这个区分配;

  应用区:给应用用户态程序使用,系统并不会为应用一次性分配完所需的所有物理内存,而是按需分配,用到一页分配一页;;如果访问到一个没有与物理内存页建立映射关系的虚拟内存页,这时候CPU就会产生缺页异常,最终这个缺页异常由系统处理,系统会分配一个物理内存页,并建好映射关系;这是因为这种情况往往分配的是单个页面,所以为了给单个页面提供快捷的内存请求服务,就需要把离散的单页、或者是内核自身需要建好页表才可以访问的页面,统统收归用户区;;

表示内存区的结构:memarea_t 结构体,包括内存区开始地址和结束地址,里面有多少个物理页面,已经分配多少个物理页面;

接下来要把内存区数据结构 和 内存页面数据 结构关联起来;

组织内存页:

定义一个挂载msadsc_t 结构的数据结构 bafhlst_t 结构体,其中需要锁、状态、msadsc_t结构数量、挂载msadsc_t结构的链表、一些统计数据;

这个数据结构只是有了 挂载msadsc_t结构的地方, 形成了bafhlst_t 结构数组,并且把这个bafhlst_t 结构数组放在一个内存分割合并的数据结构中 memdivmer_t ;

  分割:表示分配内存, 合并:释放内存;

其中 dm_mdmlielst 结构代表 bafhlsh_t 结构数组,挂载连续的 msadsc_t 结构数量 等于 这个数组下标值左移之后的值,最大值是51,因为2^51 * 2 ^ 12 = 2 ^ 63,对于64位系统,只需要这样的两个数组就能完整表示所有内存;这样做的意义

  1、内存对齐,提升CPU寻址速度 2、内存分配时,根据需求大小快速定位至少从哪一部分开始 3、内存分配时,并发加锁,分组可以提升效率 4、内存分配回收时,很多计算也更简单

  我们并不在意其中第一个 msadsc_t结构数组对应的内存物理地址从哪里开始,但是第一个 msdasc_t 结构 与 最后一个msadsc_t 结构 ,它们之间的内存物理地址之间是连续的;

memarea_t 结构体 包含一个内存分割合并 memdivmer_t 结构,这个结构中又包含 dm_mdmlielst 数组; 数组中挂载了 msadsc_t  结构;

如下图:

 

JDK8中JVM堆内存划分

一:JVM中内存

JVM中内存通常划分为两个部分,分别为堆内存与栈内存,栈内存主要用运行线程方法

存放本地暂时变量与线程中方法运行时候须要的引用对象地址。

JVM全部的对象信息都

存放在堆内存中。相比栈内存,堆内存能够所大的多,所以JVM一直通过对堆内存划分

不同的功能区块实现对堆内存中对象管理。

堆内存不够最常见的错误就是OOM(OutOfMemoryError)

栈内存溢出最常见的错误就是StackOverflowError。程序有递归调用时候最easy发生

 

二:堆内存划分

在JDK7以及其前期的JDK版本号中。堆内存通常被分为三块区域Nursery内存(young 

generation)、长时内存(old generation)、永久内存(Permanent Generation for 

VM Matedata),显演示样例如以下图:

技术图片

当中最上一层是Nursery内存,一个对象被创建以后首先被放到Nursery中的Eden内

存中,假设存活期超两个Survivor之后就会被转移到长时内存中(Old Generation)中

永久内存中存放着对象的方法、变量等元数据信息。通过假设永久内存不够。我们

就会得到例如以下错误:

java.lang.OutOfMemoryError: PermGen

而在JDK8中情况发生了明显的变化,就是普通情况下你都不会得到这个错误,原因

在于JDK8中把存放元数据中的永久内存从堆内存中移到了本地内存(native memory)

中,JDK8中JVM堆内存结构就变成了例如以下:

技术图片

这样永久内存就不再占用堆内存。它能够通过自己主动增长来避免JDK7以及前期版本号中

常见的永久内存错误(java.lang.OutOfMemoryError: PermGen),或许这个就是你的

JDK升级到JDK8的理由之中的一个吧。

当然JDK8也提供了一个新的设置Matespace内存

大小的參数。通过这个參数能够设置Matespace内存大小,这样我们能够依据自己

项目的实际情况,避免过度浪费本地内存,达到有效利用。

 

-XX:MaxMetaspaceSize=128m 设置最大的元内存空间128兆

 

注意:假设不设置JVM将会依据一定的策略自己主动添加本地元内存空间。

假设你设置的元内存空间过小,你的应用程序可能得到下面错误:

java.lang.OutOfMemoryError: Metadata space

 

java1.8之前内存区域分为方法区、堆内存、虚拟机栈、本地方法栈、程序计数器。 下图所示:

技术图片

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。很多人都更愿意把方法区称为“永久代”(Permanent Generation)。从jdk1.7已经开始准备“去永久代”的规划,jdk1.7的HotSpot中,已经把原本放在方法区中的静态变量、字符串常量池等移到堆内存中。

在jdk1.8中,永久代已经不存在,存储的类信息、编译后的代码数据等已经移动到了元空间(MetaSpace)中,元空间并没有处于堆内存上,而是直接占用的本地内存(NativeMemory)。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小: 
  -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。 
  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。 
  除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性: 
  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集 
  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集 

 

注意:如果不设置JVM将会根据一定的策略自动增加本地元内存空间。

如果你设置的元内存空间过小,你的应用程序可能得到以下错误:

java.lang.OutOfMemoryError: Metadata space

 

在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间。 

 

常量池里存储着字面量和符号引用。

 符号引用包括:1.类的全限定名,2.字段名和属性,3.方法名和属性。

字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。)。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

 

  • 1.字符串池常量池在每个VM中只有一份,存放的是字符串常量的引用值,存放在堆中.
  • 2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
  • 3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

 

 

JVM内存参数设置

堆内存设置

  • 堆内存(总的)由 -Xms 和 -Xmx 分别设置最小和最大堆内存
  • New Generation 由 -Xmn 设置,-XX:SurvivorRatio=m 设置 Eden和 两个Survivor区的大小比值;-XX:NewRatio=n 设置 New Generation 和 Old Generation 的大小比值。
  • 每个线程的堆栈大小由 ·-Xss· 设置,JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

非堆内存设置

非堆内存由 -XX:PermSize=n 和 -XX:MaxPermSize=n 分别设置最小和最大非堆内存大小

 

原文链接:https://blog.csdn.net/xiaoliuliu2050/article/details/102701527

以上是关于06 内存(上)划分与组织内存的主要内容,如果未能解决你的问题,请参考以下文章

linux内核源码分析之物理内存组织结构

面试官常问系列:Java虚拟机内存四大问题,都在这了!

JDK8中JVM堆内存划分

JDK8中JVM堆内存划分

变量与存储空间及其存储结构

JVM谈谈JVM内存区域的划分