找工作——jvm复习(一)

来源:互联网 发布:出境旅游数据 编辑:程序博客网 时间:2024/06/08 07:29

用有道笔记记录着找工作相关的知识,觉得那样不符合一个程序人开源的精神,决定把复习的知识写成博客,也算是记录自己找工作的艰辛历程吧。知识点可能不是很全面,第一轮复习,主要先大致浏览一遍知识点。

1、java内存模型
java运行时的内存区域被划分为:程序计数区、java虚拟机栈、本地方法栈、方法区、堆
其中程序计数区、java虚拟机栈、本地方法栈是线程私有的;方法区、堆是线程共享的;
程序计数区:由于CPU的每个内核在某一时刻只能执行一条线程中的指令。为了线程切换后能恢复到正确的执行位置,每条线程度需要一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,称这一类内存区域为“线程私有”的内存区域;
java虚拟机栈:java虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时,都会产生一个栈帧(jvm以栈帧为单位保存线程的运行状态,每启动一个新的线程时,也就会分配一个java栈),用于存储局部变量表、操作数栈、动态链接、方法出口等信息(栈帧包括局部变量区、操作数栈、帧数据区)。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型;
本地方法栈:与java虚拟机栈类似。虚拟机栈是为虚拟机执行java方法(字节码)服务,而本地方法栈是为虚拟机使用到的native方法服务;(设置本地方法栈大小:-Xoss;实际上无效,因为HotSpot虚拟机不区分本地方法栈和虚拟机栈;-Xss参数设定栈容量)
方法区:线程共享的内存区域,主要用来存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等,方法区早期被称为“永久代”;
堆:用来存放对象的实例,主流的虚拟机都是按照可扩展的方式来管理该块内存(通过-Xmx和-Xms控制内存大小)(GC回收机制重点照顾对象)

2、运行时常量池
是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。
附:常量池,某类型所用常量的一个有序集合,包括直接常量(基本类型,String)和对其他类型、字段、方法的符号引用。之所以是符号引用而不是像c语言那样,编译时直接指定其他类型,是因为java是动态绑定的,只有在运行时根据某些规则才能确定具体依赖的类型实例,这正是java实现多态的基础。

3、虚拟机中对象的创建
1)遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那么必须先执行相应的类加载过程。
2)类加载检查通过后,接下来虚拟机将为新生对象分配内存。分配内存的规则根据堆内存存储是否规整分为“指针碰撞”模式和“空闲列表”模式。而java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
3)为了避免频繁的对象创建,对堆内存的管理造成混乱。有两种方法:一是对分配内存空间的动作进行同步处理——采用CAS配上失败重试的方式保证更新操作的原子性;二是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,成为本地线程分配缓冲(Thread Local Allocation Buffer , TLAB)。
4)内存分配完成后,虚拟机对分配到的内存空间都初始化为零值(不包括对象头)。如果使用TLAB,这一过程可以提前至TLAB分配时进行。
5)虚拟机读取对象头中的信息,对对象进行必要的设置。
6)执行new指令之后会执行方法。

4、对象的内存布局
对象在内存中存储的布局可以分为3块区域:对象头、实例数据、和对齐填充
对象头:对象头包括两部分信息:一、用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;二、类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据:对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
对齐填充:并不是必然存在的,它仅仅起着占位符的作用。对象的大小必须是8字节的整数倍。当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

5、对象的访问定位
栈内存中对象的引用,并没有指定通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式是取决与虚拟机实现而定的。有两种方式:
1)使用句柄:java堆中将会划分出一块内存来作为句柄池,栈中的引用存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
2)直接指针:栈中的引用存储的就是对象地址;
使用句柄访问最大的好处就是当对象移动时,只会改变句柄中的实例数据指针。而引用本身不需要修改。直接指针访问,省去了一次指针定位的开销。

Java中的参数传递时传值呢?还是传引用?
要说明这个问题,先要明确两点:
1.不要试图与C进行类比,Java中没有指针的概念
2.程序运行永远都是在JVM栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。
明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以它都是进行传值调用(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。
但是传引用的错觉是如何造成的呢?在运行JVM栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到JVM堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了。
对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。
JVM堆和JVM栈中,JVM栈是程序运行最根本的东西。程序运行可以没有JVM堆,但是不能没有JVM栈。而JVM堆是为JVM栈进行数据存储服务,说白了JVM堆就是一块共享的内存。不过,正是因为JVM堆和JVM栈的分离的思想,才使得Java的垃圾回收成为可能。
Java中,JVM栈的大小通过-Xss来设置,当JVM栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常的是无法返回的递归,因为此时JVM栈中保存的信息都是方法返回的记录点。

0 0