JVM
什么是JVM
java虚拟机,我们所写的代码就是在jvm上运行,我们平时书写的代码是.java文件,然后经过编译为.class字节码文件,现在我们想要运行它,首先需要启动JVM虚拟机,JVM虚拟机本身也是一个程序(用C书写的)
JVM由哪几部分组成
类加载器
我们经过编译后的字节码文件还是存放在磁盘的一个个文件,类加载器帮我们将一个个字节码文件加载到虚拟机的内存中
运行时数据区
JVM在运行时所管理的内存区域,我们把它叫做运行时数据区,它划分了几个部分,分别存储不同的数据
执行引擎
会将java字节码指令翻译成计算机所识别的指令,然后交给CPU去执行,这也是java实现跨平台的原因,不管什么系统jvm都会翻译为对应计算机所识别的指令,交给CPU去执行。
本地方法接口
由native修饰的方法,没有方法的实现,它是由其他语言实现的,所以这些方法就需要本地方法接口来调用,也叫JNI
运行时数据区
- 方法区(jdk1.7之前也被称为永久代,jdk1.8也被称为元数据空间。),存的是一些字节码对象,类的信息,或者是一些常量信息,还有一些static修饰的静态信息,这些信息存活的时间普遍比较长。也能进行垃圾回收,回收效果不是特别好,
- 堆:对象new出来时,会被放到堆中,是5块区域里最大的一块区域,java中,几乎所有的对象都是存储在堆当中的,它是垃圾回收的一个主要的区域
- Java虚拟机栈,存放的是方法相关的数据,栈是先 进后出,我们在运行一个方法的时候,等于就启动了一个线程,此时,就会拥有它的虚拟机栈,和这个方法相关的数据,就会被封装成一个对象,当我们执行某个方法的时候,jvm把这个方法封装成一个栈针入栈, 栈针里面有局部变量表,这个局部变量表里面就是这个方法有哪些局部变量地址,同时,方法的入口,方法的出口,操作的内容,都会存放在这个栈针里,一个方法执行完毕,栈针就会移除出栈,递归运行方法,可能会导致栈溢出异常,当然,这个栈的大小也是可以调整的。 JVM参数 -Xss128k
- 本地方法栈:刚刚Java虚拟机栈是为Java方法所服务的,本地方法栈是为Native修饰过的方法所服务的
- 程序计数器:在任何一个时刻,一个内核只能执行一条线程中的指令,程序计数器是为了切换线程后能恢复到正确的执行位置,每条线程都一个独立的程序计数器,如果线程正在执行的方法是Java方法,计数器记录的是虚拟机字节码的指令地址,如果Native方法,计数器为空,这一块区域绝对没有内存溢出的风险
线程共享的区域
- 方法区
- 堆
只要jvm已启动,这2个区域就存在了,所有其他的线程都能访问这2个区域的数据
线程私有的区域
- Java虚拟机栈
- 本地方法栈
- 程序计数器
只要创建了一个线程,就会划分出这3个区域,这些线程都可以访问方法区和堆的数据
垃圾回收
什么样的对象为垃圾
用可达性分析算法,通过GC Roots的对象作为起点,从这些节点开始向后搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则此对象是不可达的,会判断为可回收的对象,如下,可作为GC Roots节点的对象
- 虚拟机栈中(栈针中的本地变量表)引用的对象,通俗的来讲就是正在执行的方法中的变量所引用的对象
- 方法区中静态变量所引用的对象
- 方法区的常量所引用的对象
- 本地方法栈中所引用的对象
垃圾回收算法
标记-清除算法
最基础的收集算法,过程中分为标记和清除2个阶段,首先标记出需要回收的对象,之后由虚拟机统一回收已标记的对象。这种算法有以下2点不足:
- 效率问题,标记和清除的效率都不高
- 空间问题,对象被回收之后会产生大量不连续的内存碎片,当需要分配较大对象时,由于找不到合适的空闲内存而不得不再次触发垃圾回收动作
复制算法
将内存划分为大小相同的2部分,每次只使用其中一半,当第一块内存用完了,就把存活的对象复制到另一块内存上,然后清除剩余可回收的对象,这样就解决了内存碎片问题。简单高效。但它的缺点很明显:
- 它浪费了一半的内存空间
- 如果对象的存活率很高,假设100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间也比较长。
标记整理算法
2个阶段:
- 标记:它和标记-清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
- 整理:移动所有存活的对象,且按照内存地址依次排列,然后将末端内存地址以后的内存全部回收。
不难看出,标记-整理算法不仅可以弥补标记-清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价,可谓是一举2得
分代收集算法
把堆分成新生代和老年代2块区域,每次新创建的对象,会进入新生代,并且有一个GC 年龄,新生代的对象存活率比较低,采用的是复制算法,新生代中又划分为3个区域,最大的Eden区,还有2块幸存者区域,Eden区经过垃圾回收,如果还存活的对象,会进入到幸存者区域,并且GC 年龄 + 1,然后第2次垃圾回收,Eden区,和刚刚的幸存者区都会进行,把存活的对象复制到另一块幸存者区域中。进入到老年代中的对象有以下3个途径,老年代使用的垃圾收集算法是标记-清除 加 标记整理
- 在新生代3个区域,反复收集,GC 年龄到达15之后,还存活,直接进入
- 占用较大的对象,如果幸存者区域放置不下,会进入老年代
- 可以配置参数,超过这个参数大小的对象会进入老年代