想把最近几天学的关于JVM的相关知识点,记录下来,一方面是加深自己的印象,也是在此做个记录,方便自己下次回顾。
JVM相关
JVM虚拟机内存空间
线程共享区
- 线程共享区是被所有线程共享的一块内存区域,在JVM启动时创建,关闭时销毁,线程共享区分为:虚拟机堆、方法区。
虚拟机堆
此空间是用于存放对象实例的(也可以理解为由new关键字创建的对象都存放在此内存空间中),此内存空间也是JVM内存回收的重点区域,且目前VM使用的回收算法为分代收集算法。因此,从GC的角度来划分,可以将此空间细分为:
-
新生代(使用复制算法)
在新生代中又可以细分为以下几个区域:- Eden区
此区域主要存放的是新创建的对象,若对象过大会直接放入老年代中。当此内存空间不够的时候会触发一次Minor GC,进行一次回收。 - from survivor
此区域存放的是上一次Minor GC后存活的对象,是在下一次Minor GC的时候被扫描。 - to survivor
此区域是保留一次GC时的幸存者,且From survivor 区域与To survivor 两个区域是可以相互转换的。
可能光看上面的解释比较抽象,我们来看一下Minor GC具体的回收过程就会比较好理解以上的几个区域了。
Minor GC 的回收过程:- Eden 区域内存空间不够,触发Minor GC。
- 将Eden区和from survivor区中存活的对象放到To survivor 区域中,且对象的年龄会+1。
注:如果空间不够会直接将多余的对象存放到老年代中;如果对象的年纪到达了老年代的年纪也直接存放到老年代中。 - 将To survivor与from survivor区域进行互换,让它成为下一次Minor GC的扫描区域。
- Eden区
-
老生代(使用标记-清理或标记-整理算法)
此区域一般存放的是生命周期较长的对象,默认是当对象的年纪到达15的时候会存放到在这里。当此区域内存空间不足时会触发Major GC,在Major GC 执行之前会先执行Minor GC,如果执行的时候有多余的对象要存放到老年代中,则会出现outOfMemroy异常。
我觉得新生代老年代主要也是根据对象的存活周期不同来划分的,新生代的对象存活周期较短,因此会产生大量对象死去,少量存活,使用复制算法进行回收的效率更高,因为只要复制少量存活的对象就可以;而老生代对象的存活周期较长且没有额外的空间可以分配担保所以就使用标记-清理或标记-回收算法。
方法区(永久代)
在java8之前方法区也可以称为永久代,里面存放的是被虚拟机加载的类信息、常量、静态变量,之后则变成元数据空间,加载的类信息存放在本地内存中,常量以及静态变量存储在堆当中。当方法区无法满足内存分配的时候则会抛出outOfMemroy异常,Java8 之后的内存空间大小由本地实际内存大小控制。
线程私有区
- 线程私有区,表示每一个线程中都会有的区域,且多条线程(方法)之间互不影响。他们的生命周期是随着方法的调用而创建,方法的结束而销毁的。
虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的时候都会创建一个栈帧来存储局部变量表、操作数帧、动态链接、方法出口等信息。每个方法从开始执行到结束的过程都会有对应的进栈、弹栈的过程。
本地方法栈
本地方法栈和虚拟机栈的作用是类似的。只不过,本地方法栈是为虚拟机使用Native方法服务,而虚拟机栈是为虚拟机执行的Java方法服务。
程序计数器
程序计数器中存储的是当前的指令地址,且这是唯一一个不会抛出OutOfMemroy的一个内存区域。
JVM垃圾回收算法
- 垃圾回收(GC) 回收的是对象在内存中的所占用的空间。
分代收集算法
现在VM中基本采用的是分代收集算法,就是根据不同区域的对象存活周期来将内存进行划分,这样就可以根据各个年代的特点采用最适当的收集算法。
标记-清理算法
标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。此方法存在的缺点:
1. 效率不高(标记和清除两个过程的效率都不高)
2. 内存空间碎片化
标记-整理算法
标记-整理和标记-清理算法基本上是一样的,但是它在标记完成之后,不是直接清理而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存区域,这样就避免出现空间的碎片化。
复制算法
将内存空间划分为大小相等的两块内存区域,且只使用其中一块,当这一块的内存用完时,就将内存中存活的对象复制到另一块上面,然后把使用过的内存空间一次性清理掉,这样就解决了效率问题以及内存碎片的问题。
新生代使用的算法就是这个,但是Eden区与survivor 区默认的内存空间大小分配是8:1。
如何判断对象是否存活
堆当中存放这很多的对象,在进行回收前,需要知道对象是否存活,那它是如何进行判断的呢?
引用计数法
实现方法:
给对象一个引用计数器,有引用则+1,引用失效-1,当引用计数器为0时,表示该对象不可能再被使用,可以被回收。
缺点:
无法解决对象之间相互循环引用的问题。
可达性分析法
通过一系列的GC Roots的根节点出发向下搜索,当一个对象从任何GC Roots出发都不可达的时候,则认为该对象不存在任何引用。不可达对象不等价于可回收对象。
标记过程
从不可达对象转换成可回收对象需要经过两次标记。
第一次标记:
对象判定为不可达对象的时候就会标记一次,标记后会判断此对象是否有必要执行finalize()方法【不必要执行的情况:对象并未覆盖finalize() 方法;已经执行过finalize() 方法】
注:任何对象的finalize() 方法都只会被系统调用一次。
第二次标记:
当对象有必要执行finalize() 方法的时候,则会将对象放入F-Queu 队列中,会由虚拟机自动出发finalize() 方法(不保证执行完全),如果不想被回收只需要与引用链上的任一对象关联既可。如果还是没有关联则会被第二次标记。
两次标记之后对象就会被判定成可回收对象,则会被GC回收清理掉。
四种引用类型
强引用
不管咋样都不回收,容易造成内存溢出。
软引用
内存足够不回收,不够就回收。
弱引用
不管内存够不够都进行回收。
虚引用
对生命周期没有影响,只会在被回收的时候得到一个消息。
后记:以为很难的东西,当你认真去学了,发现其实也就是这么一回事,也知道自己了解到的知识真的太少连沧海一粟都算不上,还是要加油多学习,努力成长,不放弃!!!