虚拟机中数据的那些事儿
一.虚拟机的数据域
虚拟机的运行时数据域组成:程序计数器,虚拟机栈,本地方法栈,堆,方法区,运行时常量池(方法区的一部分)
1.线程隔离部分:虚拟机栈,本地方法栈,程序计数器
2.线程共享:方法区,堆
程序计数器:当前线程的所执行字节码的行号指示器。(各种分支,循环,跳转等都是由当前线程的程序计数器完成)。
程序如果执行一个Java方法,这个计数器记录的是正在执行虚拟机字节码的指令地址,如果执行的是Native
方法,这个计数器为空(undefined),唯一一个没有规定OutOfMemoryError的区域。
虚拟机栈:1.生命周期与当前线程相同(线程私有)。
2.描述的是Java的内存模型:每个方法被执行的时候都会创建一个栈帧,用于保存局部变量表,操作栈,动态链接,
方法出口等信息。每一个方法被调用知道执行完成的过程,对应着一个栈帧从入栈到出栈的过程。
3.局部变量表包含这个各种基本的数据类型,还有对象的引用(指针,句柄,字节码指令的地址)。
4.如果当前线程的虚拟机栈满了(例如递归层数太深)那就会抛出StackOverFlowError,可以动态扩充栈的大小,
但是如果扩充也无法申请到足够的内存,这个时候会抛出OutOfMemoryError。
本地方法栈:这个是实现Native方法的服务。自由度很高,本地方法栈中使用的那些语言或者数据结构没有强制规定,也会抛出
OutOfMemoryError或者StackOverFlowError异常。
Java堆:1.存放对象实例,几乎所有对象实例都存在堆中分配内存。
2.是垃圾收集器的主要区域(GC堆)
3.内存中的是不连续的,可扩展的。
4.堆还可以细分为新生代与老年代,还可以分为:Eden:From Survivor:To Survivor;8:1:1。
5.为了更好的管理以及回收内存:Java堆中还可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
方法区:1.用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。(Non-Heap)大伙们说的永久代。
2.自由度很高啊,内存可以选择固定或者可扩展,还可以不实现垃圾收集。
3.永久代也不是一定永远存在的,这个区域的回收目的主要是对常量池的回收和对类型的卸载,条件苛刻啊,但是回收还是有必要。
4.同样的,无法满足内存分配的需求时,会抛出OutOfMemoryError。
运行时常量池:存放编译期间生成的各种字面量和符号引用,类加载后存放到运行时常量池中。而且具有动态性,不一定是编译期间生成常量
运行期间也可以将常量放到常量池中。
直接内存:1.可能导致OutOfMemoryError
2.JDK1.4中加入的NIO基于通道与缓冲区的IO方式
3.通过Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。(能提高性能,避免在Java堆与Native堆中来回复制数据)。
4.这个内存大小也是会受到本机总内存的影响,RAM和SWAP交换区。
二.对象的访问方式
场景:Object obj = new Object();新建的一个对象。
分析:obj中的信息(类信息,父类,方法,接口等)都会在Java堆中呈现。在虚拟机栈中,这个会成为一个reference(引用类型)
出现在本地变量表中。
问题:虚拟机栈中的reference是如何定位到Java堆中的?
方法:1.使用句柄
Java堆中有一小块内存是作为句柄池,reference指向的就是对象句柄的地址。句柄存放就是对象实例数据和类型数据(方法区)
2.使用指针
两种方法的优缺点:
句柄的好处就是,reference存放的是稳定的句柄地址,在对象移动的时候(GC收集常做的事),就只是需要改变句柄的实例指针即可。
指针的好处:就是快,比句柄少了一次指针的访问。(Sun HotSpot就是使用指针)。
总结:主要介绍了虚拟机的数据区域情况和对象的访问方式,这个是学习虚拟机最主要掌握的根本
参考:深入理解java虚拟机(神书java开发必读啊)