学习Java的过程是比学习C++和C来得轻松地多,从某种程度上来讲,和JVM自带的垃圾回收机制有关,在C或者C++中创建完数组后需要手动来进行释放,一不小心就会发生内存的泄露。JVM帮助我们自动回收不用的内存,当然,这个是以效率来换的。
JVM如何判断某个实例是否应该被回收掉呢?有两种方式:
一、引用计数法:当某个实例被一个对象引用时,那个实例的计数器就会加1,当实例中的计数器为0时,说明没有被对象引用,这个时候JVM就可以将这个实例的空间释放掉。但这样有一个问题,如果两个实例相互引用,而这两个实例又都不会再被用到了,采用这种方法这两个实例是释放不了的。
二、可达性分析方法:这种方法就比较高级了,它从main开始,为所有的实例建立一条可达的通道,如果某个实例处于不可达的范围,那么该实例就可以被释放掉。JVM在释放内存时会给即将被释放的实例一个自救的机会,如果实例复写了finalize()方法,那么实例会被放到一个队列中,执行finalize()方法,如果自救没有成功,那么就直接释放掉它所占用的内存。
Java中引用对象的方式有4种,分别是强引用、软引用、弱引用以及虚引用,下面是他们的一些区别:
1、强引用:使用频率最多的一种方式,正常的使用应该都属于强引用,不管系统的资源有多紧张,强引用的对象都不会被释放。
2、软引用:使用方法是SoftReference<Person> p = new SoftReference<Person>(new Person("Rain"));在系统资源紧张时,软引用的对象会被回收。
3、弱引用:使用方法是WeakReference<Person> p = new WeakReference<Person|>(new Person("Rain"));弱引用是在内存回收时肯定会被回收的。
4、虚引用:这个比较特殊,虚引用是和ReferenceQueue结合使用的,它不能被单独使用,当被虚引用标注的实例被回收后,实例就被放入队列中,以此来追踪实例被垃圾回收的状态。
关于JVM中的垃圾回收算法和方式,先来了解一些基本概念:
1、串行回收:相当于只有一个线程在回收资源。
2、并行回收:是多个线程在协同工作,回收资源。
3、并发执行:指当回收的工作开始时,所有的Java应用程序都停止执行,等待回收工作的完成。
4、标记-压缩:指在回收时标记出所有的待回收的空间,然后通过复制的方式,将所有的分配空间转移到一端。
5、标记-清除:指标记出待回收的空间后直接清理掉。
6、复制式:即将可用内存空间一分为二,每次只使用其中的一份内存空间,在垃圾回收时,将内存中的可达对象全部复制到另外的一个空间,然后对本空间进行一次性回收。
JVM在堆内存中采用的是分代回收的策略,这种策略是上述回收算法和方式的一个组合,JVM将内存分为新生代、老生代以及永久代的堆。
年轻代中有三个区,分别是一个Eden区和两个Survivor区。新生成的对象是放在Eden区中,当Eden区满时,将Eden区中存活的对象复制到其中的一个Survivor区中,当这个Survivour区满了,则将存活的对象复制到另外一个Survivor区中,当另外一个区也满时,则会将其中存活的对象转移到老生代区中。
老生代区中存放的是一些生命周期较长的对象。
最后永久代中存放的是一些静态文件。
JVM中垃圾回收机制在下列几种情况下触发:
1、Eden区满,则会触发Scavenge GC,这个回收只会影响到新生代区,不会影响到其他区。
2、当永久代、老生代满或者显示调用System.gc()时,会触发Full GC,这个效率比较低,要尽量避免。