深入理解JVM(三)——垃圾收集策略具体解释
Java虚拟机的内存模型分为五个部分。各自是:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区。
这五个区域既然是存储空间,那么为了避免Java虚拟机在执行期间内存存满的情况,就必须得有一个垃圾收集者的角色。不定期地回收一些无效内存,以保障Java虚拟机可以健康地持续执行。
这个垃圾收集者就是寻常我们所说的“垃圾收集器”。那么垃圾收集器在何时清扫内存?清扫哪些数据?这就是接下来我们要解决的问题。
程序计数器、Java虚拟机栈、本地方法栈都是线程私有的,也就是每条线程都拥有这三块区域,并且会随着线程的创建而创建。线程的结束而销毁。那么。垃圾收集器在何时清扫这三块区域的问题就攻克了。
此外,Java虚拟机栈、本地方法栈中的栈帧会随着方法的開始而入栈,方法的结束而出栈。并且每一个栈帧中的本地变量表都是在类被载入的时候就确定的。因此以上三个区域的垃圾收集工作具有确定性,垃圾收集器可以清楚地知道何时清扫这三块区域中的哪些数据。
然而,堆和方法区中的内存清理工作就没那么easy了。
堆和方法区全部线程共享,并且都在JVM启动时创建,一直得执行到JVM停止时。因此它们没办法依据线程的创建而创建、线程的结束而释放。
堆中存放JVM执行期间的全部对象,尽管每一个对象的内存大小在载入该对象所属类的时候就确定了。但到底创建多少个对象仅仅有在程序执行期间才干确定。
方法区中存放类信息、静态成员变量、常量。类的载入是在程序执行过程中,当须要创建这个类的对象时才会载入这个类。因此,JVM到底要载入多少个类也须要在程序执行期间确定。
因此,堆和方法区的内存回收具有不确定性,因此垃圾收集器在回收堆和方法区内存的时候花了一些心思。
堆内存的回收
1. 怎样判定哪些对象须要回收?
在对堆进行对象回收之前,首先要推断哪些是无效对象。我们知道。一个对象不被不论什么对象或变量引用。那么就是无效对象。须要被回收。
一般有两种判别方式:
引用计数法
每一个对象都有一个计数器,当这个对象被一个变量或还有一个对象引用一次,该计数器加一;若该引用失效则计数器减一。当计数器为0时,就觉得该对象是无效对象。可达性分析法
全部和GC Roots直接或间接关联的对象都是有效对象。和GC Roots没有关联的对象就是无效对象。GC Roots是指:
- Java虚拟机栈所引用的对象(栈帧中局部变量表中引用类型的变量所引用的对象)
- 方法区中静态属性引用的对象
- 方法区中常量所引用的对象
- 本地方法栈所引用的对象
PS:注意!GC Roots并不包含堆中对象所引用的对象!这样就不会出现循环引用。
两者对照:
引用计数法尽管简单,但存在一个严重的问题,它无法解决循环引用的问题。
因此。眼下主流语言均使用可达性分析方法来推断对象是否有效。
2. 回收无效对象的过程
当JVM筛选出失效的对象之后,并非马上清除,而是再给对象一次重生的机会。详细步骤例如以下:
推断该对象是否覆盖了finalize()方法
- 若已覆盖该方法,并该对象的finalize()方法还没有被执行过。那么就会将finalize()扔到F-Queue队列中;
- 若未覆盖该方法。则直接释放对象内存。
执行F-Queue队列中的finalize()方法
虚拟机会以较低的优先级执行这些finalize()方法们。也不会确保全部的finalize()方法都会执行结束。假设finalize()方法中出现耗时操作,虚拟机就直接停止执行,将该对象清除。对象重生或死亡
假设在执行finalize()方法时,将this赋给了某一个引用。那么该对象就重生了。假设没有,那么就会被垃圾收集器清除。
注意:
强烈不建议使用finalize()函数进行不论什么操作!
假设须要释放资源,请使用try-finally。
因为finalize()不确定性大,开销大,无法保证顺利执行。
方法区的内存回收
我们知道,假设使用复制算法实现堆的内存回收,堆就会被分为新生代和老年代。新生代中的对象“朝生夕死”,每次垃圾回收都会清除掉大量的对象;而老年代中的对象生命较长。每次垃圾回收仅仅有少量的对象被清除掉。
因为方法区中存放生命周期较长的类信息、常量、静态变量,因此方法区就像是堆的老年代,每次垃圾收集的仅仅有少量的垃圾被清除掉。
方法区中主要清除两种垃圾:
1. 废弃常量
2. 废弃的类
1. 怎样判定废弃常量?
清除废弃的常量和清除对象相似。仅仅要常量池中的常量不被不论什么变量或对象引用,那么这些常量就会被清除掉。
2. 怎样废弃废弃的类?
清除废弃类的条件较为苛刻:
1. 该类的全部对象都已被清除
2. 该类的java.lang.Class对象没有被不论什么对象或变量引用
仅仅要一个类被虚拟机载入进方法区,那么在堆中就会有一个代表该类的对象:java.lang.Class。
这个对象在类被载入进方法区的时候创建,在方法区中该类被删除时清除。
3. 载入该类的ClassLoader已经被回收
垃圾收集算法
如今我们知道了判定一个对象是无效对象、判定一个类是废弃类、判定一个常量是废弃常量的方法,也就是知道了垃圾收集器会清除哪些数据,那么接下来介绍怎样清除这些数据。
1. 标记-清除算法
首先利用刚才介绍的方法推断须要清除哪些数据,并给它们做上标记。然后清除被标记的数据。
分析:
这样的算法标记和清除过程效率都非常低。并且清除完后存在大量碎片空间。导致无法存储大对象,减少了空间利用率。
2. 复制算法
将内存分成两份,仅仅将数据存储在当中一块上。当须要回收垃圾时,也是首先标记出废弃的数据,然后将实用的数据拷贝到还有一块内存上,最后将第一块内存全部清除。
分析:
这样的算法避免了碎片空间,但内存被缩小了一半。
并且每次都须要将实用的数据全部拷贝到还有一片内存上去,效率不高。
解决空间利用率问题:
在新生代中。因为大量的对象都是“朝生夕死”。也就是一次垃圾收集后仅仅有少量对象存活。因此我们可以将内存划分成三块:Eden、Survior1、Survior2,内存大小各自是8:1:1。
分配内存时,仅仅使用Eden和一块Survior1。
当发现Eden+Survior1的内存即将满时,JVM会发起一次MinorGC,清除掉废弃的对象。并将全部存活下来的对象拷贝到还有一块Survior2中。
那么,接下来就使用Survior2+Eden进行内存分配。
通过这样的方式,仅仅须要浪费10%的内存空间就可以实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题。
可是,当一个对象要申请内存空间时,发现Eden+Survior中剩下的空间无法放置该对象,此时须要进行Minor GC,假设MinorGC过后空暇出来的内存空间仍然无法放置该对象,那么此时就须要将对象转移到老年代中。这样的方式叫做“分配担保”。
什么是分配担保?
当JVM准备为一个对象分配内存空间时,发现此时Eden+Survior中空暇的区域无法装下该对象,那么就会触发MinorGC,对该区域的废弃对象进行回收。
但假设MinorGC过后仅仅有少量对象被回收,仍然无法装下新对象,那么此时须要将Eden+Survior中的全部对象都转移到老年代中,然后再将新对象存入Eden区。
这个过程就是“分配担保”。
3. 标记-整理算法
在回收垃圾前。首先将全部废弃的对象做上标记,然后将全部未被标记的对象移到一边,最后清空还有一边区域就可以。
分析:
它是一种老年代的垃圾收集算法。老年代中的对象一般寿命比較长。因此每次垃圾回收会有大量对象存活,因此假设选用“复制”算法。每次须要复制大量存活的对象,会导致效率非常低。
并且,在新生代中使用“复制”算法,当Eden+Survior中都装不下某个对象时,可以使用老年代的内存进行“分配担保”,而假设在老年代使用该算法。那么在老年代中假设出现Eden+Survior装不下某个对象时。没有其它区域给他作分配担保。因此,老年代中一般使用“标记-整理”算法。
4. 分代收集算法
将内存划分为老年代和新生代。老年代中存放寿命较长的对象,新生代中存放“朝生夕死”的对象。然后在不同的区域使用不同的垃圾收集算法。
Java中引用的种类
Java中依据生命周期的长短,将引用分为4类。
1. 强引用
我们平时所使用的引用就是强引用。
A a = new A();
也就是通过keywordnew创建的对象所关联的引用就是强引用。
仅仅要强引用存在,该对象永远也不会被回收。
2. 软引用
仅仅有当堆即将发生OOM异常时,JVM才会回收软引用所指向的对象。
软引用通过SoftReference类实现。
软引用的生命周期比强引用短一些。
3. 弱引用
仅仅要垃圾收集器执行。软引用所指向的对象就会被回收。
弱引用通过WeakReference类实现。
弱引用的生命周期比软引用短。
4. 虚引用
虚引用也叫幽灵引用,它和没有引用没有差别。无法通过虚引用訪问对象的不论什么属性或函数。
一个对象关联虚引用唯一的作用就是在该对象被垃圾收集器回收之前会受到一条系统通知。
虚引用通过PhantomReference类来实现。
深入理解JVM(三)——垃圾收集策略具体解释的更多相关文章
- 深入理解JVM(三)垃圾收集器和内存分配策略
3.1 关于垃圾收集和内存分配 垃圾收集和内存分配主要针对的区域是Java虚拟机中的堆和方法区: 3.2 如何判断对象是否“存活”(存活判定算法) 垃圾收集器在回收对象前判断其是否“存活”的两个算法: ...
- 深入理解JVM内存分配策略
理解JVM内存分配策略 三大原则+担保机制 JVM分配内存机制有三大原则和担保机制 具体如下所示: 优先分配到eden区 大对象,直接进入到老年代 长期存活的对象分配到老年代 空间分配担保 对象优先在 ...
- 深入理解JVM(5)——垃圾收集和内存分配策略
1.垃圾收集对象 垃圾收集主要是针对堆和方法区进行. 程序计数器.虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收. 哪 ...
- 深入理解JVM(二)--垃圾收集算法
一. 概述 说起垃圾收集(Garbage Collection, GC), 大部分人都把这项技术当做Java语言的伴随生产物. 事实上, GC的历史远远比Java久远, 1960年 诞生于MIT的Li ...
- 深入理解JVM:垃圾收集器与内存分配策略
堆里面存放着Java世界差点儿全部的对象实例,垃圾收集器在对堆进行回收前.第一件事情就是要确定这些对象之中哪些还存活,哪些已经死去.推断对象的生命周期是否结束有下面几种方法 引用计数法 详细操作是给对 ...
- 深入理解JVM : Java垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现. Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商.不同版本的虚拟机所提供的垃圾收集器都可能会有很大差 ...
- 理解JVM之垃圾收集器详解
前言 垃圾收集器作为内存回收的具体表现,Java虚拟机规范并未对垃圾收集器的实现做规定,因而不同版本的虚拟机有很大区别,因而我们在这里主要讨论基于Sun HotSpot虚拟机1.6版本Update22 ...
- 理解JVM之垃圾收集器概述
前言 很多人将垃圾收集(Garbage Collection)视为Java的伴生产物,实际1960年诞生的Lisp是第一门真正使用内存动态分配与垃圾手机技术的语言.在目前看来,内存的动态分配与内存回收 ...
- 深入理解JVM(三) -- 对象的内存布局和访问定位
一 对象的内存布局: 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding). HotSpot的对 ...
随机推荐
- 【周年庆】china-pub 14周年庆感恩回馈四波狂热来袭
活动主题:china-pub 14周年庆感恩回馈四波狂热来袭活动说明:[第1波]电子书免费抢!10万好书 65折封顶! 活动规则: 1.活动期间内凡 ...
- Orchard模块开发全接触2:新建 ProductPart
一:创建 Part 1:项目引用 Orchard.Framework: 2:创建 Models 文件夹: 3:在 Models 文件夹下创建类 ProductPartRecord,如下: public ...
- C#多线程读写同一文件处理
在多线程访问读写同一个文件时,经常遇到异常:“文件正在由另一进程使用,因此该进程无法访问此文件”. 多线程访问统一资源的异常, 解决方案1,保证读写操作单线程执行,可以使用lock 解决方案2,使用S ...
- poj 1325 Machine Schedule 题解
Machine Schedule Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 14479 Accepted: 6172 ...
- [leetcode]Next Permutation @ Python
原题地址:https://oj.leetcode.com/problems/next-permutation/ 题意: Implement next permutation, which rearra ...
- Linear Regression总结
转自:http://blog.csdn.net/dongtingzhizi/article/details/16884215 Linear Regression总结 作者:洞庭之子 微博:洞庭之子-B ...
- Commands to help you to Start Using ScaleIO Storage
To start using your storage: Log in to the MDM: scli --login --username admin --password <passwor ...
- C# 6.0 的那些事
这两天期中考试没时间去看Connect();直播,挺可惜的,考完后补看了Connect(); 把C#6.0的新东西总结一下. 自动属性初始化 (Initializers for auto-proper ...
- JAVA-错误The type BookServiceImpl must implement the inherited abstract method
错误原因 :是因为class继承了其他的类,没有导入过来,选择add unimplemented methods进行解决
- Effective JavaScript Item 63 注意异步调用中可能会被忽略的异常
异常处理是异步编程的一个难点. 在同步的代码中,异常可以非常easy地通过try catch语句来完毕: try { f(); g(); h(); } catch (e) { // handle an ...