JVM虚拟机将内存数据分为:
程序计数器、虚拟机栈、本地方法栈、Java堆、方法区等部分。
程序计数器用于存放下一条运行的指令;
虚拟机栈和本地方法栈用于存放函数调用堆栈信息;
Java堆用于存放Java程序运行时所需的对象等数据;
方法区用于存放程序的类元数据信息。
1、程序计数器
每一个线程都必须有一个独立的程序计数器,用于记录下一条要运行的指令。各个线程之间的计数器互不影响,独立工作;是一块线程私有的内存空间。
如果当前程序正在执行一个Java方法,则程序计数器记录正在执行的Java字节码地址,如果当前线程正在执行一个Native方法,则程序计数器为空。
2、Java虚拟机栈
Java虚拟机栈也是线程的私有空间,它和Java线程在同一时间创建,它保存方法的局部变量、部分结果,并参与方法的调用和返回。
在Java虚拟机规范中,定义了两种异常与栈空间有关:StackOverflowError 和OutOfMemoryError。
在 HotSpot 虚拟机中,可以使用 -Xss 参数(如:-Xss1M)来设置栈的大小。栈的大小直接决定了函数调用的可达深度。
虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中,存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。每一个方法的调用都伴随着栈帧的入栈操作。相应的,方法的返回则表示栈帧的出栈操作。
如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中的局部变量表就会比较大,栈帧会膨胀以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多。
局部变量
如果一个局部变量被保存在局部变量表中,那么GC根就能引用到这个局部变量所指向的内存空间,从而在GC时,无法回收这部分空间。这里有一个非常简单的示例来说明局部变量对GC的影响。
3、本地方法栈
本地方法栈和Java虚拟机栈的功能很相似,也属于线程的私有空间。Java虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理本地方法的调用。本地方法并不是用Java实现的,而是使用C实现的。在SUN的Hot Spot虚拟机中,不区分本地方法栈和虚拟机栈。因此,和虚拟机栈一样,他也会抛出 StackOverflowError 和 OutOfMemoryError。
4、Java堆
Java堆可以说是Java运行时内存中最为重要的部分,几乎所有的对象和数组都是在堆中分配空间的。Java堆分为新生代和老年代两个部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被回收,生存得足够长,老年对象就会被移入老年代。
新生代又可进一步细分为:
edensurvivor space0(s0 或者 from space)survivor space1(s1或者to space)eden:对象的出生地,大部分对象刚刚建立时,通常会存放在这里。s0 和 s1 为 survivor(幸存者)空间,存放其中的对象至少经历过一次垃圾回收,并得以幸存。如果在幸存区的对象到了指定年龄仍未被回收,则有机会进入老年代(tenured)。
5、方法区
方法区也是 JVM 内存区中非常重要的一块内存区域,与堆空间类似,它也是被 JVM 中所有的线程共享的。方法区主要保存的信息是类的元数据。
方法区中最为重要的是类的类型信息、常量池、域信息、方法信息。
1、类型信息包括类的完整名称、父类的完整名称、类型修饰符(public/protected/private)和类型的直接接口类表;2、常量池包括这个类方法、域等信息所引用的常量信息;3、域信息包括域名称、域类型和域修饰符;4、方法信息包括方法名称名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈和方法帧栈的局部变量区大小以及异常表。总之,方法区保存的信息,大部分来自于 class 文件,是 Java 应用程序运行必不可少的重要数据。
方法区是 JVM 的一种规范。 在Host Spot虚拟机的实现中,方法区也被称为永久区,是一块独立于 Java 堆的内存空间。虽然叫永久区,但是永久区中的对象同样可以被 GC 回收的(注:在 Java8 中,永久区已经被 Metaspace 元空间取而代之。相应的,JVM参数 PermSize 和 MaxPermSize 被 MetaSpaceSize 和 MaxMetaSpaceSize 取代,使用无效)。对永久区 GC 的回收,通常主要从两个方面分析:一是 GC 对永久区常量池的回收;二是永久区对类元数据的回收。