JVM运行时数据区
JVM的多线程就是通过线程轮流切换分配处理器执行时间的方式来实现的。
1.程序计数器
可以看做是当前线程所执行的字节码的行号指示器,在概念模型中,字节码解释器工作时就是通过这个值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能。
如果执行的是JAVA方法,计数器记录的是正在执行的虚拟机字节码指令地址,如果是Native方法,则计数器值为空。无OutOfMemoryError情况。
2.虚拟机栈
同计数器,线程私有,生命周期同线程一样。
描述Java方法执行的内存模型,每个方法执行时会创建栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等,每个方法从调用到执行完成的过程,对应一个栈帧在虚拟机中入栈到出栈的过程。
局部变量表存放编译期可知的各种基本数据类型(bool,int….对象引用等)和returnAddress类型(指向一条字节码指令的地址),其中64位长度的long,double占两个局部变量控件(Slot),其余一个。如果线程请求的栈深度大于虚拟机允许的,则抛出StackOverFlowError。
3.本地方法栈
与虚拟机栈类似,只不过对应的Native方法,同样也会抛出stackOverFlowError和OutOfMemoryError
4. JAVA堆
线程共享区,存放所有实例,GC主要区域,GC算法一般都采用分代收集方法。java堆可以细分为新生代,老生代,永生代(从1.7开始渐渐被移除,1.8全面移除)。
从内存分配的角度来看,线程共享的Java堆可能划分多个线程私有的分配缓存区(ThreadLocal ALLOCATION buffer)
java堆可以处在物理上不连续的内存空间,单逻辑上连续即可。在实现时,可以是固定大小的,也可以是动态扩展的,通过-Xmx/-Xms来控制。
5.方法区
和java堆一样,线程共享内存区,它用于存储已经被虚拟机加载的类信息,常量,静态变量,及时编译器编译后的代码等数据。
在JDK1.7之前,都是使用永生代来实现方法区,这样hotspot可以像像管理java堆一样管理这部分内存,省去为方法区编写内存管理代码的工作。在1.7之后,hotspot将放弃使用永生代,转而使用Native Memory来实现方法区。在1.7中,已经把原理放在永生代中的字符串常量池中移除。
6. 运行时常量池
运行时常量池是方法区中的一部分,class文件中出来有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分将在类加载后进入方法区的运行时常量池中存放。
java虚拟机对class文件的每一个部分有严格的规定,每一个字节用于存储的哪种数据都必须符合规范上的要求,才能被JVM认可,加载,装配,执行。但对于运行时的常量池,jvm没有做任何细节要求,不过除了保存class文件中描述的引用外,还会吧翻译出来的直接引用也存储在运行时常量中。
运行时常量池还有一个重要特性就是,不要钱常量一定只有在编译期产生,也就是并非预置入class文件中的常量池内容才能进入方法区运行时常量池,运行期间也可以动态放入新的常量,例如String类的intern()方法.
7.直接内存
直接内存并不是运行时数据区一部分,也不是java虚拟机中定义的内存区域,但是却被频繁使用。而且也会导致outofMemoryError异常出现。
在JDK1.4中新加入的NIO类,引入一直基于chanel与buffer的I/O方式,它可以直接使用native函数直接分配堆外内存,然后通过java堆中的directByteBuffer对象作为这块内存的引用进行操作。