[jjzhu学java]之深入理解JVM之垃圾收集器与内存分配策略

  • 深入理解JVM之垃圾收集器与内存分配策略
    • 如何判断对象已经消亡
      • 引用计数算法
      • 根搜索算法
      • 引用

深入理解JVM之垃圾收集器与内存分配策略

java中对象的创建需要的内存都是在java堆中申请的,所以垃圾收集的区域就是对java堆和方法区的内存区域进行GC。

如何判断对象已经消亡

垃圾收集器的主要任务就是找出已经“消亡”的对象,将其标记并清除其说用内存的过程,如何判断某个对象已经“消亡”,不同的虚拟机有不同的判断策略

引用计数算法

引用计数(Reference Counting)算法的基本思想就是:给每个对象添加一个引用计数器,每当有一个地方对该对象进行了引用,引用计数器就加1;引用失效后就减1;当引用计数器为0时,就表示没有任何地方引用了该对象,这可以认为该对象已经“消亡”了。
虽然引用计数算法思想简单,效率也很高,但是java虚拟机并没有用到该算法标记“消亡”对象,因为当出现循环引用的时候,表现就不是那么好了。我们可以编写测试代码测试并看GC信息看看java虚拟机到底有没有用该算法。

/**
* @ClassName: ReferenceCountingGC
* @Description: 引用计算GC
* @author 祝佳俊(jjzhu_ncu@163.com)
* @date 2016年10月20日 下午4:57:53
*
*/
public class ReferenceCountingGC {
    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    private byte[] bigSize = new byte[2 * _1MB];
    public static void testGC(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB; //互相引用
        objB.instance = objA;
        objA = null;
        objB = null;

        System.gc();
    }
    public static void main(String[] args) {
        testGC();
    }
}

代码示例中,新建了两个对象objA,objB,然后通过objA.instance = objB,objB.instance = objA让他们互相引用,然后将objA、objB都置为空,将会触发GC,我们可以通过-XX:+PrintGCDetails让java虚拟机在发生GC后打印出GC的具体信息,
代码片运行时的VM参数为-Xmx20M -Xms20M -XX:+PrintGCDetails
运行程序,可看到如下打印结果:

[GC [PSYoungGen: 4340K->256K(5952K)] 4340K->256K(19648K), 0.0009373 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System) [PSYoungGen: 256K->0K(5952K)] [PSOldGen: 0K->162K(13696K)] 256K->162K(19648K) [PSPermGen: 3025K->3025K(21248K)], 0.0053905 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
 PSYoungGen      total 5952K, used 205K [0x00000000ff960000, 0x0000000100000000, 0x0000000100000000)
  eden space 5120K, 4% used [0x00000000ff960000,0x00000000ff993408,0x00000000ffe60000)
  from space 832K, 0% used [0x00000000ffe60000,0x00000000ffe60000,0x00000000fff30000)
  to   space 832K, 0% used [0x00000000fff30000,0x00000000fff30000,0x0000000100000000)
 PSOldGen        total 13696K, used 162K [0x00000000fec00000, 0x00000000ff960000, 0x00000000ff960000)
  object space 13696K, 1% used [0x00000000fec00000,0x00000000fec28910,0x00000000ff960000)
 PSPermGen       total 21248K, used 3043K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
  object space 21248K, 14% used [0x00000000f9a00000,0x00000000f9cf8c28,0x00000000faec0000)

在分析结果前,先对GC的内容先做一个介绍:

1、GC日志的第一行[GC [PSYoungGen说明了在新生代发生了GC(Minor GC),这里也可以看出,当前的HotSpot虚拟机采用的是Parallel Scavenge(PS)垃圾收集器 后面的4340K->256K代表GC前后的新生代内存区域的变化,这里从4340K变到256K,说明虚拟机进行了垃圾回收,后面的(5952K)代表新生代的内存区域大小,4340K->256K(19648K)这里的括号内的19648K代表新生代和年老代的内存大小。之后的0.0009373 secs代表的是GC所用的事件。
2、GC日志的第二行[Full GC (System)表示系统触发的一次Full GC,也就是代码中System.gc();所引起的GC,一次Full GC会对java堆中的所有区域进行GC(新生代、年老代、永久代),所以后面的PSYoungGen(新生代)、PSOldGen(年老代)、PSPermGen(永久带)显示了各区域的GC情况,我们可以看到PSYoungGen: 256K->0K(5952K),新生代经过Full GC后,全被清空了。
3、后面的Heap堆显示了各区域的最终使用情况
从最后的Full GC (System) [PSYoungGen: 256K->0K(5952K)]可以看到,新生代中的内存全被GC了,所以说,HotSpot并没有用引用计数算法来对“消亡”对象进行GC。

根搜索算法

现在一般的垃圾收集器都是用该算法(GC Root Tracing)来判断对象是否“消亡”,该算法的基本思想就是:通过一组称为“GC Roots”的对象作为根节点,然后从这些根节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到根节点之间没有任何一条引用链的话,就认为该对象已经“消亡”,如下图所示:

可以作为GC Roots的对象包括:
1. 虚拟机栈(栈帧中的本地变量表)中的引用的对象
2. 方法区中 的类静态属性引用的对象
3. 方法区中的常量引用的对象
4. Native方法引用的对象

引用

任何判断对象是否消亡都是通过引用来判断,引用以前的定义是:如果reference类型的数据中存储的数值代表的是另一块内存区域的起始地址的话,就称这块内存代表一个引用

时间: 2024-12-05 19:54:49

[jjzhu学java]之深入理解JVM之垃圾收集器与内存分配策略的相关文章

JAVA垃圾收集器与内存分配策略详解_java

引言 垃圾收集技术并不是Java语言首创的,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.垃圾收集技术需要考虑的三个问题是: 1.哪些内存需要回收 2.什么时候回收 3.如何回收 java内存运行时区域的分布,其中程序计数器,虚拟机栈,本地方法区都是随着线程而生,随线程而灭,所以这几个区域就不需要过多考虑回收问题.但是堆和方法区就不一样了,只有在程序运行期间我们才知道会创建哪些对象,这部分内存的分配和回收都是动态的.垃圾收集器所关注的就是这部分内存. 一 对象

java中内存分配策略及堆和栈的比较[转]

2.1 内存分配策略 按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允 许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据

[jjzhu学java]深入理解JVM笔记之内存管理机制

深入理解JVM笔记之内存管理机制 运行时数据区域 程序计数器 JVM栈 本地方法栈 Java堆 方法区 运行时常量池 直接内存 对象访问 OutOfMemoryError异常 Java堆溢出示例 JVM栈和本地方法栈溢出 运行时常量池溢出 本机直接内存溢出 深入理解JVM笔记之内存管理机制 运行时数据区域 程序计数器 每个线程都有一个程序计数器(PC),是当前线程所执行的字节码的行号指示器,通过改变程序计数器的值来选取下一条指令.各线程之间的计数器互不影响,是线程私有的内存. 如果线程执行的是一

理解JVM(4)- 堆内存的分代管理

前一篇从整体上了解了一下JVM的运行时数据区,它由_线程私有的栈内存_和_线程共享的堆内存.方法区_组成.本章节将详细了解一下堆内存又被分为哪些区域,或者说JVM是如何把对象分配到这些区域上的 JVM根据对象在内存中存活时间的长短,把堆内存分为新生代(包括一个Eden区.两个Survivor区)和老年代(Tenured或Old).Perm代(永久代,Java 8开始被"元空间"取代)属于方法区了,而且仅在Full GC时被回收.大致如下图 为对象分配空间,就是把一块确定大小的内存从堆中

[jjzhu学java]之自动装箱的陷阱

自动装箱.拆箱的陷阱 装箱与拆箱 java语言中为每种基本数据类型(int,float,double-)都提供了与之对应的包装器类型(Integer,Float,Double).从java se5之后就开始提供了自动装箱的特性.想要得到一个数值为2016的Integer时,只需要如下的赋值语句: //Integer a = Integer.valueOf(2016); Integer a = 2016; 该语句就会自定根据=右边的数值创建相应的Integer,这个过程就是自动装箱. 拆箱与装箱是

[jjzhu学java]之JDK集合框架源码分析

Java Collection Collection接口 AbstractCollection类 AbstractList类 Vector类 Stack栈 ArrayList AbstractSequentialList LinkedList线性链表 Map接口 AbstractMap HashMap LinkedHashMap treeMap HashTable 总结 Java Collection 图中实线边框表示的是实现类(ArrayList, Hashtable等),虚线边框的是抽象类(

[jjzhu学java]之solr4.9同步mysql数据

Solr是一个高性能,采用Java5开发,基于Lucene的全文搜索服务器.同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置.可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎.        可以将数据库中的数据导入到solr中,对于百万级别的数据可以快速响应查询. 1.安装jdk JDK版本需要1.7以上 2.安装solr 下载solr压缩包,解压安装包,进入到SOLR_HOME\example目录下,运行命令java –

java对象生命周期-一个对象被垃圾收集器回收了为什么还要卸载

问题描述 一个对象被垃圾收集器回收了为什么还要卸载 一个对象被垃圾收集器回收了为什么还要卸载?被gc回收了不是已经不存在了吗? 解决方案 说反了吧,先卸载才能回收 http://www.cnblogs.com/mengdd/p/3594608.html 解决方案二: 自己 Delete 可靠一些,且实时性高. 靠 GC 不知道是什么时候才能收回.

深入理解JVM之一:Java内存区域

前言 Java虚拟机运行时数据区分为以下几个部分: 方法区.虚拟机栈.本地方法栈.堆.程序计数器.如下图所示: 程序计数器 程序计数器可以理解为当前线程执行的字节码的行号指示器,字节码解释器就是通哟改变这个值来获取需要执行的下一条需要执行的字节码指令.对于多线程来说,每条线程都有自己的程序计数器,这样各线程之间的计数器互不影响,这类内存区域也叫作"私有内存"(可以看到其实并不是私有的),之所以这么设计,是因为在多线程的情况下,完全可能出现线程中断的情况,那么当被中断的线程需要回复执行的