JVM是Java Virtual Machine(Java虚拟机)的缩写
运行过程
JAVA源代码通过编译器变成二进制字节码,字节码再通过解释器转行成机器码。
源代码 ----- 编译器 -----> 字节码
字节码 ----- 解释器 -----> 机器码
好处:
- 一次编写,到处运行
- 自动内存管理
一、JVM内存结构
JVM主要是由堆、栈、方法区、本地方法栈、程序计数器组成
方法区储存类信息,常量。静态变量等数据,是线程共享区域
栈又分为Java栈和本地方法栈,用于方法的执行,属于线程私有区域
1、本地方法栈
本地方法栈是保存native方法进入区域的地址
native方法是指Java代码调用非Java代码的接口,该代码的实现由非Java语言实现。该方法不提供实现体,例如:
private native void test();
2、程序计程器
程序计数器是计算机处理器中的寄存器,它包含当前正在执行的指令的地址(位置)。当每个指令被获取,程序计数器的存储地址加一。在每个指令被获取之后,程序计数器指向顺序中的下一个指令。当计算机重启或复位时,程序计数器通常恢复到零。
作用
记录下一条JVM指令的执行地址
特点
- 线程私有
- 不会出现内存溢出
3、方法区
方法区储存类信息,常量。静态变量等数据,是线程共享区域
4、栈
栈又名堆栈,每个线程运行时所需要的内存,被称为栈,由多个栈桢组成。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
上溢和下溢都被称为栈溢出
5、堆
堆由新生区、老年区,永久区组成。垃圾回收主要在新生代(伊甸园区)和老年代。如下图所示:
- 新生代:由伊甸园(Eden)、幸存区0(ServivorFrom)、幸存区1(ServivorTo)组成,当三个区域都满了,会触发一次重量级GC
- 伊甸园:是java新生的对象存放的地方,当该区域内存满了会触发一次轻量级GC,对新生代进行一次垃圾回收
- ServivorTo:保留一次GC存活的对象
- ServivorFrom:上一次GC的存活者,转移到该区域进行扫描
- 老年代:当一些对象经历了多次重量级GC(一般是15次),就会被转移到老年代
- 永久区:内存的永久保存区域,主要存放Class和Meta(元数据)的信息。在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。储存的是java运行时的环境或者类信息,该区域不存在垃圾回收。
内存溢出时诊断的工具
jvisualvm、jconsole
二、垃圾回收(GC)
计算哪些是垃圾的算法:引用计数法、可达性分析
GC的过程的算法:复制算法、标记清除算法、标记清除整理算法
1、引用计数法
- 含义:该算法是给每个对象记录被引用的次数,如果该对象被引用,计数器就+1,如果取消引用,该计数器就-1,如果该对象的计数器为0,那么说明该对象可以被回收。
- 举例:如下述代码所示:test()方法中创建了一个对象,当main方法调用test方法时,该对象的计数器便会+1,当方法结束时,该对象的计数器便会-1
public static void main(String[] args) {
test();
}
public static void test(){
Test2 test2 = new Test2();
}
- 问题
1、无法处理循环引用的问题
2、每创建一个对象便会进行一次加减计数,导致性能开销较大
2、复制算法
复制算法主要是针对幸存0区(ServivorFrom)和幸存1区(ServivorTo)
GC过程
- 当我们进行垃圾回收时,发现form区中有8个对象,其中有3个已经死亡。
- 垃圾回收时,将存活对象移到to区
- 清除from区中的死亡对象
- 这时,form区就变成了to区(from区和to区并不是固定的,而是随着每一次的垃圾回收不断的变化,识别的方法主要是谁空谁就是to区)
优劣势 - 优势:比较高效
- 劣势:to区永远为空,损失了一半的可用内存。并且form内存空间碎片化,可用空间并不是一整块
3、标记清除算法
过程
- 标记:扫描内存,将存活对象标记出来
- 清除:将未标记的垃圾对象清除
优劣势:
优势:不需要额外空间
劣势:两次扫描,浪费时间,产生内存碎片
4、标记清除压缩算法
多了一次扫描,解决了标记清除算法中内存碎片的问题
过程
- 标记:扫描内存,将存活对象标记出来
- 清除:第二次扫描,将未标记的垃圾对象清除
- 整理:第三次扫描,整理内存
三、总结
- 年轻代存活率低,适合用复制算法
- 老年代存活率高,适合标记清除和标记压缩