运行时数据区域
1、程序计数器(Program Counter Register)
程序计数器是一块较小的、线程私有的内存空间,可以看作是当前线程所执行的字节码的行号指示器。jvm的多线程是通过线程轮流切换、分配处理器的执行时间来实现的。为了保证线程切换后能恢复到正确的位置执行,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响。
此内存区域是唯一一个在《java虚拟机规范》中没有规定任何OutMemoryError情况的区域。
2、java虚拟机栈(Java Virtual Machine Stack)
Java虚拟机栈也是线程私有的,生命周期与线程相同。每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
每个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到处出栈的过程。局部变量表中存放了编译期的各种java虚拟机基本数据类型、对象引用等。
3、本地方法栈(Native Method Stacks)
本地方法栈与虚拟机栈所发挥的作用非常相似。区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地方法服务。
4、Java堆 (Java Heap)
java堆是虚拟机所管理的内存中最大的一块、被所有线程共享的一块内存区域。在《Java虚拟机规范》中有言:"所有的对象实例以及数组都应当在堆上分配"。但由于即时编译技术的进步、以及逃逸分析技术上的日益强大,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。
java堆是垃圾收集器管理的内存区域,java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。当前主流的java虚拟机都是按照可扩展来实现的。如果在java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
5、方法区(Method Area)
方法区域java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、即时编译器编译后的代码缓存等数据。
说到方法区,不得不提一下“永久代”这个概念。永久代是jdk1.8之前的方法区实现,jdk1.8及以后方法区的实现便成为元空间。
jdk1.8之前永久代还没被彻底移除的时候通常通过小面参数来调节方法区大小。
-xx :PermSize=N //方法区(永久代) 初始大小 -xx :MaxPermSize=N //方法区(永久代) 最大大小,超过这个大小会抛出Out Of Memory Error 其实这并不是一个好主意,由于固定大小上限,无法进行调整,非常容易导致内存溢出的问题。
jdk1.8的时候,永久代被元空间取而代之,元空间使用的是直接内存。
-xx :Metspaces=N //设置Metspace的初始 -xx :MaxMetaspaceSize=N //设置metspace的最大大小 如果不指定大小的话,随着更多的类的创建,虚拟机会耗尽所有可用的系统内存
6、运行时常量池(Runtime Constant Pool)
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等信息外,还有用于存放编译期生成的各种字面量与符号引用的常量池表(Constant Pool Table)。常量池表会在类加载后存放到方法区的运行时常量池中。
值得注意的是,jdk1.7之后,字符串常量池和静态变量被从方法区中拿到了堆中。但是运行时常量池元空间中。
7、字符串常量池
字符串常量池是JVM为了提升性能和减少内存消耗,针对字符串专门开辟的一块区域,主要目的是为了避免字符串的重复创创建。JDK1.8版本的字符串常量池中存的是字符串对象,以及字符串常量值。
String s = "ab" // 放到常量池中 String s = "ab" //从常量池中查找 System.out.println(aa==bb); //true //在声明一个字符串字面量时,如果字符串常量池中能够找到该字符串字面量,则直接返回该引用; //如果找不到,则在常量池中创建该字符串字面量的对象,并返回其引用。
jdk1.7为什么要将字符串常量池移动到堆中?
主要是是因为用永久代的GC回收效率太低,将其放入堆中,能够更高效及时地回收字符串内存!!!!!!