JVM内存即运行时数据区,分为堆(heap)、方法区、栈、本地方法栈、程序计数器。
其中,堆和方法区是线程共享的,栈、本地方法栈和程序计数器是每个线程私有的。
-
本地方法栈
提供虚拟机使用到的本地Native方法服务。 -
程序计数器
程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。每个线程都需要有一个独立的程序计数器,并且不能互相被干扰,字节码解释器选取下一条指令,分支,循环,跳转,异常处理,线程恢复都需要依赖它,我们称这类内存区域为线程私有内存。 -
栈
每个方法在执行的同时都会创建一个栈帧用于存储局部变量,操作数栈,动态链接,方法出口等信息,每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出stackoverflowError;如果虚拟机可以动态扩展,但是无法申请到足够的内存时,就会抛出outOfMemoryError异常。 -
方法区
方法区也称“永久代”,主要为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 带来不必要的复杂度,并且回收效率偏低。
- 堆