JVM历史了解
1996年 Classic VM发布,到2002年jdk1.4 Classic VM退出历史舞台 NIO问世。
2004年jdk1.5发布产生重要改变,增加 范型、注解、装箱、枚举、可变长参数,都是我们现在常用到的。
2011年jdk1.7发布NIO2即现在的AIO等
2014年jdk1.8 Lambda表达式问世。CurrentHashMap升级 、对IO做了升级等。
JVM运行流程
java程序经过一次编译之后,将java代码编译为字节码也就是class文件,然后在不同的操作系统上依靠不同的java虚拟机进行解释,最后再转换为不同平台的机器码,最终得到执行。
加载
JVM基本结构
如下图
Java虚拟机只与“Class文件”关联,与语言和文件的来源无关,如:可以通过Java、Ruby生成一个class文件,甚至可以按照“Class文件”的文件格式自己手动编写一个class文件,但是这个文件有特定的结构。
从这个结构不难看出,class文件被jvm装载以后,经过jvm的内存空间调配,最终是由执行引擎完成class文件的执行。当然这个过程还有其他角色模块的协助,这些模块协同配合才能让一个java程序成功的运行,下面就详细介绍这些模板,它们也是后面学习jvm最重要的部分。
寄存器:每个线程都又一个寄存器,指向下一个指令地址。
方法区:类加载时,保存类的信息(类型的常量池、字段、方法信息、方法字节码信息)
JAVA堆:应用系统对象都保存在堆中,所有线程共享java堆信息,对分代GC而已堆是分代的如下
JAVA棧:属线程私有,棧由一系列帧构成,帧保存一个方法的局部变量信息、操作数棧、常量池指针,每一次方法调用创建一个帧并压棧
小对象在没有逃逸情况下一般可以直接在棧上分配,可自动回收,减轻GC压力,大对象或者逃逸对象无法在棧上分配。
堆棧方法区交互
内存的分配和回收策略
jvm已经发展处三种比较成熟的垃圾收集算法:1.标记-清除算法;2.复制算法;3.标记-整理算法;4.分代收集算法
标记-清除算法
这种垃圾回收一次回收分为两个阶段:标记、清除。首先标记所有需要回收的对象,在标记完成后回收所有被标记的对象。缺陷是这种回收算法会产生大量不连续的内存碎片,当要频繁分配一个大对象时,jvm在新生代中找不到足够大的连续的内存块,会导致jvm频繁进行内存回收(目前有机制,对大对象,直接分配到老年代中)
复制算法
这种算法会将内存划分为两个相等的块,每次只使用其中一块。当这块内存不够使用时,就将还存活的对象复制到另一块内存中,然后把这块内存一次清理掉。这样做的效率比较高,也避免了内存碎片。但是这样内存的可使用空间减半,是个不小的损失。
优势
这使得每次都是只对整个半区进行内存回收,内存分配时也不用考虑内存碎片等问题(可使用"指针碰撞"的方式分配内存),实现简单,运行高效;
缺陷
空间浪费,可用内存缩减为原来的一半,太过浪费;
效率随对象存活率升高而变低,当对象存活率较高时,需要进行较多复制操作,效率将会变低
标记-整理算法
这是标记-清除算法的升级版。在完成标记阶段后,不是直接对可回收对象进行清理,而是让存活对象向着一端移动,然后清理掉边界以外的内存
优势:
不会像复制算法,效率随对象存活率升高而变低,老年代存活时间长没有可担保空间,一般不能直接选用复制算法算法,而选用标记-整理算法。
不会像标记-清除算法,产生内存碎片,清除前,进行了整理,存活对象都集中到空间一侧。
缺陷:
除像标记-清除算法的标记过程外,还多了需要整理的过程,效率更低;
分代收集算法
当前商业虚拟机都采用这种算法。首先根据对象存活周期的不同将内存分为几块即新生代、老年代,然后根据不同年代的特点,采用不同的收集算法。在新生代中,每次垃圾收集时都有大量对象死去,只有少量存活,所以选择了复制算法。而老年代中因为对象存活率比较高,所以采用标记-整理算法(或者标记-清除算法)
优势:
可以根据各个年代的特点采用最适当的收集算法;
缺陷:
仍然不能控制每次垃圾收集的时间;
几乎所有商业虚拟机的垃圾收集器都采用分代收集算法;
如HotSpot虚拟机中全部垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1(也保留);