JVM&垃圾回收机制

来源:互联网 发布:三星s5智能网络切换 编辑:程序博客网 时间:2024/05/16 08:44

1垃圾回收算法?

根对象(java静态变量寄存器.典型的是Main函数)

1.   引用计数(Reference Counting
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

问题:没办法解决循环引用问题。比如:对象A有一个引用指向B对象,B也有一个引用指向A,如果A和B都没有被其他对象引用,其实已经是垃圾,但是没办法回收。

2.   标记-清除(Mark-Sweep)
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。

根引用:在栈中


缺点:效率不高,需要扫描所有对象。存在内存碎片问题

3.   复制(Copying)
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。 标记可用对象,然后复制到另一堆空间。适合生命周期短对象。


缺点:需要2倍内存。

4.   标记-整理(Mark-Compact)
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

标记-清除后,整理内存,防止内存碎片。

5.   增量收集(Incremental Collecting)
实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。

6.   分代(Generational Collecting) (在新生代,采用复制算法;在老生代,采用Mark-Compact标记算法,永久代,存放Class结构,常量信息信息)
基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。Survivor1(幸存区1)Survivor2(幸存区2)复制算法双倍内存空间。

清空eden时间很短,主要时间花费在复制对象上,适合生命周期短对象,这样更多对象死亡,只要复制很少对象,这样加快程序速度。

Hotspot JVM 6中的共划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation)

(一) 年轻代:

年轻代分三个区。一个Eden区,两个Survivor区。对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到一个Survivor区,当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当第二个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到年老代。

(二) 年老代(Old Generation)

存放了经过一次或多次GC还存活的对象,一般采用Mark-Swap或者Mark-Compact算法进行GC

(三) 永久代

也就是前面提到的方法区,并不属于堆(Heap).但是GC也会光顾这个区域,存放了每个Class的结构信息,包括常量池、字段描述、方法描述。与垃圾收集要收集的Java对象关系不大

 

备注:没有万能的垃圾回收器,每种垃圾回收器都有自己的适用场景

 

 

2 JVM程序计数器和堆栈怎么组织起来的

线程隔离就是线程独享,互相独立运行线程。多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。


    Javap –verbose A.class vm原语类汇编

JVM实例针对进程的,执行引擎针对线程。

栈(栈帧):存放方法参数,局部变量,函数返回结果。Java方法调用的状态(包括局部变量,参数类型,返回值),当某个线程调用方法的时候,JVM就会将一个新的帧压入到Java内存栈,当方法调用完成过后,JVM将会从内存栈中移除该栈帧。VM里面不存在一个可以存放中间计算数据结果值的寄存器,其内部指令集使用Java栈空间来存储中间计算的数据结果值,这种做法的设计是为了保持Java虚拟机的指令集紧凑。

不论是正常结束还是异常结束,JVM都会弹出或者丢弃该栈帧,则上一帧的方法就成为了当前帧。

栈帧为各个独立线程独享的,JVM里面没有寄存器。处理操作帧的时候Java虚拟机是基于内存栈的而不是基于寄存器的

Java指令集相当于Java程序的汇编语言

虚拟机的内层循环的执行过程如下:

do{

取一个操作符字节;

根据操作符的值执行一个动作;

}while(程序未结束)

 

在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句:方法索引)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

调用实例方法 this ,local var1,local  var2 作为012个变量。调用类方法 直接 local var1,local var2

 

PC寄存器[PC registers]:程序计数器:存放下一条将被执行指令的地址,它用来指示当前 Java 执行引擎执行到哪条Java字节码,指针指向方法区的字节码。。(操作码+操作数),正常指令顺序 pc=pc+1  遇到分支跳转时候jump,会指向要跳转的指令地址。

执行引擎(CPU)中指令寄存器会根据pc指向指令地址,将指令代码取指,译码然后启动硬件控制。

 

方法区:存放类字节码信息,静态变量,类操作代码(对象的操作,静态代码),线程共享线程安全的。从方法区获取对类字节码信息,将调用方法压栈,包括局部变量,参数

堆:存放对象的属性,变量。

 

执行引擎(相当于CPU-JIT,just in time):负责执行字节码。方法的字节码是由Java虚拟机的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。执行引擎执行字节码时,首先取得一个操作码,如果操作码有操作数,取得它的操作数。它执行操作码和跟随的操作数规定的动作,然后再取得下一个操作码。这个执行字节码的过程在线程完成前将一直持续。

具体执行过程:

 

 

 

 JVM通过引导类加载器(Bootstrap ClassLoader)加载类Main的字节码,通过校验之后为类在方法区建立静态内存结构。

 

 

 

② JVM启动主线程执行Main的静态方法main。将main以片断(Frame)的方式压入当前线程堆栈。

 

 

 

③ JVM通过类加载器加载类Class1,并为Class1建立方法区内存结构。然后调用Class1的构造函数,并将其压入当前线程堆栈。执行完毕构造函数之后把构造函数片断(Frame)从当前线程堆栈中移出。

 

 

 

④ JVM执行Class1的实例p的add方法,并且将其压入堆栈,当add方法返回,从当前线程堆栈中移出add片断(Frame)。

 

⑤ 主函数main执行完毕返回,当前线程堆栈清空,主线程执行完毕,进程直接退出。

 

 

JVM通过执行引擎来完成字节码的执行,在执行过程中JVM采用的是自己的一套指令系统,每个线程在创建后,都会产生一个程序计数器(pc)和栈(Stack),其中程序计数器中存放了下一条将要执行的指令,Stack中存放Stack Frame,表示的为当前正在执行的方法,每个方法的执行都会产生Stack FrameStack Frame中存放了传递给方法的参数、方法内的局部变量以及操作数栈,操作数栈用于存放指令运算的中间结果,指令负责从操作数栈中弹出参与运算的操作数,指令执行完毕后再将计算结果压回到操作数栈,当方法执行完毕后则从Stack中弹出,继续其他方法的执行。

      在执行方法时JVM提供了invokestaticinvokevirtualinvokeinterfaceinvokespecial四种指令来执行

     1invokestatic:调用类的static方法

     2 invokevirtual 调用对象实例的方法

     3 invokeinterface:将属性定义为接口来进行调用

     4 invokespecial JVM对于初始化对象(Java构造器的方法为:<init>)以及调用对象实例中的私有方法时。

 

主流的访问方式有两种:使用句柄和直接指针 如果使用句柄访问方式Java堆中将会

划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,如下图所示:

 

如果使用的是直接指针访问方式Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址,如下所示:

这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非

常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针

定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。

就本书讨论的主要虚拟机Sun HotSpot而言,它是使用第二种方式进行对象访问的,

但从整个软件开发的范围来看,各种语言和框架使用句柄来访问的情况也十分常见。

 

执行技术

    主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行

    (1)解释属于第一代JVM,

    (2)即时编译JIT属于第二代JVM,

    (3)自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式

(4)自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。




0 0
原创粉丝点击