hotSpot VM是最主流的JVM。
1.JVM执行流程:
程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area) ,而字节码文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(ExecutionEngine)将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。
2.JVM运行时数据区
JVM根据需要,把从操作系统申请到的一整个内存空间,划分为不同的部分,每个部分都有各自不同的功能。
具体划分:
2.1 堆(线程共享)
堆的作用:程序中创建的所有对象都在保存在堆中。
2.2 java虚拟机栈(线程私有)
Java 虚拟机栈的作用:Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是 Java 方法执行的内存模型(方法之间的调用关系):每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
2.3 本地方法栈(线程私有)
本地方法栈:给调用native方法准备的栈空间,主要用来存储native()方法间的调用关系。
2.4 程序计数器(线程私有)
用来记录当前线程执行的行号。
2.5 方法区/元数据区(线程共享)
用来存储被虚拟机加载的类信息、常量、静态变量。
考点:给一段代码,问某个变量在哪个区域?
原则:
局部变量:栈
普通成员变量:堆
静态成员变量:方法区/元数据区
类加载:
加载:把.class文件找到,打开文件,读文件,将文件内容读取到内存中,加载完成后得到类对象;
验证:检查.class文件的格式是否正确
准备:给类对象分配内存空间(先在元数据区占个位置),将静态变量设置0值。
解析:初始化字符串常量,将符号引用转变为直接引用。
初始化:调用构造方法,进行成员变量初始化,执行代码块,静态代码块,加载父类;
符号引用和直接引用的理解:
字符串常量,需要一块内存空间来存储这些字符的实际内容,另外还需要一个引用来指向该内存空间,在类加载之间,字符串常量是在.class文件中的,此时这个“引用”记录的并不是字符串常量的真实地址,而是记录它在文件中的偏移量,此时就是符号引用,只有当类加载完成,字符串常量被真正赋予了内存空间,此时的引用才会获得真正的地址,变为直接引用。
一个类,什么时候会被加载呢?
Java中的类不是程序一启动就全部加载,而是用到那个类就加载那个类(类似于懒汉模式),那怎样算用到该类了呢?
- 构造类的实例
- 调用这个类的静态方法/静态属性
- 加载子类时就会先加载父类。
加载完成后,后续使用不会重复加载。
双亲委派模型(经典问题)
双亲委派模型描述的就是加载过程中,找.class文件的过程。
上述加载器如何配合工作?
垃圾回收机制GC
1.什么是内存泄漏和内存溢出?
内存泄漏:是指用户向系统申请内存,只申请不归还,可能是用户自己导致部分内存的引用地址丢失,从而导致部分内存无法访问,也没有归还给系统回收,因此造成内存泄漏。
内存溢出:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM(out of memory),即所谓的内存溢出,简单来说就是自己所需要使用的空间比我们拥有的内存大内存不够使用所造成的内存溢出。
由于上述问题的存在,java中引入了GC帮助程序员进行内存的回收,GC主要是针对“堆区”进行回收,另外,GC是以“对象”为单位进行回收。
GC的实际工作过程:1.找到垃圾/判定垃圾;2.对内存进行释放
那么,怎样判定一个对象是否是垃圾呢??
1.引入计数(不是Java的做法)
给每个对象分配一个计数器,每当有引用指向该对象时,计数器+1,每次该引用被销毁,计数器就-1.
优点:简单有效。
缺点:
1)空间利用率低,造成空间的浪费。
2)存在循环引用问题:
2.可达性分析(Java的做法)
思路:通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的。
垃圾回收算法:
- 标记清除
算法分为"标记"和"清除"两个阶段 : 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
算法的不足:
1)效率问题:标记和清除这两个过程的效率都不高
2)内存碎片化问题:标记清除后会产生大量的碎片化的内存空间。
- 复制算法
"复制"算法是为了解决"标记-清理"的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使 用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后 再把已经使用过的内存区域一次清理掉。
3.标记-整理算法
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。标记-整理算法在对“对象”进行标记后,并不进行清除,而是类似于顺序表中插入元素,对存活的对象进行搬移。
4.分代算法(现在常用)
分代算法是通过区域划分,实现不同区域和不同的垃圾回收策略,从而实现更好的垃圾回收。
该算法是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每 次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。