虚拟机的工作从一个代码的运行开始。
整个过程:Java源文件经过前端编译器(javac或ECJ)将.java文件编译为Java字节码文件(也就是.class文件),然后JRE加载Java字节码文件,载入系统分配给JVM的内存区,然后执行引擎解释或编译类文件,再由即时编译器将字节码转化为机器码。
1、编译期
2、类加载期
类加载器会对定义的类进行加载,此过程又分为四步:
1、认证
2、准备
3、解析
4、初始化
类加载这里要注意,有一个双亲委派机制。
类加载器分层:
启动类加载器(BootstrapClassLoader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(AppClassLoader)
用户自定义类加载器(User ClassLoader):不一定有,定义了才有
注意如上的类加载器并不是通过继承的方式实现的,而是通过组合的方式实现的。而JAVA虚拟机的加载模式是一种委派模式。下层的加载器能够看到上层加载器中的类,反之则不行。类加载器可以加载类但是不能卸载类。
当加载类的时候会层层委托,尽量让最上面的类加载器去加载类,如果上层加载器加载不了就会往下传递,如果都加载不了就说明定义的类有错,会报错。这种机制的设计是为了防止类被加载多次,并且防止有些java虚拟机已经定义好的类(比如String Integer)被我们程序员写代码类名定义的一样而修改(原因如下)。注意**,类只会加载一次**
如何防止被修改:
当JVM加载一个类的时候,下层的加载器会将任务委托给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查完了之后,按照相反的顺序进行加载,如果Bootstrap加载器找不到这个类,则往下委托,直到找到类文件。对于某个特定的类加载器来说,一个Java类只能被载入一次,也就是说在Java虚拟机中,类的完整标识是(classLoader,package,className)。一个类可以被不同的类加载器加载。
3、运行时数据区:
一、java堆:用来存放对象实例(所有对象的内存分配都会放在堆里)堆满了会报OutOfMemoryError异常。
jdk1.8去掉了永久代。堆可以具体细分新生区和老年区,其中新生区又可分为伊甸园区以及幸存者1区(from区)和幸存者2区(to区)。
二、java虚拟机栈:
栈满了会报StackOverflowError异常
1、局部变量表(还会存放java的8种基本数据类型)
2、操作数栈(操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区,也就是用来放运算时的一些值)
3、动态链接:链接到运行时常量池,实现动态链接
4、方法的出口
三、本地方法栈:与虚拟机栈类似,不过虚拟机栈是为虚拟机执行Java方法(字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。该区域同样会报StackOverflowError和OutOfMemoryError异常。
四、pc计数器:保存的是当前执行的字节码的偏移地址,用来保证程序的正常运行顺序。(当线程执行的是本地方法的时候,程序计数器中保存的值是空(undefined);原因很简单:本地方法是C++/C 写的,由系统调用,根本不会产生字节码文件,因此,程序计数器也就不会做任何记录 )。
其中线程私有的是:虚拟机栈,本地方法栈,pc计数器
线程共享的是:堆和元空间(方法区)
只有共享内存才会有GC(垃圾回收)
四、 执行引擎(Execution Engine)
类加载器将字节码载入内存之后,执行引擎以Java 字节码指令为单元,读取Java字节码。问题是,现在的java字节码机器是读不懂的,因此还必须想办法将字节码转化成平台相关的机器码。这个过程可以由解释器来执行,也可以由即时编译器(JIT Compiler)来完成。
GC篇:
首先明确,java程序员无法自己进行手动垃圾回收。并且回收大都发生在新生代,清理Eden区和Survivor区叫Minor GC;清理Old区叫Major GC;清理整个堆空间—包括年轻代和老年代叫Full GC,当full GC都不能释放内存的时候,就会发生OOM。
四种引用:
1、强引用就是指在程序代码之中普遍存在的,类似"Object obj=new Object()"这类的引用,垃圾收集器永远不会回收存活的强引用对象。
2、软引用:还有用但并非必需的对象。在系统 将要发生内存溢出异常之前 ,将会把这些对象列进回收范围之中进行第二次回收。
3、弱引用也是用来描述非必需对象的,被弱引用关联的对象 只能生存到下一次垃圾收集发生之前 。当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。
4、虚引用是最弱的一种引用关系。 无法通过虚引用来取得一个对象实例 。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
垃圾回收的算法:
1、复制算法:主要发生在新生代,把to区的垃圾复制到from区,保证to区永远是空的(from区和to区是可以相互转换的,谁空谁就叫做to区)。
优势:无内存碎片
劣势:浪费空间
2、标记清除算法:扫描对象,标记活着的对象。对没有标记的对象进行清除。
优势:节省jvm空间
劣势:(1)会有内存碎片
(2)两次扫描,浪费时间
3、标记清除压缩:为防止内存碎片产生,将存活的对象压缩,使内存连续。
优势劣势:减少了内存碎片,但是更加耗费时间。
jvm的GC 最后使用的是分代收集算法,也就是新生代用复制算法,老年代用标记压缩算法。
原因:在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
垃圾收集器篇: