Java虚拟机基本结构Java虚拟机基本结构1.类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放在一块叫方法区的内存空间。除了类的信息外,方法区可能还存放常量池信息。2.Java堆在虚拟机启动时候建立,他是java程序最主要的内存工作区域,几乎所有的对象实例都保存在这,堆是所有线程共享的。3.Java的NIO库允许java程序直接使用内存,直接内存是java堆外的,直接像系统申请内存空间。4.垃圾回收系统是java虚拟机重要的组成部分,垃圾回收系统主要对方法区,java堆,直接内存进行回收,其中java堆是垃圾回收的主要场合。5.每一个java虚拟机线程都有一个私有的java栈,一个线程在创建的时候就建立了一个java栈,java栈中保存着帧信息,局部变量,方法参数,和java方法的调用返回密切关系。6.本地方法栈和java栈类似,最大的不同在于java栈用于java方法的调用,本地方法栈用于本地方法的调用。7.PC寄存器也是每个线程的私有空间,java虚拟机会为每个java线程创建一个PC寄存器,在任意时刻,每个java线程都在执行一个方法,这个正在被执行的方法被称为当前方法,如果当前方法不是本地方法,那么PC寄存器会指向当前正在被执行的指令,否则(本地方法),PC寄存器值就是undefined。8.执行引擎是java虚拟机最核心组件之一,他主要负责执行虚拟机字节码。Java堆Java堆是和java应用程序关系最为密切的内存空间,几乎所有的java对象实例存储在堆中,并且java堆完全是自动管理,通过垃圾回收制度,垃圾对象会被自动清理,不需要显示释放。根据垃圾回收机制的不同,java堆有可能会被分为不同的结构,最常见的一种,就是将java堆分成新生代和老年代,新生代新对象和存放年龄不大的对象,老年代存放老年对象,新生代有可能又被分为eden区,s0区,s1区。S0,s1又被称为from区和to区,它们是两块大小相同的内存空间。大多数情况下对象首先被分到eden区,再一次新生代垃圾回收后,如果对象还活着,则进入s0或者s1区,之后每经过一次新生代垃圾回收,存活的对象的年龄加一,到了一定年龄后,则被定义为老年对象,进入老年代。java栈1.出入java栈Java栈是一块线程私有的内存空间,如果说java堆和程序数据密切相关,那么java栈就和线程执行密切相关,线程执行的基本就是函数调用,每次函数调用的数据都是通过java栈传递。Java栈与数据结构上的栈有着类似的含义,它是一块先进后出的数据结构,只支持出栈和入栈两种操作,在java栈中主要保存的是桢栈,每一次函数调用,就会有一个对应的桢栈压入java栈,每一次函数调用结束,就会又一个桢栈被弹出java栈,当前所正在运行的函数就是当前桢(位于栈顶),它保存着当前函数的局部变量表,中间运算结果等数据。2.局部变量表局部变量表是桢栈重要组成部分,它保存着函数的参数以及局部变量,局部变量表中的变量只在当前函数中有效,当函数调用完成后,随着函数桢栈销毁3.操作数栈操作数栈也是桢栈重要组成部分,他主要保存计算过程中的中间结果,同时作为计算过程中变量的临时保存空间,他也是一个先进后出的数据结构。4.桢栈数据5.栈上分配栈上分配是java虚拟机提供的一项优化技术,基本思想是,对于线程私有的对象,可以将他们打散分配在栈上,而不是分配在堆上,分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高性能。方法区和java堆一样,方法区也是一块所以线程共享的一块内存区域,它用于保存系统的类信息,比如类的字段,方法,常量池等.(方法区也可以理解为永久区),方法区的大小决定了系统可以保存多少个类垃圾回收认识垃圾回收如果不及时对内存中不会再被使用的对象进行垃圾回收,那么这些垃圾对象所占用的内存空间会一直保留到应用程序结束,而这些被保留的空间不被其他对象使用,如果大量不被使用的对象一直占据着内存,当需要内存空间时,无法使用这些被占据的内存,有可能导致内存溢出,所以要进行垃圾回收垃圾回收算法1.引用计数法给任何一个对象添加一个引用计数器,对于一个对象,只要有对象引用了他,那么就在他的引用计数器上加一,当引用失效时候,就减一,当引用计数器为0时,那么对象就不再被使用。2.标记清除法这个方法将垃圾回收分成2个阶段,第一个阶段标记阶段,首先通过根节点,标记所有从根节点的可达对象,所以,未被标记的对象就是未被引用的垃圾对象。接着第二个阶段清除阶段,清除所有未被标记的对象,这个方法可能产生的问题是,执行完垃圾回收后,会有许多不连续的内存空间。当大对象需要连续内存空间是,不连续的内存空间的效率低于连续的内存空间。3.复制算法将原有的内存空间分成大小相同的两块内存,每次只使用其中的一块,在垃圾回收时候,将正在使用的内存中的存活对象复制到未被使用的内存当中去,接着,清除正在使用的内存中的所有对象,每次垃圾回收时,交替进行。如果系统中的垃圾对象很多,那么复制的存活对象就比较少,所以在回收时候这个算法还是很高效的。然后他又可以保证复制完后有连续的内存空间,确保没有碎片。但是他的代价是将系统内存折半,导致其中一块空闲。复制算法比较适用于新生代,因为新生代的对象存活率较小,他的垃圾对象多余存活对象。4.压缩标记算法压缩标记算法是老年代的一种算法,他是改进的标记清除方法,和标记清除法一样,首先也需要从根节点开始,对所有可达对象做一个标记,接着他将所有存活的对象压缩到内存一端,之后清理边界外所有空间,这样做避免了碎片的产生,也不需要将内存分为两块。他等同于标记清除法执行完成之后,执行一次内存碎片整理。5.分代算法将内存区间根据对象的特点分成几块。根据每块内存的特点使用不同的回收算法,提高垃圾回收效率。一般来说java虚拟机会将新建的对象都放入称为新生代的内存区域,新生代对象的特点是存活率较小,因此容易产生垃圾对象,所以对新生代可以采用复制算法来进行垃圾回收,如果经过几次垃圾回收,对象还存活的话,则会将对象放入老年代的内存区域,老年代的对象几乎都是经历几次垃圾回收后得以存活的,因此老年代中的对象被认为存活率较大,垃圾对象较少,因此不适合复制算法,他可以使用标记清除法或者标记压缩法可以提高效率。可达性判断垃圾回收的基本思想就是判断每个对象的可达性,就是从根节点开始是否可以访问到该对象,如果可以,就说明该对象在使用中,反之,说明该对象不再被引用,一般来说这个对象需要被回收,但是在某些情况下,该对象有可能复活,那么对他的回收是不合理的。所以可以为可达性分为几种状态。第一种,从根节点开始可以到达该对象,该对象不被回收。第二种,对象所有引用被释放,但是对象调用了finalize()方法,使对象复活,该对象不被回收第三中,该对象已经调用了finalize()方法,并且没有复活,那么就进入不可达状态,不可达的对象无法复活,该对象会被垃圾回收。Finalize()方法只会调用一次。可以称为根节点的是虚拟机栈(帧栈中存储的本地变量)中引用的对象方法区中类静态属性引用对象方法区中常量引用对象本地方法栈JNI引用对象Java中提供了4个级别的引用强引用-强引用可以直接访问目标对象,强引用所指向的对象在任何时候都不会被系统垃圾回收,虚拟机宁愿抛出OOM异常,也不会回收对象,强引用可能导致内存泄漏。软引用-软引用是比强引用弱一点的引用类型,当堆空间不足时候,就会被回收弱引用-弱引用是比软引用较弱的引用类型,在垃圾回收时,只要发现弱引用,不管系统堆空间使用情况如何,都会将其对象进行回收。虚引用-最弱的一种引用类型,随时随地都会被回收垃圾回收器1.串行回收器串行回收器是指使用单线程进行垃圾回收的回收器,每次回收时,串行回收器只有一个工作线程,串行回收器可以在新生代和老年代中使用,根据作用于不同的堆空间,分为新生代串行回收器和老年代串行回收器。在串行回收器进行垃圾回收时,java应用中所有线程必须暂停等待垃圾回收完成。新生代串行回收器:使用复制算法。老年代串行回收器:使用标记压缩方法2.并行回收器并行回收器在串行回收器的基础上做了改进,他是使用多个线程同时进行垃圾回收ParNew回收器是一个工作在新生代上的回收器,他只是将串行回收器进行多线程化,他的回收策略和算法及参数跟新生代串行回收器一样的。新生代parallelGC回收器也是使用复制算法的收集器,表面上他和ParNew回收器一样是个多线程独占时回收器,但他有个重要特点,他非常关注系统的吞吐量老年代paralleloldGC回收器也是一种多线程并发收集器,和新生代的parallelGC回收器一样他也关注系统的吞吐量,他是一个应用于老年代的收集器。他采用标记压缩算法3.CMS回收器CMS回收器的工作过程与其他垃圾回收器相比,比较复杂,他的主要步骤有,初始标记、并发标记、预清理、重新标记、并发清除和并发重置。其中初始标记和重新标记是独占系统资源的,而预清理、并发标记、并发清除和并发重置是可以和用户线程一起执行的。根据标记清除算法,初始标记、并发标记和重新标记都是为了标记出需要回收的对象,并发清除则是在标记完成后,正式回收垃圾对象。并发重置是指在垃圾回收完成后,重新初始化CMS数据结构和数据,为下一次垃圾回收做好准备,并发标记、并发清理和并发重装都是可以和应用程序线程一起执行的。4.G1回收器….对象何时进入老年代新的对象一般会在新生代的eden区创建。对象的年龄是对象经历数次垃圾回收决定的,在新生代对象每经历一次垃圾回收,如果他没有被回收,那么他的年龄就加1,虚拟机提供了一个控制新生代最大年龄的参数MaxTenuringThreshold,默认情况下,他为15,也就是说新生代在最多经历15次垃圾回收后,还没被回收的话,就将进入老年代。除了年龄外,对象的大小也会影响进入老年代,如果对象太大,无法在新生代保存,他会直接晋升到老年代类加载器1.类装载条件Class只有在必须要使用的时候才会被装载,java虚拟机不会无条件的装载Class类,java虚拟机规定,一个类在使用前,必须经过初始化,这里的使用是指主动使用。主动使用是指当创建一个类的实例时,比如通过new,反射等。当调用类的静态方法时候,当使用类或者接口的静态字段时,当使用反射方法时候,当初始化子类时,首先初始化父类,启动虚拟机含有main方法的那个类2.加载类加载类属于类加载的第一阶段,在加载类时,java虚拟机必须完成以下任务,第一,通过类的全名,获取类的二进制数据流。第二,解析类的二进制流为方法区内的数据结构,第三,创建该类对应的Class类的实例,表示该类型。对于类的二进制流数据,虚拟机可以通过多种途径获取网络本地或者数据库。在获取到二进制流数据后,java虚拟机会处理这些数据,并最终转换成Class的实例,Class实例是访问类型元数据的接口,也是实现反射的关键数据,通过Class类提供的接口,可以访问一个类的方法字段等。当类加载到系统后,就开始类加载的第二阶段,连接操作,连接操作的第一步是验证操作,它的目的是为了保证+加载的字节码是合法,合理的并且符合规范,比如格式检查,语义检查,什么抽象方法是否实现,继承的父类是否是final,字节码的验证,跳转是否正确,符号引用验证等。当一个类验证通过时,开始连接操作的第二步,准备阶段,在这个阶段,虚拟机会为这个类分配相应的内存空间,并设置静态变量的初始值。如果类存在常量字段,那么常量字段也会在准备阶段被附上正确的值,直接存放于常量池,这个赋值属于java虚拟机行为,不属于变量的初始化,事实上在准备阶段,不会有任何java代码执行在准备阶段完成后,就进入第三部解析阶段,解析阶段就是将类的方法,字段,,接口的符号引用转为直接引用类的初始化,是类加载的最后一个阶段,如果前面的步骤都没问题,那么表示的类就能顺利的装载到系统中,此时,类才会开始执行java字节码,初始化阶段最重要的的工作是执行类的初始化方法clinit,这个方法是编译器自动生成的,他是