关于堆和方法区浅析
JVM结构
新建对象语句一目了然,废话不多说,看图
一、堆(Heap)
【概述】
-
Java内存区域中一块用来存放对象实例的区域【
几乎所有的对象实例都在这里分配内存】 通过new关键字创建的对象都会被放在堆内存,jvm 运行时数据区中,占用内存最大的就是堆(Heap)内存!因此,它是垃圾收集器(GC)管理的主要目标 -
Java堆区在JVM启动的时候即被创建,其空间大小也就被确定了,几乎所有的对象实例以及数组都在运行时分配到堆上,所以它是JVM管理的最大的一块、也是最重要的一块内存空间。在方法结束后,堆中的对象不回马上被移除,仅仅在垃圾收集的时候才被移除,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
-
而对象实例和数组可能永远不会存储在栈上,因为栈帧中保存的是引用,这个引用指向对象实例或者数组在堆中的位置。
堆可以处于物理上不连续的内存空间,但在逻辑上它应该被视为连续的。
【堆空间大小的设置和查看】
默认情况下:
- 初始内存大小:物理电脑内存大小 1/ 64
- 最大内存大小:物理电脑内存大小 1/ 4 Edit Configration>add VM option>输入
- -Xms:可设置堆的初始空间,等价于-XX:InitialHeapSize
- -Xmx:可设置堆的最大空间,等价于-XX:MaxHeapSize
- -XX:+PrintGCDetail:可查看堆空间中设置的参数
- -Xmn:可设置年轻代的空间大小
- 在HotSpot中,Eden(伊甸园)空间和另外两个Survivor空间缺省所占的比例是8:1:1,当然我们可以通过选项"-XX:SurvivorRatio"调整这个比例空间。 实际情况中,当我们查看内存分配是时发现并不是8:1:1,是因为系统为我们默认定义了自适应内存分配策略,我们可以通过"-XX:-UseAdaptiveSizePolicy"关闭自适应的内存分配策略。
二、方法区
- 《Java虚拟机规范》中说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩”。但对于HotSpot JVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。所以,方法区可以看作是独立于Java堆的内存空间。
- 另外,方法区包含了一个特殊的区域“运行时常量池”。
- 其中主要存储加载的类字节码、class/method/field等元数据对象、static-final常量、static变量、jit编译器编译后的代码等数据。
-
类型信息:( 类class、接口interface、枚举enum、注解annotation)JVM必须在方法区中存储以下类型信息:
- 这个类型的完整有效名称(全名=包名.类名)
- 这个类型直接父类的完整有效名(对于interface或是java. lang.Object,都没有父类)
- 这个类型的修饰符(public, abstract, final的某个子集)
- 这个类型直接接口的一个有序列表
-
域信息(成员变量):
- JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
- 域的相关信息包括:域名称、 域类型、域修饰符(public, private, protected, static, final, volatile, transient的某个子集)。
-
方法信息:JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序
-
方法名称
-
方法的返回类型(或void)
-
方法参数的数量和类型(按顺序)
-
方法的修饰符(public, private, protected, static, final,synchronized, native , abstract的一个子集)
-
方法的字节码(bytecodes)、操作数栈、局部变量表及大小( abstract和native 方法除外)异常表( abstract和native方法除外)
-
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
-
方法区的演进:
面试常问
- Jdk 1.6 及之前:有永久代(静态变量存放在永久代上)、字符串常量池(1.6在方法区)
- Jdk 1.7 :有永久代,但已经逐步 " 去永久代 ",字符串常量池、静态变量移除,保存在堆中
- dk 1.8 及之后: 无永久代,常量池1.8在元空间。但静态变量、字符串常量池仍在堆中
参考