深入java----垃圾回收
Java和C++之间有一睹内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人想出来。-------《深入理解JVM虚拟机》
补充:在无用对象判断这两种方法中,都是靠对象的引用进行判断对象是否无用。又因为在某些情况下我们不一定需要回收对象,因此在jdk1.2后,java对引用进行了扩充,将引用分为4类“强引用”“软引用”“弱引用”“虚引用”。
补充2:对于方法区中的垃圾回收,回收效率远远低于堆,在此区域中主要回收的内容为:无用的类以及废弃的常量。废弃常量的回收与堆中非常类似,但是无用类的回收将麻烦许多。
补充3:四种引用类型:强引用,垃圾回收不回收被引用对象。软引用,内存溢出之前列入二次回收范围。弱引用,生存到下一次垃圾回收。虚引用,对象被收集时收到一个系统通知。
下面详细说说四种回收算法。
- 标记-清除:分为两个阶段,首先是进行无用对象判断,进而标记出所有需要回收的对象,标记完成后进行统一的对象回收。此算法为回收算法最基础算法,但是其有两个缺点,效率不高和内存碎片太多。
- 复制:将内存分为三个区域,一块大区域eden与两块小区域survivor,每次使用只使用eden和一块survivor,回收时将eden和survivor中存活的对象复制到未使用的survivor上,然后将之前使用过的区域清除。当survivor空间不够时,借用eden区域实施算法。
- 标记整理:首先将标记对象,然后将标记的对象向内存一端移动,然后直接清理边界之外的区域。这种算法主要适用与对象存活时间长,存活率高的情况。
- 分代收集:将内存分为老年代和新生代,针对老年代,采用标记整理算法或标记清除算法,针对新生代,采用复制算法。因为新生代中大部分对象存活时间短,存活率极低,如果使用标记整理算法效率不高,同时老年代中对象存活时间长,存活率高,使用复制算法极大影响效率。
对于垃圾回收,还有一个重要的概念 “安全点”,他用在无用对象判断前使所有线程停止,这个停止的地方就是安全点,然后进行无用对象判断这一过程。原因简单来说,你不能在内存还是动态的过程中进行判断某个对象是否无用。
如何让线程跑到安全点呢?
首先虚拟机设置一个中断标志,然后线程去主动轮询这个标志,让发现中断为真,将自己挂起。但是这里有一个漏洞,如果线程本身是挂起状态,那么这个线程如何知道现在是垃圾回收器工作中,自己不能运行呢?这里便又引入的安全区域的概念,意思是在这个区域中,gc可以安全进行。
下面将列出主要的垃圾收集器及其特点:
- Serial(新生代)--Serial Old(老年代):单线程收集器,特点:简单高效。新生代采用“复制”,老年代采用“标记整理”。
- ParNew(新生代):Serial的多线程版本,但在但CPU下,不一定效果比Serial好,其成为新生代收集器的选择一个很大原因是能与CMS收集器配合。新生代采用“复制”
- Parallel Scavenge(新生代)--Parallel Old(老年代):吞吐量优先收集器。特点:可自适应调节回收时间,适合后台运算。
- CMS(老年代):特点:并发收集,低停顿。
- G1:最前沿收集器,按下不表。
目前虚拟机采用的收集器搭配方案:CMS-ParNew Parallel Scavenge--Parallel Old Serial Old-ParNew
深入java----java内存区域及对象的创建
看完深入理解jvm之后自己再用图的方式进行一遍梳理,用以加深理解。
第一部分,首先对整体java运行时内存区域有一个整体框架式的了解。
运行时内存区域的划分如上图所示,那么接下里看看一个对象的创建又怎么样的过程。
首先是需要一块内存区域,而寻找内存区域主要分为两种策略:
- 指针碰撞,当空闲内存区域为连续区域时,那么只需要将空指针移动一段与对象大小相等的距离即可。
- 空闲列表,当空闲内存区域不连续时,需要维护一个表来记录那些内存可用,并在分配时给对象分配一个足够大的空间并更新表。
(注:内存区域是否连续主要和垃圾回收策略相关)
在上面的过程中,对象的大小如何确定呢?这就涉及对象在内存中的存储形式是什么了。
一个java对象在内存中的结构分三块:
- 对象头:用于存储运行数据(类型指针,gc标志,状态锁等),大小为8字节的一倍或两倍。
- 实例数据:用户所需要的真正有效的数据。
- 对齐填充:站位用,因为要保证一个对象的大小为8字节的整数倍。
通过确定上述三个块便可确定一个对象的大小。
当一个对象创建好后,对象放在堆里,我们如何访问呢?
要访问一个对象,就需要用到java内存区域中虚拟机栈里面的局部变量表,通过局部变量表中的reference数据来访问堆上的具体对象。