您的位置:首页 > 其它

JVM学习篇(1)之组成结构

2016-01-21 16:37 316 查看

JVM的组成

JVM由4大部分组成



1.        ClassLoader

2.        Runtime Data Area

3.        Execution Engine

4.        Native Interface。

1.     ClassLoader:是负责加载class文件,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution
Engine决定。
2.        Execution Engine:是执行引擎。Class文件被加载后,会把指令和数据信息放入内存中,Execution Engine则负责把这些命令解释给操作系统。

3.        Native Interface :负责调用本地接口的。他的作用是调用不同语言的接口给JAVA用,他会在Native
Method Stack中记录对应的本地方法,然后调用该方法时就通过Execution Engine加载对应的本地lib。

4.        Runtime Data Area :是存放数据的。

Java内存

运行时数据区



1.        程序计数器

1)        作用:当前线程所执行的字节码的行号指示器,通过计数器来选取下一条需要执行的字节码指令。

2)        线程私有:每个线程都有一个独立的程序计数器。且互不影响、独立存在。

3)        Java方法—------正在执行的字节码指令地址。

Native方法----------空指针

2.        Java虚拟机栈

1)        线程私有:生命周期同线程。一个线程对应一个栈。栈里存储的是栈帧。

2)        一个方法对应一个栈帧。

a)        栈帧中包含:局部变量,执行环境,操作数栈。

b)        方法从调用到完成对应栈帧在虚拟机中的入栈、出栈。

3)        局部变量表:用来存储一个类的方法中所用到的局部变量。在编译期分配大小,且大小固定。

4)        执行环境:用于保存解析器对于java字节码进行解释过程中需要的信息,包括:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针

5)        操作数栈:用于存储运算所需要的操作数和结果

3.        本地方法栈

与Java虚拟机栈类似,为Java虚拟机使用到的Native方法服务。





4.        Java堆:

1)        作用:是用来存放对象信息的,和Stack不同,Stack代表着一种运行时的状态。换句话说,栈是运行时单位,解决程序该如何执行的问题,而堆是存储的单位,解决数据存储的问题

2)        线程共享

3)        用于存放实例对象,是垃圾收集器管理的主要区域。

5.        方法区(非堆内存)

1)        线程共享,用于存放Class字节码文件的数据结构

2)        其中包含运行时常量池

a)        用于存放编译期生成的字面变量和符号引用。

b)        具有动态性,运行期间也可能将新的常量放入池中。

JVM中对象的探秘(发生在堆中)

1.        对象的创建

1)        检查这个指令的参数是否能在常量池中定位到一个类的符号引用,同时检查这个符号引用所代表的类是否已被加载、解析和初始化。若没有则加载。

2)        为新生对象分配内存的:(对象大小在加载后即可确定)

方式:

a)        指针碰撞式:适用于内存规整。

b)        空闲链表式:适用于内存不规整

安全:

a)        对分配内存空间的动作同步处理。

b)        内存的分配按照线程划分到不同的空间中。由此每个线程在Java堆中需预先分配一块小的内存,叫本地线程分配缓冲。

3)        初始化默认值为0

4)        为对象设置必要的信息。这些信息存储在对象头中。通过对象头可获得对象的信息。

5)        执行init方法初始化

2.        对象在内存中的存储结构

对象头、实例数据和对其填充

1)        对象头

2)        实例数据:对象在堆中真正存储的有效信息。(程序中定义的各种类型字段,包括父类的。)

3)        对其填充:保证8字节的整数倍。

3.        如何定位对象

Java程序通过栈上的referenc数据来操作堆上的对象。操作方式:

1)        句柄访问:



优点:对象移动时只需修改实例指针即可,不用修改reference。

2)        直接指针



         优点:效率高,加少了一次寻址的时间。

垃圾收集器与内存分配策略

内存的回收

判断对象是否可被回收



1.        引用计数算法

1)        思想:给对象中添加一个引用计数器,没放有一个地方引用它时,计数器值就+1;引用失效时,计数器值就-1;当计数器的值为0时表示不再被任何对象引用。

2)        优点:简单、高效

3)        缺点:不能解决循环引用的问题。(主流Java虚拟机中没有使用这个方法)

2.        可达性分析算法:(实际使用的方案)

1)        思想:

通过一系列的称为“GC Root”的对象作为起点,从这些结点开始向下搜索,搜索所走的路径称为【引用链】,当一个对象到GC Root没有任何引用链相连时,则此对象是不可用的。

2)        哪些可以是“GCRoot”

a)        Java虚拟机栈(栈帧中本地变量表)中的引用(referencce)

b)        本地方法栈中的引用(reference)

c)        方法区中类的静态属性引用

d)        方法区中常量引用

3.        引用的分类

1)        强引用

类似于Object obj = new Object();

2)        软引用

用来描述一些有用但非必须的对象。有SoftReference类实现。

回收时机:将要发生内存溢出异常之前,将这些对象列近回收范围中,之后进行第二次回收。

3)        弱引用

用来描述非必须对象,强度比软引用更若一些。由WeakReference类实现。

只能生存到下一次垃圾收集之前。

回收的时机:当垃圾收集器工作时,无论当前内存是否足够,都会被回收。

4)        虚引用

由PhantomReference类实现。

方法区

主要回收两部分:废弃常量和无用的类。

1.        废弃常量:

判定:与堆中的对象类似。没有任何String对象引用。

2.        无用类

判定:

1)        Java堆中不存在任何该类的对象。(对象没有)

2)        该类的Class对象没有在任何地方被引用。(Class不被引用)

3)        该类的ClassLoader已被回收。(加载器没了)

回收算法

3种回收算法

1.        标记-清理算法            (最基本的回收算法)

1)        思想:分为标记和清除两个阶段

首先标记出所有需要回收的对象(即标记那些可被回收的对象),在标记完后统一回收所有被标记的对象

2)        效率低、产生大量不连续的内存碎片。

2.        复制算法              (解决上一算法中的效率问题)

1)        思想:将内存分为大小相等的两个部分,每次使用一个部分。当其中一块用完了,就将还存活的对象复制到另一块中(这一步也起到了整理碎片的目的),然后对使用过的内存空间一次性清理掉。

2)        优点:简单、高效

3)        缺点:内存为原来的一半。

4)        实际使用:

a)        使用用于新生代的回收

b)        改进:

将内存分为一大二小3块空间。Eden和2个Survivor。(8:1)

回收时将Eden和Survivor中还存活的对象一次性地复制到另一块Survivor中,然后清理。

如果另外一块Survivor空间没有足够空间存放上一次新生代收集的存活对象时,将多余的通过分配担保机制进入老年代。

3.        标记-整理算法

1)        使用于老年代

2)        思想:标记,然后将所有存活的对象都想一端移动,最后直接清理掉端边界以外的内存。

注:新生代和老年代根据对象的存活周期长短来划分的。

HotSpot的算法实现   (如何高效执行GC)

1.        枚举GC Root结点     (如何枚举)

1)        方法:借助一个OopMap的数据结构来存储哪些地方存放着引用。

2.        安全点                (如何保证一致性)

1)        原因:在枚举结点的时候需要保证一致性。以保证在枚举结点时引用关系不发生变化。保证一致性的措施是暂停所有Java执行的线程。

2)        作用:只有达到安全点时才停顿,开始GC

3)        安全点的选取:在指令复用的地方设置。

4)        在GC发生时如何让所有线程都跑到安全点:

a)        抢先式:先全部中断,如果有没到安全点的在回复线程让它跑到安全点。    (几乎不采用)

b)        中断式:发生GC中断时不直接对线程操作,通过设置一个标记。各线程执行时主动轮询这个标记,若标记为真则自己中断线程。

轮询的地方 = 安全点 + 对象分配内存地方

3.        安全区

1)        安全区内不会发生引用关系的变化。

2)        线程在安全区内任何地方都能随时中断。

3)        结合安全区的完整停顿过程:

a)        线程进入安全区时,标记自己。

b)        发生GC时,不用管标示自己已进入安全区的线程状态

c)        线程离开安全区时,检查是否已完成了根结点的枚举。

完成--à 离开

未完成----à等到完成后再离开

对象销毁的过程(经过两次标记)

1.        寻找:通过第一步(可达性分析)寻找没有与“GC Root”相关联的引用。(第一次标记)

2.        筛选:筛选的条件是此对象是否有必要执行finalize()方法。

当对象没有重写finalize()方法或已经被虚拟机调用过,视为“没必要执行”。

3.        销毁:

1)        将上一步筛选出的对象放入到F-Queue的队列中。

2)        JVM创建一个低优先级的Finalizer线程来执行其中对象的finalize()方法。

3)        若有的对象finalize()方法执行时间过长,将会跳过这个对象,稍后GC会对F-Queue中的对象进行第二次小规模标记。(第二次标记)

4)        在第二次回收之前还没有被其他引用链接的话,在第二次回收时就被真的回收了。

内存的分配



1.        对象优先在Eden分配

对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机发起一次Minor GC(新生代GC)。

2.        大对象直接进入老年代

大对象:需要大量连续内存空间的Java对象。

目的:可以避免在Eden区及两个Survivor区之间发生大量的内存赋值。

3.        长期存活的对象将进入老年代

区分:

1)        为每个对象定义一个年龄(Age)计数器。

2)        每经过一次Minor GC,年龄+1。

3)        达到阈值(15)时,移至老年代。

4.        动态年龄判定

设A=Survivor空间中相同【年龄】对象个数大于一般。年龄大于A的对象也可直接进入老年代。

5.        空间分配担保        (老年代为新生代担保)

发生Minor GC之前,查询老年代最大连续空闲空间OM

若OM > 新生代所有对象总和NM

         Minor GC 安全,可以执行

否则

         查看是否允许担保失败

         允许:

若OM > 每次晋升对象的平均大小pM

         执行Minor GC

否则

         执行一次FullGC(老年代GC)让老年代腾出更多空间

                            不允许

                                     执行一次FullGC(老年代GC)让老年代腾出更多空间
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JVM