堆(heap)栈(stack)和方法区(method)

Posted hugeba

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆(heap)栈(stack)和方法区(method)相关的知识,希望对你有一定的参考价值。

技术图片

 

 技术图片

 

 

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引擎独立异步地存取。

以上是关于堆(heap)栈(stack)和方法区(method)的主要内容,如果未能解决你的问题,请参考以下文章

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

堆(heap)栈(stack)和方法区(method)

JVM内存—堆(heap)栈(stack)方法区(method) (转)

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

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

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