一、JVM介绍
JVM(Java Virtual Machine)作为JDK的必需品,是运行java程序是不可少的一个元素。它是java得以跨平台的核心组件,包揽了从java代码的编译、优化到内存管理的多种功能。
Java之所以比C高级,是因为他屏蔽了一些C进行底层操作的功能,例如指针。Java使用自动化的方式进行内存管理,一切与内存有关的琐事都交予JVM来管理。使得开发人员无需使用诸如:释放指针(delete p)的操作,也不必担心会导致内存泄露,尽管这使得java在效率上不能够与C相媲美 ,但他使得开发效率大大提高。
二、JVM运行时数据区
首先需了解JVM在运行时,需要维护的几个数据区。
共享or私有 | 数据区名 | 介绍 |
线程共享区域 | 堆 | 被所有线程共享的一块区域,用于分配对象的实例。例如当执行new Object()时,就在堆上面创建一个Object的实例。 |
方法区 | 存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。可以理解为存储一些类的结构信息,这些信息一般是描述一个类的结构,而没有实质性的数据(数据存放在堆了)。 | |
线程私有区域 | 虚拟机栈 | 在每个方法被调用时,会创建一个栈帧(入栈),而每个方法调用完成,便对应一个出栈的过程。递归调用如果层次太深,则会出现栈溢出,因为调用了太多方法(入栈),却没有返回(出栈),这样更容易理解。 |
程序计数器 | 当前线程所执行的字节码的行号指示器,他告诉我们接下来要执行的指令地址。与汇编中的PC概念相同。 | |
本地方法栈 | 与虚拟机栈类似,只是虚拟机栈执行java方法。而本地方法栈则为虚拟机使用到的Native方法服务(如使用JNI调用的一些非java语言提供的服务)。 |
Java虚拟机栈中的局部变量表存放了编译期可知的各种基本数据类型(boolean、int、char…),对象引用(Reference类型)。对象引用类型指针指向的是Java堆中的对象实例数据。例如Object o = new Object(),o是在栈中,其指针指向了Java堆中的实例数据new Object()的数据。而堆中存放的仅仅是对象的数据,而无数据结构,相应的结构存储在方法区当中,故堆中的实例数据中,还有一个指向其对象类型的指针,这样,就完美了。这便是通过直接指针访问对象方式(还有通过句柄访问对象的方式)。如下图所示。
三、JVM的GC(Garbage Collection)
1、判断对象“存活”or“死亡”的方式?
要进行垃圾回收,必须先知道堆中(其他区域较少进行回收栈会自行出栈、方法区的数据较少变动),哪些对象是“死亡”的。有一种流行的方式,是使用“引用计数法”来判断,若引用计数器为0时,则表明该对象不会被使用,则可以回收。但这种方式无法解决对象间相互循环引用的问题,故JVM中并不使用该方式。
JVM中,使用“根搜索算法”,它从一系列的名为“GC Roots”的对象作为起点向下搜索,若某个对象不可到达,则判定为可回收的对象。
2、可回收==回收?
当一个对象被判定为可回收时,并非就是“非死不可”的。死亡前,有要进行“2次标记”。
第一次,若该对象有finalize()的方法,则会被加入一个名曰F-Queue的队列中,并在低优先级的线程中执行。若无,或者finalize()已被执行过,则筛选出该对象。
第二次,若在执行该对象的finalize()方法的时候(该方法在对象存活期间,只会执行一次),他与引用链上的某对象相关联,则成功逃脱。否则,第二次又筛选中,判死刑了。
3、如何进行内存的回收?
主要有3中方法:
1)标记-清除算法。先标记,将所有的需要回收的对象进行标记(使用上述的方式)。再清除,将标记的对象空间回收。
缺点:效率不高,并且会导致内存碎片。
2)标记-整理算法。也是先标记,然后将存活对象按指针顺序移动,就像在顺序表中删除元素类似。最后,将剩余的内存释放。
3)复制算法。也是先标记。但他会将可用内存事先划分为两个等大的区域,进行整理时,将一个内存区域中存活的对象移至另一个区域中。
缺点:内存利用率低。(改进的算法可用弥补这一不足)