运行时数据区域
运行时数据区主要分为两个部分
1.线程私有:虚拟机栈、本地方法栈、程序计数器
这些区域依赖线程的启动和结束而创建和销毁
2.线程共享:堆、方法区
程序计数器
程序计数器是一块很小的内存区域,他的作用可以看做当前线程所执行的字节码的行号指示器,每一个线程根据独有的程序计数器来确定当前线程所执行的行号。字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支、跳转、循环、异常处理、线程恢复都需要依赖于程序计数器。
在一个线程中,如果程序正在执行的是一个java方法,程序计数器记录的是当前虚拟机正在执行的字节码指令地址,如果程序执行的Native方法(比如调用底层的C,C++等非java方法),程序计数器值为空,所以程序计数器不可能有内存溢出的情况,是java虚拟机中唯一一个没有规定任何OutOfMemoryError情况的区域。
虚拟机栈
很多java程序员都喜欢把内存分为“栈”和“堆”,因为这两块是java内存中最重要的部分,而这里的“栈”就是虚拟机栈。
栈的概念就是先进后出,每一个方法执行的时候都会创建一个栈帧,就像一个已知内存大小的小箱子,然后放在虚拟机栈中,方法结束后就取出来。栈帧里存放着局部变量表、操作栈、动态链接、方法出口等信息。
局部变量表中存放在编译期已知的各种基本数据类型、对象引用。其中64位的long、double类型占用两个局部变量空间,其余的数据只占一个。
操作栈往往用来数值计算的内存区域,从局部变量表中取到需要计算的数值,然后存放在操作栈中。所以为了减少内存的消耗,VM一般都是只分配当前方法中单次计算所需要的最大内存空间给当前的栈帧。同时为了增加运行效率,减少数据的不断复制,在大部分虚拟机的实现中,将当前方法的局部变量表和上层方法的操作数栈的内存形成部分重叠,从而减少参数的不断复制而引起的性能消费。
本地方法栈
虚拟机栈执行的肯定是java方法,而本地方法栈中调用的就是Native方法,一个Native方法就是一个java调用非java代码的接口
堆内存
堆内存是java虚拟机内存中最大的一块,用于存放new出来的对象示例,所有线程公用这块内存区域。为了更好的管理内存和垃圾回收,堆又细分为新生代和老年代,通过分代回收算法进行垃圾回收,所谓的分代回收算法只是结合了以前的三种算法:复制算法、标记清理法、标记整理法。以后的垃圾回收机制中会细讲。
方法区
用于存放以为加载的类信息、静态变量、常量、及时编译器编译后的代码等数据,也就是所谓的永久代。由于经常会引起内存溢出,jdk8废弃了永久代,改为元空间存储,占用系统本机的内存。
运行时常量池:Class文件除了类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,将会存放在运行时常量池中,运行期间的一些常量也会放在常量池中。
直接内存
直接内存又叫堆外内存,这部分内存不是由jvm管理和回收的。需要我们手动的回收。堆内内存是属于jvm的,由jvm进行分配和管理,属于"用户态",而推外内存是由操作系统管理的,属于"内核态"。
package MM;
import java.nio.ByteBuffer;
public class Buffer {
public static void main(String[] args) {
// TODO Auto-generated method stub
while(true) {
ByteBuffer.allocate(10*1024*1024);
}
}
在JDK4中加入了NIO类,引入了基于通道与缓冲区的IO方式,可以调用Native方法在堆外分配一块内存区域,然后通过一个存储在java堆里的DirectByteBuffer对象作为引用进行操作,避免来回复制,显著提高效率。
采用直接内存的优点:
1:对于频繁的io操作,我们需要不断把内存中的对象复制到直接内存。然后由操作系统直接写入磁盘或者读出磁盘。
这时候用到直接内存就减少了堆的内外内存来回复制的操作。
2:我们在运行程序的过程中可能需要新建大量对象,对于一些声明周期比较短的对象,可以采用对象池的方式。但
是对于一些生命周期较长的对象来说,不需要频繁调用gc,为了节省gc的开销,直接内存是必备之选。
3:扩大程序运行的内存,由于jvm申请的内存有限,这时候可以通过堆外内存来扩大内存。
还有很多没讲清楚的,后续文字中会讲到。关注微信公众号沉沉影视任何电影网络剧发名字就有百度云链接,还有海量学习视频哦~ |
---|