JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)

堆区:堆内存用于存放由new创建的对象和数组。堆是JVM管理的内存中最大的一块,堆被所有线程共享,目的是为了存放对象实例,几乎所有的对象实例都在这里分配。

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

栈区:Java栈是一块线程私有的空间,一个栈,一般由三部分组成:局部变量表、操作数据栈和帧数据区

1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

栈分为栈、本地方法栈、程序计数器栈(三个部分都是线程独占)

栈(方法栈):是线程私有的,线程在执行每个方法时都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时执行入栈,方法返回时执行出栈

本地方法栈:与栈类似,也是用来保存线程执行方法时的信息,不同的是,执行 Java 方法使用栈,而执行 native 方法使用本地方法栈。

程序计数器:保存着当前线程所执行的字节码位置。简单来讲是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令。每个线程工作时都有一个独立的计数器。程序计数器为执行 Java 方法服务,执行 native 方法时,程序计数器为空。 是一个非常小的内存空间,几乎可以忽略不记。

方法区:Java方法区和堆一样,方法区是一块所有线程共享的内存区域,保存系统的类信息。各个线程共享的内存区域,又叫非堆区。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

3.方法区的大小决定系统可以保存多少个类。如果系统定义太多的类,导致方法区溢出。虚拟机同样会抛出内存溢出的错误。方法区可以理解为永久区。


栈区:用来存储局部变量表、操作栈、动态链接、方法出口等信息。 遵循“先进后出”/“后进先出”原则。

  局部变量表:用于报错函数的参数及局部变量(基本类型变量区)

  操作数栈   :主要保存计算过程的中间结果,同时作为计算过程中的变量临时的存储空间。(操作指令区)

  帧数据区    :  除了局部变量表和操作数据栈以外,栈还需要一些数据来支持常量池的解析,这里帧数据区保存着访问常量池的指针,方便计程序访问常量池,另外当函数返回或出现异常时卖虚拟机子必须有一个异常处理表,方便发送异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。(执行环境上下文)

Heap堆区:

  根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为新生代和老年代。其中新声带存放新生的对象或者年龄不大的对象,老年代则存放老年对象。

    新生代分为Eden space、survivor 0 space(s0)、survivor 1 space(s1)(s0和s1也被称为from和to区域,他们是两块大小相等并且可以互相角色的空间。)

    绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄后,则进入老年代(养老区、永久存储区)。

    养老区:养老区用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。

    永久存储区:是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。原因有二:
     a. 程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。
     b. 大量动态反射生成的类不断被加载,最终导致Perm区被占满。
说明:
     Jdk1.6及之前:常量池分配在永久代 。
     Jdk1.7:有,但已经逐步“去永久代” 。
     Jdk1.8及之后:无(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)。

1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.
2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
3. 堆:存放所有new出来的对象。
4. 静态域:存放静态成员(static定义的)
5. 常量池:存放字符串常量和基本类型常量(public static final)。
6. 非RAM存储:硬盘等永久存储空间

这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。
  对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。


static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。

static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用。(方法区)


Java中native堆在用户空间,堆外内存 1次拷贝,堆内存 2次拷贝

直接内存在用户空间,进入内核空间需要操作系统辅助API切入,以及数据从用户空间传入内核空间。
普通应用都是用户空间,只有驱动程序等直接访问内核空间

只有内核空间的内存才能被DMA引擎独立异步地存取。

JVM学习——元空间(Metaspace)

 

一、从方法区(PermGen)到元空间(Metaspace)

  • 方法区(PermGen)
  1. JDK1.8以前的HotSpot JVM有方法区,也叫永久代(permanent generation)。
  2. 方法区用于存放已被虚拟机加载的类信息、常量、静态变量,即编译器编译后的代码。
  3. 方法区是一片连续的堆空间,通过-XX:MaxPermSize来设定永久代最大可分配空间,当JVM加载的类信息容量超过了这个值,会报OOM:PermGen错误。
  4. 永久代的GC是和老年代(old generation)捆绑在一起的,无论谁满了,都会触发永久代和老年代的垃圾收集。
  5. JDK1.7开始了方法区的部分移除:符号引用(Symbols)移至native heap,字面量(interned strings)和静态变量(class statics)移至java heap。
  • 为什么要用Metaspace替代方法区
    随着动态类加载的情况越来越多,这块内存变得不太可控,如果设置小了,系统运行过程中就容易出现内存溢出,设置大了又浪费内存。

二、Metaspace的组成

Metaspace由两大部分组成:Klass Metaspace和NoKlass Metaspace。

  • Klass Metaspace
  1. Klass Metaspace就是用来存klass的,就是class文件在jvm里的运行时数据结构(不过我们看到的类似A.class其实是存在heap里的,是java.lang.Class的对象实例)。
  2. 这部分默认放在Compressed Class Pointer Space中,是一块连续的内存区域,
    紧接着Heap,和之前的perm一样。通过-XX:CompressedClassSpaceSize来控制这块内存的大小,默认是1G。
    下图展示了对象的存储模型,_mark是对象的Mark Word,_klass是元数据指针
     
    has ccs.jpg
  3. Compressed Class Pointer Space不是必须有的,如果设置了-XX:-UseCompressedClassPointers,或者-Xmx设置大于32G,就不会有这块内存,这种情况下klass都会存在NoKlass Metaspace里。
     
    no ccs.jpg
  • NoKlass Metaspace
  1. NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool等,可以由多块不连续的内存组成。
  2. 这块内存是必须的,虽然叫做NoKlass Metaspace,但是也其实可以存klass的内容,上面已经提到了对应场景。
  3. NoKlass Metaspace在本地内存中分配。
  • 指针压缩
  1. 64位平台上默认打开
  2. 设置-XX:+UseCompressedOops压缩对象指针, oops指的是普通对象指针(ordinary object pointers), 会被压缩成32位。
  3. 设置-XX:+UseCompressedClassPointers压缩类指针,会被压缩成32位。

三、Metaspace内存管理

  1. 在metaspace中,类和其元数据的生命周期与其对应的类加载器相同,只要类的类加载器是存活的,在Metaspace中的类元数据也是存活的,不能被回收。
  2. 每个加载器有单独的存储空间。
  3. 省掉了GC扫描及压缩的时间。
  4. 当GC发现某个类加载器不再存活了,会把对应的空间整个回收。

Metaspace VM使用一个块分配器(chunking allocator)来管理Metaspace空间的内存分配。块的大小依赖于类加载器的类型。
Metaspace VM中有一个全局的可使用的块列表(a global free list of chunks)。当类加载器需要一个块的时候,类加载器从全局块列表中取出一个块,添加到它自己维护的块列表中。当类加载器死亡,它的块将会被释放,归还给全局的块列表。
块(chunk)会进一步被划分成blocks,每个block存储一个元数据单元(a unit of metadata)。Chunk中Blocks的是分配线性的(pointer bump)。这些chunks被分配在内存映射空间(memory mapped(mmapped) spaces)之外。在一个全局的虚拟内存映射空间(global virtual mmapped spaces)的链表,当任何虚拟空间变为空时,就将该虚拟空间归还回操作系统。

 
metachunks.jpg

上面这幅图展示了Metaspace使用metachunks在mmapeded virual spaces分配的情形。

四、metaspace的主要参数

MetaspaceSize
MaxMetaspaceSize
CompressedClassSpaceSize
MinMetaspaceExpansion
MaxMetaspaceExpansion
MinMetaspaceFreeRatio
MaxMetaspaceFreeRatio
UseLargePagesInMetaspace
InitialBootClassLoaderMetaspaceSize

MetaspaceSize

metaspaceGC发生的初始阈值,也是最小阈值,默认20.8M左右,与之对比的主要是指Klass Metaspace与NoKlass Metaspace两块committed的内存和。

  • 触发metaspaceGC的阈值是不断变化的:当metaspace使用的内存接近阈值时,会尝试增大阈值。metaspaceGC后也会调整阈值。

MaxMetaspaceSize

由于metaspace大部分在本地内存中分配,默认基本是无穷大,但仍然受本地内存大小的限制。为了防止metaspace被无止境使用,建议设置这个参数。

  • 这个参数会限制metaspace(包括了Klass Metaspace以及NoKlass Metaspace)被committed的内存大小,会保证committed的内存不会超过这个值,一旦超过就会触发GC。
  • 和MaxPermSize的区别,根据MaxMetaspaceSize,并不会在jvm启动的时候分配一块这么大的内存出来,而根据MaxPermSize分配的内存则是固定大小。

CompressedClassSpaceSize

Compressed Class Pointer Space区域的大小,默认1G。
如果设置了-XX:-UseCompressedClassPointers,或者-Xmx设置大于32G,则这个参数不生效。

MinMetaspaceExpansion

MinMetaspaceExpansion和MaxMetaspaceExpansion这两个参数这两个参数和扩容其实并没有直接的关系,并不是为了增大committed的内存。
主要是在比较特殊的场景下救急使用,增大触发metaspace GC的阈值,延迟GC的发生。
默认332.8K,增大触发metaspace GC阈值的最小要求。
如果需要分配的内存小于MinMetaspaceExpansion,则将metaspace GC的阈值提升MinMetaspaceExpansion。

MaxMetaspaceExpansion

默认5.2M,增大触发metaspace GC阈值的最大要求。
如果需要分配的内存大于MinMetaspaceExpansion但是小于MaxMetaspaceExpansion,那增量就是MaxMetaspaceExpansion。
如果需要分配的内存超过了MaxMetaspaceExpansion,那增量就是MinMetaspaceExpansion加上要分配的内存大小

注:每次分配只会给对应的线程一次扩展触发metaspace GC阈值的机会,如果扩展了,但是还不能分配,那就只能等着做GC了。

MinMetaspaceFreeRatio

MinMetaspaceFreeRatio和下面的MaxMetaspaceFreeRatio,主要是影响触发metaspaceGC的阈值。

默认40,表示每次GC完之后,如果metaspace内存的空闲比例小于MinMetaspaceFreeRatio%,那么将尝试做扩容,增大触发metaspaceGC的阈值。
不过这个增量至少是MinMetaspaceExpansion才会做,不然不会增加这个阈值。
这个参数主要是为了避免触发metaspaceGC的阈值和gc之后committed的内存的量比较接近,于是将这个阈值进行扩大。

注:这里不用gc之后used的量来算,主要是担心可能出现committed的量超过了触发metaspaceGC的阈值,这种情况一旦发生会很危险,会不断做gc,这应该是jdk8在某个版本之后才修复的bug

MaxMetaspaceFreeRatio

默认70,这个参数和上面的参数基本是相反的,是为了避免触发metaspaceGC的阈值过大,而想对这个值进行缩小。
这个参数在gc之后committed的内存比较小的时候并且离触发metaspaceGC的阈值比较远的时候,调整会比较明显。

UseLargePagesInMetaspace

默认false,这个参数是说是否在metaspace里使用LargePage,一般情况下我们使用4KB的page size,这个参数依赖于UseLargePages这个参数开启,不过这个参数我们一般不开。

InitialBootClassLoaderMetaspaceSize

64位下默认4M,32位下默认2200K,metasapce前面已经提到主要分了两大块,Klass Metaspace以及NoKlass Metaspace,而NoKlass Metaspace是由一块块内存组合起来的,这个参数决定了NoKlass Metaspace的第一个内存Block的大小,即2*InitialBootClassLoaderMetaspaceSize,同时为bootstrapClassLoader的第一块内存chunk分配了InitialBootClassLoaderMetaspaceSize的大小

五、jstat里的metaspace字段

我们看GC是否异常,除了通过GC日志来做分析之外,我们还可以通过jstat这样的工具展示的数据来分析,前面我公众号里有篇文章介绍了jstat这块的实现,有兴趣的可以到我的公众号你假笨里去翻阅下jstat的这篇文章。

我们通过jstat可以看到metaspace相关的这么一些指标,分别是M,CCS,MC,MU,CCSC,CCSU,MCMN,MCMX,CCSMN,CCSMX

MC & MU & CCSC & CCSU

  • MC表示Klass Metaspace以及NoKlass Metaspace两者总共committed的内存大小,单位是KB,虽然从上面的定义里我们看到了是capacity,但是实质上计算的时候并不是capacity,而是committed,这个是要注意的
  • MU这个无可厚非,说的就是Klass Metaspace以及NoKlass Metaspace两者已经使用了的内存大小
  • CCSC表示的是Klass Metaspace的已经被commit的内存大小,单位也是KB
  • CCSU表示Klass Metaspace的已经被使用的内存大小

M & CCS

  • M表示的是Klass Metaspace以及NoKlass Metaspace两者总共的使用率,其实可以根据上面的四个指标算出来,即(CCSU+MU)/(CCSC+MC)
  • CCS表示的是NoKlass Metaspace的使用率,也就是CCSU/CCSC算出来的

PS:所以我们有时候看到M的值达到了90%以上,其实这个并不一定说明metaspace用了很多了,因为内存是慢慢commit的,所以我们的分母是慢慢变大的,不过当我们committed到一定量的时候就不会再增长了

MCMN & MCMX & CCSMN & CCSMX

    • MCMN和CCSMN这两个值可以忽略,一直都是0
    • MCMX表示Klass Metaspace以及NoKlass Metaspace两者总共的reserved的内存大小,比如默认情况下Klass Metaspace是通过CompressedClassSpaceSize这个参数来reserved 1G的内存,NoKlass Metaspace默认reserved的内存大小是2* InitialBootClassLoaderMetaspaceSize
    • CCSMX表示Klass Metaspace reserved的内存大小

JVM内存模型——堆(heap)、栈(stack)和方法区(method)的更多相关文章

  1. (转)Java里的堆(heap)栈(stack)和方法区(method)(精华帖,多读读)

    [color=red][/color]<一> 基础数据类型直接在栈空间分配, 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收.   引用数据类型,需要用new来创建,既在栈 ...

  2. Java里的堆(heap)栈(stack)和方法区(method)

    基础数据类型直接在栈空间分配, 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收.   引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量 . 方法 ...

  3. JVM 内存 (堆(heap)、栈(stack)和方法区(method) )

    JVM 内存初学 (堆(heap).栈(stack)和方法区(method) ) 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令)2.jv ...

  4. [转]JVM 内存初学 (堆(heap)、栈(stack)和方法区(method) )

    这两天看了一下深入浅出JVM这本书,推荐给高级的java程序员去看,对你了解JAVA的底层和运行机制有比较大的帮助.废话不想讲了.入主题: 先了解具体的概念:JAVA的JVM的内存可分为3个区:堆(h ...

  5. JVM 内存初学 堆(heap)、栈(stack)和方法区(method)

    这两天看了一下深入浅出JVM这本书,推荐给高级的java程序员去看,对你了解JAVA的底层和运行机制有比较大的帮助.废话不想讲了.入主题:先了解具体的概念:JAVA的JVM的内存可分为3个区:堆(he ...

  6. 转:JVM 内存初学 (堆(heap)、栈(stack)和方法区(method) )

    原文地址:JVM 内存初学 (堆(heap).栈(stack)和方法区(method) ) 博主推荐 深入浅出JVM 这本书 先了解具体的概念:JAVA的JVM的内存可分为3个区:堆(heap).栈( ...

  7. JVM 内存初学 (堆(heap)、栈(stack)和方法区(method) )(转载)

    想想面试的时候很多会问jvm这方面的问题虽然还是菜鸟不太能用到现在但是还是了解一下, 找资料的时候看见个大佬写的很好转载到这方便以后自己复习和给大佬做宣传 以下为大佬的博客原文: 这两天看了一下深入浅 ...

  8. java虚拟机知识和 内存 堆(heap)、栈(stack)和方法区(method)

    1.虚拟机实例 每个java程序都运行在自己的java虚拟机实例中,运行三个java程序就会得到三个虚拟机实例 守护线程(虚拟机自己使用,比如说执行垃圾收集任务的线程) 非守护线程(java初试线程, ...

  9. java面试之----堆(heap)、栈(stack)和方法区(method)

    JAVA的JVM的内存可分为3个区:堆(heap).栈(stack)和方法区(method)也叫静态存储区. 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的 ...

随机推荐

  1. mac下pmset的使用方法

    在终端输入:pmset -g custom    这个命令可以显示系统所有的电源设置 lidwake: 当屏幕掀开的时候唤醒Mac,1是开启  0是关闭autopoweroff: 如果Mac处于睡眠状 ...

  2. ANGULAR JS WATCH监听使用

    ANGULAR 监听使用: 当angular数据模型发生变化时,我们需要如果需要根据他的变化触发其他的事件. $watch是一个scope函数,用于监听模型变化,当你的模型部分发生变化时它会通知你. ...

  3. Qt4.8 移植(超详细Configure的参数)

    Qt4.8.6 configure 参数 不只是适用于Qt4.8.6,原则上适用于Qt4所有版本 Usage: configure [-h] [-prefix <dir>] [-prefi ...

  4. Postman 是一个非常棒的Chrome扩展,提供功能强大的API & HTTP 请求调试

    Postman 是一个非常棒的Chrome扩展,提供功能强大的API & HTTP 请求调试   需要FQ才能安装,使用时应该不用FQ了,除非使用postman的历史记录功能:   非常棒的C ...

  5. 封装bt轮播图淡入淡出效果样式

    <!--BT轮播图-->    <div data-ride="carousel" class="carousel slide carousel_inn ...

  6. Golang struct结构

    结构struct Go中的struct与C中的struct非常相似,并且Go没有class,代替了class的位置,但并没有代替class的功能 使用type struct{} 定义结构,名称遵循可见 ...

  7. Centos7安装搭建NTP服务器和NTP客户端同步时间

    NTP简介: NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议. 在计算机的世界里,时间非常地重要 例如:对于火箭发射这种科研活动,对时间的 ...

  8. windows service 的错误 错误 14001:

    1.Windows服务启动时报:“错误 14001:由于应用程序配置不正确,应用程序未能启动.重新安装应用程序可能会纠正这个问题.”的错误. 原因:Windows 服务程序 配置文件中   <a ...

  9. 峰Redis学习(1)Redis简介和安装

    是从博客:http://blog.java1234.com/blog/articles/310.html参考过来的: 第一节:Redis 简介 为什么需要NoSQL,主要应对以下问题,传统关系型数据库 ...

  10. 项目复审——Alpha阶段

    Deadline: 2018-5-19 10:00PM,以提交至班级博客时间为准. 5.10实验课上,以(1.2班级,3.4班级为单位)进行项目复审.根据以下要求,完成本团队对其他团队的复审排序. 参 ...