java虚拟机学习笔记一

来源:互联网 发布:python 传参数 编辑:程序博客网 时间:2024/06/10 00:19
1、jdk1.7主要改进项目包括:
    #提供新的g1手机器,
    #加强对非java语言的调用支持,
    #升级累加在机制
   目前两个商用虚拟机JRockit和HotSpot,下面看java虚拟机的发展史:
    #Sun Classic/Exact VM
    #Sun HotSpot VM
   Dalvik VM并不是一个Java虚拟机,它没有遵循java虚拟机规范,不能直接执行Java的Class文件,使用的是寄存器架构而不是JVM常见的栈架构
   但是它与Java又有着联系,他执行的dex(Davlvik Executable)文件可以通过Class文件转化而来,使用Java语法编写应用程序。
   
2、Java8提供的函数式编程一个重要的有点就是这样的程序天然的适合并行运行。

3、运行时数据区域:
   #程序计数器:
    1):是一块较小的内存空间,可以看成当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过这个计数器的值来选取吓一跳需要执行的
    字节指令。每个线程都需要一个独立的程序计数器,各个线程之间计数器互不影响。
    2):如果是执行Java方法,这个计数器记录是虚拟机字节码地址,如果是执行Native方法,则计数器值为空(Undefined),此内存区域是唯一一个在
    Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
   #Java虚拟机栈:
    1):线程私有的,生命周期与线程相同,虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储
    局部变量表,操作数栈,动态联结,方法出口等信息。每一个方法从调用到完成,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。一般所讲的“栈”,也
    就是现在阶段理解的,以前面试被问道的,就是这个虚拟机栈,的局部变量表部分。
    2)在Java虚拟机规范中,对这个区域规定了两种异常状况:
        #如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
        #如果虚拟机栈可以动态扩展,扩展到无法申请到足够的内存,就会抛出OutOfMemoryError异常。
   #本地方法栈:
    1):和虚拟机栈不同(主要执行Java方法即字节码服务),而本地方法栈则为Native方法服务。
    2):本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
   #Java堆:
    1):是java虚拟机内存管理最大一块,是被所有线程共享的一块内存区域,唯一目的就是存放实例。
    2):所有实例对象以及数组都要在这里分配内存,是垃圾收集器管理的主要区域,即GC堆
    3):由于收集器都参用分代收集算法,又可以分为新生代和老年代。
    4):这就引入到了一个和上面将的线程私有空间的话题,所以在线程共享的Java堆可能分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
    都是存放变量实例。
   #方法区:
    1):方法区和Java堆一样,也是各个线程共享的内存区域,并不是用来存方法的!存储已被虚拟机加载的类信息、常量、静态变量、jit编译后的代码。
    2):很多人可能称方法区为“永久代”,其实二者并不完全等价,仅仅是因为hotspot虚拟机设计团队把gc分带手机扩展到方法区,或者说用永久代来实现方法区而已。
    3):并不是称为永久代就不会垃圾回收了,这块区域主要是针对常量池的回收和类型的卸载。
       4):运行时常量池:是方法区的一部分
   #直接内存:
    这其实并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。
    直接内存可能在下面一种情况:
      jdk1.4中引入了NIO类,引入了一种基于通道的(channel)和缓冲区(buffer)的io方式,可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆的
    DirectByteBuffer对象对这块区域进行引用。因为能够避免在Java堆和Native堆中来回复制数据,能够在一些场景中显著提高性能。

4、对象创建:
   #内存分配有两种方式:
    1):指针碰撞(Bump the Pointer):指的是内存里有相应大小区域,直接指向就可以了
    2):空闲列表(Free List):没有足够的连续区域,则需要在一张对象表记录并指向。
   #多线程下分配内存两种方式:
    1):使用同步处理,采用CAS配上失败重试的方式保证更新操作的原子性;
    2):在各自的TLAB中分配,只有TLAB用完需要分配新的TLAB时,才需要同步锁定;
   做完分配内存的动作后,就要对分配的内存对象进行必要设置,例如哪个类的实例,对象的哈西码等等,但此时从虚拟机角度来看,一个新的对象已经产生,但从
   java程序角度看还没有,因为所有字段信息都是零。

   #对象的内存布局:
    1)对象在内存中可分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
        #对象头:hashcode,gc分代年龄,所状态标志,线程持有锁等,对象头另一部分是指向类元数据的指针,从而可以知道是哪个类的实例。
         如果是数组的话,还有一个用于记录数组长度的数据。
        #实例数据:各种类型字段内容,无论是从父类中继承下来的,还是子类中定义的,都要记录起来。
        #对齐填充:并不是必然存在的,对象的大小必须是8字节的整数倍,对象头正好是8字节的整数倍,所以实例数据没有对齐是,就要填充。
   #对爱嗯的访问定位有两种方式:
    1):句柄池:也就是变量指向的并不是对象,而是堆中的以个句柄池的某一个指针。
    2):直接指针访问:直接指向堆里面的相应数据。

5、由于hotspot虚拟机中并不区分虚拟机栈和本地方法栈,因此-Xoss参数存在但是无效,栈容量只能有-Xss参数设定。

6、面对gc有三个问题:
    1):哪些内存需要回收?
    2):什么时候回收?
    3):如何回收?
   由于,程序计数器、虚拟机栈和本地方法栈三个随线程生或灭,内存回收都具有确定性,所以不用过多考虑回收问题。因此内存回收主要考虑的是
   方法区和java堆。
   如何确定java对象需要回收?
    1):引用计数法:通过给对象添加一个引用计数来实现,但是很难解决对象之间循环引用的问题。
        例如:
            objA=objB
            objB=objA
            objA=null
            objB=null
        这样,在虚拟机进行gc操作时,仍然会回收objA和objB,所以虚拟机不是采用这种方法。
    2):可达性分析算法:通过一条引用链(Reference Chain)来,当一个对象到GC Root没有任何引用链相连时,证明此对象不可达。
        在Java中,可作为GC Root的对象包括:
        #虚拟机栈中引用的对象
        #方法区中静态类属性引用的对象
        #方法区中常量引用的对象
        #本地方法栈中JNI引用的对象。
   由前面推论,如果在可达性分析中,一个对象并不可达,那是否“非死不可”呢?
   不,要宣告一个对象真正死亡,至少要经历两次标记过程:
    1):没有直接与GC Roots相连接的引用链,则进行第一次标记,并且如果此时对象并没有覆盖finalize()方法或者finalize方法已经被虚拟机执行过,则此时就没必要
    再执行finalize方法。
    2):如果经过判断后,有必要执行finalize,就会把所有有必要执行的放入一个F-Queue,并有虚拟机建立的一个Finalizer线程去执行它,但仅仅是触发执行,而不会
    等待虚拟机执行结束,因为如果finalize方法有死循环等,就要使得队列其他对象进行永久等待。
    3):finalize方法是对象摆脱死亡命运的最后一次机会,此时虚拟机会对F-Queue队列进行第二次标记,如果对象想在这里拯救自己,只需要在这里面建立与任何一个对象
    的对象关联即可,例如:把this关键字复制给某个类变量或对象遍历那个,那第二次标记它时,能够被移出“即将标记”的集合。
    4):不推荐使用finalize自救,运行代价高昂,不确定性大,使用try-finally或者其他方式可以做的更好。
   回收方法区:方法区又称为hotspot虚拟机中的永久代,其实里面还是有垃圾收集的,主要回收两部分内容:废弃常量和无用的类。

7、垃圾回收算法:
   标记清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
    存在问题:
        1:标记清除效率不高
        2:容易产生大量内存碎片
   复制算法(回收新生代):把内存费为大小相等的两块,每次只使用其中一块,当这一块用完了,就将还存活的对象复制到另一块上面,然后把已使用过的内存空间一次性清理。
   标记-整理算法(回收老年代):让所有存活对象都向另一端移动,然后直接清理吊边界以外的内存。
   分代收集算法:就是根据对象存活周期,将内存划分为几块,一般是新生代和老年代,并根据各个年代算法采用最适当的垃圾收集算法。如上。
   GC进行时,必须停顿所有java执行线程。

8、HotSpot垃圾收集的算法实现:
   枚举根节点:在hotspot中,通过一组成为oomap的数据结构来的值哪些地方存在的对象的引用。记录下来哪些位置是引用,在gc扫描时,就可以直接得知这些信息了。
   安全点:程序只有到达安全点时,才能停下来开始gc,因为在gc是所有线程都要停顿,所以如何停顿呢?这里有两种方法:
    1):抢先式中断:首先把所有线程全部中断,如果发现有的线程不再安全点上,就让他“跑”到安全点(基本不使用)
    2):主动式中断:设置一个标志,各个线程主动去轮寻这个标志,发现标记为真就中断挂起,轮询标志的地方和安全点是重合的。
   安全区域(safe region):正常情况如何进入gc的问题是可以通过安全点来解决的,但是如果线程出于sleep或者blocked状态呢?
    此时就需要安全区域来解决了。具体如下,
    当线程进入safe region时,标记自己一进入,因此一旦当jvm要开始gc时,就不会去管标识自己为safe region的线程了。另一方面,当safe region线程要离开
        safe region时,就需要检查,如果gc已经完成了根节点枚举(或者整个gc过程),就让线程继续执行,否则必须等待知道可以安全离开safe region的信号为止。