说实话,看完这章之后有点失望。
首先说的是运行时数据区域。有些区域会随着虚拟机进程的启动而存在,有些区域则依靠用户线程的启动和结束而建立和销毁。
这句话我感觉是很重要的。
首先说程序计数器。它是线程私有的。是当前程序所执行的字节码的行号指示器。
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令包括 分支,循环,跳转,异常处理,线程恢复等。
并且,java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,所以在确定的任何一个时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。
总而言之。程序计数器是线程安全的,并且没有了。
接下来是java虚拟机栈。以前我只知道方法局部变量都是存储在栈上的,现在基本也差不多。
它是线程私有的,生命周期和线程相同。它描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用来存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用到执行完成的过程都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈的局部变量表中存放了编译期中可知的各种基本数据类型以及对象引用类型,他不等同于对象本身,可能是一个指向对象起始地址的引用指针,可能是指向一个代表对象的句柄或其他于此对象相关的位置和returnAddress类型(指向了一个字节码指令的地址)。它所需要的内存空间在编译期间完成分配
java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,存放的主要是对象实例。根据java虚拟机的规范,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
方法区于堆一样,都是各个线程共享的内存区域,它用于存储已被java虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,运行时常量池是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
对象的创建:
虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过,如果没有,先执行相应的类加载过程
加载检查通过后,就在虚拟机分配内存,对象所需内存的大小在类加载完成后则可完全确定。
用java程序通过栈上的reference数据来操作堆上的具体对象,所以访问方式有两种使用句柄和直接指针两种。
如果是句柄访问,那么java堆中会划分出一块内存来作为句柄池。reference中存储的就是对象的句柄地址。而句柄中包含了实例数据类型与类型数据各自的具体地址信息。如图:
如果使用直接指针访问,那么java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息。而reference中存储的直接就是对象地址。如图:
各有优势:
使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
而直接指针访问方式的最大好处是速度更快。它节省了一次指针定位的时间开销,由于对象的访问在java找那个很频繁,所以,这开销后也是非常可观的执行成本。