概述
c语言的垃圾回收是人工回收,他们可控性强,java语言垃圾回收是自动的,可控性差,有时候会出现内存溢出的情况(内存溢出: 也就是jvm分配的内存对象过多,超过了最大可分配内存的大小)
提到垃圾回收机制,就要提到Gc方法,System.gc()这个方法就是来调用垃圾收集器的,在调用时,垃圾收集器将运行已回收但未使用的内存空间,它将尝试释放被丢弃对象占用的存,System.gc()方法有一个免责声明,它和finalize()方法一样不能保证马上能执行,有时候程序执行的比较快,到结束进程了都没有执行Gc,他只是告诉垃圾回收器要进行垃圾回收,但他不能保证完成任务.
在了解垃圾回收机制之前,我们需要对jvm有所了解
1.jvm的作用:
首先通过编译器把java代码转化成字节码,类加载器再把字节码加载到内存,将其放在运行时数据区(RuntimeData area)的方法区内,而字节码文件只是jvm的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎,将字节码翻译成底层系统指令,再交给CPU去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface) 来实现整个程序的功能
2.jvm内存模型(java栈,native栈,程序计数器(线程独占),堆,方法区(线程共享))
java栈: 属于线程私有区,存储栈帧
native栈:主要用于对接其他语言
程序计数器: 计算java栈执行的行数
方法区: 常量池,不可变的常量数据
堆: 存储所有对象,gc的主要回收空间
JVM区域总体分两类,heap(堆)区和非heap区。
heap区又分为:
Eden Space(伊甸园,新生代)、 Survivor Space(幸存者区)、 Old Gen(老年代)。 非heap区又分:
Code Cache(代码缓存区); Perm Gen(永久代); Jvm Stack(java虚拟机栈); Local Method Statck(本地方法栈);
Eden Space字面意思是伊甸园,对象被创建的时候首先放到这个区域,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域
关于垃圾回收我们需要知道三点
第一: jvm会在什么时候进行垃圾回收?
1.在CPU空闲的时候;
2.在堆内存存储满的时候;
3.主动调用system.gc()尝试进行回收;
第二: 在进行垃圾回收之前,我们需要知道在java中怎么判断一个对象是否是垃圾?
很简单,没有任何引用的对象,就是一个垃圾
那么我们怎么判断一个对象有没有"引用"呢
一.引用计数法
二.可达性分析
一.引用计数法
1.判断对象的引用数量,来决定对象是否可以被回收
2.每一个对象实例都一个引用计数器,如果对象被引用,那么引用计数器+1,引用完成则-1
3.任何引用计算为0的对象实例都可以被当做垃圾回收
但这种简单的算法没有被jvm所引用,原因就是它不能解决对象之间循环引用的问题
看下面这段代码
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
class MyObject{
public Object object = null;
}
最后两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。
2.可达性分析算法
通过判断对象的引用链是否可达来决定对象是否可回收
程序把所有的引用关系看作是一张图,通过一系列名为GC Roots的对象作为起始点,从这些节点开始往下搜索,搜索所有走过的路径,就是引用链,如果,当一个对象到GC Roots之间,没有任何引用链相连(也就是GC Roots到这个对象不可达)那么证明这个对象是不可用的,是可以被回收的。
第三:如何回收垃圾?
那么这个地方就是我们说的垃圾收集器,也就是垃圾收集算法
算法又有四种: 标记清除算法,复制算法,标记整理算法,分代收集算法
标记清除算法:
对所有需要回收的对象,进行标记,然后统一进行回收.
优点:就是简单,缺点就是效率问题,最大的缺点就是空间问题,标记清除后会产生大量不连续的内存碎片,当程序在以后的运行中需要分配较大的对象时,无法找到足够的连续内存,而造成内存空间浪费
复制算法:
就是将内存按容量大小分为大小相等的2块,每次只使用其中一块,当这一块的内存用完了,将还存活的对象复制到另一块上面,然后对原先的那块内存空间一次性清理掉,这样使的每次都是对其中的一块内存进行使用,内存分配时也就不考虑内存碎片的问题了,缺点就是赋值算法在对象存活率较高是,就要进行较多的复制操作,效率就会变低,更关键的是将内存缩小了一半.
标记整理算法:
和标记清除算法很相似,有一点不一样,标记清除算法对存活的对象不做任何处理,标记整理算法它对存活的,不存活的对象都进行处理,所以不会造成内存碎片
分代收集算法:
目前jvm使用最多的一种算法,比较智能的一种算法,他会根据上面的三种算法就行垃圾回收处理
那么现在的重点就是它怎么自动根据场景进行选择,这个具体的场景又是什么?
其实这个具体的场景指的就是针对jvm的哪一个区域,jdk1.7之间分为3个区域,
新生代、老年代、永久代(在jdk1.8以后其实就废除了永久代,但是他有一个跟永久代很像的,也就是元空间,
废弃永久代的原因:由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryErroy。元空间的本质和永久代类似。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。也就是不局限与jvm可以使用系统的内存。理论上取决于32位/64位系统可虚拟的内存大小。)
我们通过对新生代、老年代、永久代的了解后,可以得出结论
在新生代中,每次垃圾收集时都有大批对象死去,只有少量存活(因为新生代gc比较频繁、对象存活率低),那么这里我们就可以使用复制算法,只需要付出少量存活对象的复制成本就可以完成收集
在老年代中,因为对象存活率较高,没有额外空间对他进行分配担保,那么就需要使用标记清除或者标记整理算法
*.一个对象的这一辈子*
我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
年轻代中的GC
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
最后
以上就是高兴朋友为你收集整理的关于java垃圾回收机制的全部内容,希望文章能够帮你解决关于java垃圾回收机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复