jvm-内存管理机制(一)

jvm-内存管理机制(一)

JVM内存区域主要包括以下部分:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。
JVM内存管理也能够理解为–jvm运行时数据区,jvm内存主要管理的就是这片内存区域。
html

堆(各线程共享区域)

java的堆是一个运行时的数据区,用来存储数据的单元,存放经过new关键字新建的对象和数组,对象从中分配内存。
在堆中声明的对象,是不能直接访问的,必须经过在栈中声明的指向该引用的变量来调用。引用变量就至关因而为数组或对象起的一个名称,之后就能够在程序中使用栈中的引用变量来访问堆中的数组或对象。 java

声明的对象是在堆内存中初始化的, 真正用来存储数据的,不能直接访问。
引用类型变量是保存在栈当中的,一个用来引用堆中对象的符号而已(指针)。linux

jvm堆的特色:程序员

  1. java虚拟机所管理的内存中最大的一块。java堆时被全部线程共享的一块内存区域,在虚拟机启动时建立。
  2. 此内存区域的惟一目的就是存放对象实例,几乎全部的对象实例都在这里分配内存。(几乎全部的对象实例以及数组都要在堆上分配。
  3. java堆实GC管理的就是该区域,也称为GC堆。
  4. java堆中还细分为:新生代,老年代;再细分一点有Eden空间,From Survivor(sərˈvaɪvə(r),幸存者)空间,To Survivor空间

方法区(各线程共享区域)

各线程共享区域,存储已被虚拟机加载的类信息常量静态变量,即时编译器编译后的代码等数据。
类信息有:每一个类的全限定名、每一个类的直接超类的全限定名、该类是类仍是接口、该类型的访问修饰符、直接超接口的全限定名的有序列表。
每一个已装载类的详细信息:运行时常量池、字段信息、方法信息、静态变量、到类classloader的引用、到类class的引用。 web

已装载类详细信息

  1. 运行时常量池:在方法区中,每一个类型都对应一个常量池,存放该类型所用到的全部常量,常量池中存储了诸如文字字符串、final变量值、类名和方法名常量。
  2. 字段信息:字段信息存放类中声明的每个字段的信息,包括字段的名、类型、修饰符。
  3. 字段名称:指的是类或接口的实例变量或类变量,字段的描述符是一个指示字段的类型的字符串,如private A a=null;则a为字段名,A为描述符,private为修饰符
  4. 方法信息:类中声明的每个方法的信息,包括方法名、返回值类型、参数类型、修饰符、异常、方法的字节码。
    (在编译的时候,就已经将方法的局部变量、操做数栈大小等肯定并存放在字节码中,在装载的时候,随着类一块儿装入方法区。)
  5. 静态变量:就是类变量,类的全部实例都共享,在方法区有个静态区,静态区专门存放静态变量和静态块。
  6. 到类classloader的引用:到该类的类装载器的引用。
  7. 到类class 的引用:虚拟机为每个被装载的类型建立一个class 实例,用来表明这个被装载的类。

java方法区中-运行时常量池

java 常量池实际上分为两种形态:静态常量池 和运行时常量池 。算法

  • 所谓静态常量池 ,即*.class文件中的常量池。class文件中的常量池不只仅包含字符串(数字)字面量,还包含类、方法的信息,它们占用class文件绝大部分空间。
  • 而运行时常量池 ,则是jvm虚拟机在完成类装载操做后,将class文件中的常量池载入到内存中,并保存在方法区 中,咱们常说的常量池,就是指方法区中的运行时常量池。

运行时常量池(Constant Pool Table),用于存放编译期生成的各类字面量符号引用String字符串final变量值、类和结构的彻底限定名,方法的名称和描述符,字段的名称和描述符,这部份内容将在类加载后存放到方法区的运行时常量池中。它们以数组形式经过索引被访问,是外部调用与类联系及类型对象化的桥梁.
数组

在运行时,JVM从常量池中得到符号引用,而后在运行时解析成引用项的实际地址,最后经过常量池中的全限定名、方法和字段描述符,把当前类或接口中的代码与其它类或接口中的代码联系起来。
运行时常量池中的常量,基原本源于各个class文件中的常量池。
程序运行时,除非手动向常量池中添加常量(好比调用intern方法),不然jvm不会自动添加常量到常量池安全

运行时常量池除了存放编译期产生的Class文件的常量外,还可存放在程序运行期间生成的新常量,比较常见增长新常量方法有String类的internd()方法。String.intern()是一个Native方法,它的做用是:若是运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;若是没有,则在常量池中建立与此String内容相同的字符串,并返回常量池中建立的字符串的引用。不过JDK7的intern()方法的实现有所不一样,当常量池中没有该字符串时,再也不是在常量池中建立与此String内容相同的字符串,而改成在常量池中记录堆中首次出现的该字符串的引用,并返回该引用。 数据结构

因为运行时常量池在方法区中,咱们能够经过jvm参数:-XX:PermSize、-XX:MaxPermSize来设置方法区大小,从而间接限制常量池大小。
在jdk8中,移除了方法区,转而用Metaspace区域替代,因此咱们须要使用新的jvm参数:-XX:MaxMetaspaceSize多线程

可是,JDK1.7以前运行时常量池是方法区的一部分,JDK1.7及以后版本已经将运行时常量池从方法区中移了出来,在堆(Heap)中开辟了一块区域存放运行时常量池。

PermGen(永久代)

绝大部分 Java 程序员应该都见过 “java.lang.OutOfMemoryError: PermGen space “这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,然后者则是 JVM 规范的一种实现,而且只有 HotSpot 才有 “PermGen space”,而对于其余类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并无“PermGen space”。因为方法区主要存储类的相关信息,因此对于动态生成类的状况比较容易出现永久代的内存溢出。

Metaspace(元空间)

其实,移除永久代的工做从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没彻底移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
元空间的好处总结:
- 字符串存在永久代中,容易出现性能问题和内存溢出。
- 类及方法的信息等比较难肯定其大小,所以对于永久代的大小指定比较困难,过小容易出现永久代溢出,太大则容易致使老年代溢出。
- 永久代会为 GC 带来没必要要的复杂度,而且回收效率偏低。

java栈 stack(私有)

每一个方法在执行的同时都会建立一个栈帧用于存储局部变量操做数栈动态连接方法出口等信息,每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

  • 局部变量区为一个以字为单位的数组,每一个数组元素对应一个局部变量的值。调用方法时,将方法的局部变量组成一个数组,经过索引来访问。若为非静态方法,则加入一个隐含的引用参数this,该参数指向调用这个方法的对象。而静态方法则没有this参数。所以,对象没法调用静态方法。
  • 操做数栈也是一个数组,可是经过栈操做来访问。所谓操做数是那些被指令操做的数据。当须要对参数操做时如a=b+c,就将即将被操做的参数压栈,如将b 和c 压栈,而后由操做指令将它们弹出,并执行操做。虚拟机将操做数栈做为工做区。
  • 帧数据区处理常量池解析,异常处理等

若是线程请求的栈深度大于虚拟机所容许的深度,将会抛出stackoverflowError;若是虚拟机能够动态扩展,可是没法申请到足够的内存时,就会抛出outOfMemoryError异常。

linux查看线程默认栈空间大小

一、经过命令 ulimit -s 查看linux的默认栈空间大小,默认状况下 为10240 即10M
二、经过命令 ulimit -s 设置大小值 临时改变栈空间大小:ulimit -s 102400, 即修改成100M
三、能够在/etc/rc.local 内 加入 ulimit -s 102400 则能够开机就设置栈空间大小
四、在/etc/security/limits.conf 中也能够改变栈空间大小
soft stack 102400

本地方法栈(私有)

虚拟机栈为虚拟机执行java方法提供服务,而本地方法栈为虚拟机使用到的本地方法服务。

程序计数器(私有)

  1. 是一块较小的内存空间,它的做用能够看作是当前线程所执行的字节码的行号指示器。每条线程都须要一个独立的程序计数器,各线程之间互不影响
  2. 字节码解释器选取下一条指令,分支,循环,跳转,异常处理,线程恢复都须要依赖它

堆和栈的题外话

这里描述的是C,C++里面的堆和栈。

什么是堆,什么栈,为何要有堆和栈?

从数据结构层次理解,栈是一种先进后出的线性表,只要符合先进后出的原则的线性表都是栈,至于采用的存储方式(实现方式)是顺序存储(顺序栈)仍是链式存储(链式栈)是没有关系的。堆则是二叉树的一种,有最大堆最小堆,排序算法中有经常使用的堆排序。

从系统层次理解,栈是系统为运行的程序分配的先进后出的存储区域。在执行函数时,函数内部局部变量的存储单元能够在栈上建立(针对CISC架构而 言,RISC架构下,局部变量的存储单元是在寄存器上建立),函数执行结束时这些存储单元自动被释放。堆是系统管理的能够被程序利用的全局存储空间,动态 内存分配就是从堆上分配。

如今计算机(串行执行机制),都直接在代码层次支持栈这种数据结构。这体如今,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操做。好比ARM指令中的stmfd和ldmfd。由于栈内存分配运算内置于处理器的指令集中,因此效率很高,可是支持的数据有限,通常是整数、指 针、浮点数等系统直接支持的数据类型,并不直接支持其余的数据结构。
和栈不一样,堆的数据结构并非由系统(不管是机器系统仍是操做系统)支持的,而是由函数库提供的。

栈和堆对比

  • 栈是系统提供的功能,特色是快速高效,缺点是有限制,数据不灵活;而堆是函数库提供的功能,特色是灵活方便,数据适应面普遍,可是效率有必定下降。
  • 栈是系统数据结构,对于进程/线程是惟一的;堆是函数库内部数据结构,不必定惟一,不一样堆分配的内存没法互相操做。
  • 栈空间分静态分配和动态分配两种。静态分配是编译器完成的,好比自动变量(auto)的分配。动态分配由alloc函数完成。栈的动态分配无需释放 (是自动的),也就没有释放函数。为可移植的程序起见,栈的动态分配操做是不被鼓励的!堆空间的分配老是动态的,虽然程序结束时全部的数据空间都会被释放 回系统,可是精确的申请内存/释放内存匹配是良好程序的基本要素。
  • stack:由系统自动分配,速度较快。但程序员是没法控制的。heap:由new分配的内存,通常速度比较慢,并且容易产生内存碎片,不过用起来最方便。

看了堆和栈的介绍,堆和栈都是内存,就比如一张桌子里面有两个抽屉,一个抽屉里面存放一种东西,另一个抽屉存放另外一种东西,这样方便存取方便,找东西也比较快捷方便。

栈和堆存储


  1. 图中描述的是操做系统分配的虚拟地址空间,不是实际的内存空间哦;堆和栈在地址空间上面是必定在一块儿的,可是物理内存则不必定。(虚拟地址空间只是存了一个地址,这个地址指向了一块实际的内存空间)。
  2. 堆的地址空间是从低到高存储,栈的地址空间是从高到底存储。(就是一个小区,有100栋楼,堆从1-100开始分房子;而栈是从100到1分房子)
  3. 局部变量,操做数都是放在栈里面的,使用时入栈占用栈内存空间,时候完毕后出栈释放栈内存空间;堆则不同,new关键字有程序本身分配内存空间,使用完毕后本身回收空间垃圾值。

注意:每一个线程都有本身的栈空间哦;因此多线程的时候栈是不用担忧的,栈是线程私有的,不存在线程安全问题。
出栈后自动释放空间,因此这也就是后面要将的,垃圾收集历来没有说过要针对栈的,都是针对堆操做的。(java里面方法区和堆在这里都是堆空间)

顺带讲一下高低位

上面说的堆和栈的高低位是地址空间的高低位,就是楼层编号同样。这里讲的高低位则是字节序的高低位,是不同的哦。
在汇编指令中,刚开始的cpu是16位的,分为高8位,低8位,分别用AH,AL指令操做高,低位。AH直接指向高位地址,Al直接指向低8位地址。
后来CPU支持32位了,为了向下兼容,32位的高位用EAX表示,那么64位的CPU呢?64位的高位尚未相应的指令,因此64位的二进制字节序取值须要使用位运算,向右移位操做。高低位的区分只是为了存取方便和读取效率高。

参考文献

http://blog.csdn.net/u011080472/article/details/51321769
《深刻理解java虚拟机》
堆和栈:http://www.cnblogs.com/SinSay/archive/2008/11/12/1332076.html
http://zy77612.iteye.com/blog/1152225