Java内存与垃圾收集知识总结

总结一下关于Java内存的知识,今天我不生产知识,我只是知识的搬运工。

1.运行时数据区域

java虚拟机在执行JAVA程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

由所有线程共享的数据区

  • 堆[Heap]:

    Java堆是Java虚拟机管理的内存中最大的一块,此内存区域的唯一目的就是存放对象实例。所有的对象实例以及数组都要在堆上分配,但随着虚拟机技术的发展,这个变得不这么绝对。Java堆是垃圾收集其管理的主要区域,因此很多时候也被称为GC堆。根据虚拟机规范的规定,Java堆可以是处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

  • 方法区[Method Area]:

    方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

线程隔离的数据区:

  • 虚拟机栈[VM]:

    虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

  • 本地方法栈[Native Method Stack]:

    本地方法栈与虚拟机栈发挥的作用非常相似,区别就在于它是为Native方法服务的。

  • 程序计数器[Program Counter Register]:

    程序计数器是一块儿较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

上面是运行时数据非配的区域,我们了解之后,关注下面的重头戏,JVM很重要的垃圾回收机制。

2.对象的生命旅程

为了避免程序员繁重的内存管理工作,JVM提供了垃圾回收机制。

2.1对象是否可达

我们通过可达性分析来判断一个对象是否存活。
这个算法的基本思路就是通过一系列的成为“GC Roots”的对象作为起始点,从这些点开始写向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话说就是从GC Roots到这个对象不可达时),则证明这些对象是不可用的。
在Java中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候他们暂时处于缓刑阶段,如果要真正需要宣告一个对象死亡,至少要经历两次标记的过程。
第一次标记是看该对象是否有必要执行finalize()方法,如果有必要会放入F-Queue队列中。
第二次标记前,对象可以通过重新与引用链上的任何一个对象建立关联,则可以逃脱被回收的命运,否则就会被回收。

2.2方法区回收

我们大多数时候认为方法区是属于永久代的,因为永久代的回收成本较高,所以我们可以选择不对永久代就行回收,但也可以对其进行回收。永久代的垃圾回收主要会后两部分内容:废弃常量和无用的类。
无用的类需要同时满足下面三个条件:

  • 该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

虚拟机可以对满足上述3个条件的无用类进行回收。

2.3四种引用

由上文可知,一个对象是否被回收,和引用有很大关系,Java语言提供了四个级别的引用

  • 强引用

    强引用是我们最常见的Object o = new Object()这类引用,只要强引用还存在,垃圾回收器就永远不会回收被引用的对象。

  • 软引用

    软引用用来描述一些还有用但并非必须的对象,对于软引用关联的这对象,当内存资源紧张的时候,才会触发GC。是GC二次回收的对象,只有当回收软引用对象空间仍不足是,才会抛出内存异常对象。
    常用方法有SoftRefrence tSoftRef = new SoftRefrence(t)tSoftRef.get()方法
    每个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变成不可达),软引用对象就会进入引用队列,通过这个引用队列可以跟踪那个对象的回收情况,在初始化时加入一个引用队列的参数即可。

  • 弱引用

    弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作室,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。用WeakRefrence实现弱引用,和软引用一样,有两种初始化的方法。

  • 虚引用

    最弱的一种引用关系,一个对象有没有虚引用,完全不会对其生存时间构成影响,随时都可能被垃圾回收器回收。虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收的过程。也无法通过get方法获得对象的实例。当垃圾回收器准备回收一个对象是,如果发现它有虚引用,就会在回收对象后,将这个虚引用加入引用队列,已通知应用程序对象的回收情况。可以使用PhantomRefrence实现虚引用。

3.垃圾回收算法

3.1单打独斗的算法

  • 引用计数法

    对于一个对象A,给其配备一个整型的计数器,只要任何一个对象引用了A,则A的引用计数器就加1,当引用失效后,引用计数器就减1,只要对象的引用计数器为0,则对象A就不可以在被引用,会被回收掉。
    但这种算法那有两个问题:(1)无法处理循环引用的情况,会引起内存泄露(2)每次引用产生和消除的时候,都需要伴随一个计数器变化的操作,影响性能。

  • 标记清除法

    如其名,分为两个阶段。标记阶段,实现通过根节点,标记所有从根节点开始的可达对象,因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清楚所有未被标记的对象。标记清楚算法可能产生的最大问题就是空间碎片。

  • 复制算法

    它将可用内存按照用量划分为大小相等的两块,每次只是用其中的一块,当这一块的内存用完了,就将还存活的对象复制到另外一块上去,然后再将已使用的内存空间一次性清理掉。这样使得每次都是对整个半区进行内存回收,不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可。这种方法的缺陷在于代价太大,将可用内存缩小到了原来的一半。所以在老年代这种每次回收只有一小部分甚至没有对象被回收的场景不适合用这种方法。

  • 标记-整理算法

    根据老年代的特点提出的算法。第一阶段标记过后,让所有存活的对象都向一端移动,然后直接清理掉端边界的内存。

3.2 集众长,分代收集算法

结合java对象的生存特点,提出了分代收集算法,结合上面各种算法的优点。
分代算法根据对象的特点将内存区间分成几块

  • 新生代

    存放年轻对象的空间。年轻对象是指刚刚创建的,或者经历垃圾回收次数不多的对象。分为Eden区间,SurvivorFrom空间和SurvivorTo空间。通常是8:1:1,因为大部分新生对象都是朝生夕灭的,垃圾对象远远多于存活对象。
    在垃圾回收时,eden区的存活对象会被复制到未使用的Survivor空间中(假设是to),正在使用的Survivor空间(假设是From)中的年轻对象也会被复制到to空间中(大对象,或者老年对象会直接进入老年代,如果to空间已满,则对象直接进入老年代)。此时,eden空间和from空间中的剩余对象就是垃圾对象,可以直接清空,to空间则存放此次回收后仍然存货的对象。这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间的浪费。
    通过参数SurvivorRadio来设置,即Survivor/Enden ,默认是8 。
    新生代满了或者区域不足触发的是MinorGC。

  • 老年代

    在老年代中,几乎所有的对象都是经过几次垃圾回收后依然得以存活的。因此可以认为这些对象在一定时间内会常驻内存。如果使用复制算法,代价很到,一般使用标记-清除或者标记-整理算法。
    参数:Xms设置初始堆大小,Xmx设置最大堆大小,NewRadio设置老年代与新生代的比例,老年代加新生代的大小之和即堆的大小。
    什么时候进入老年代呢?
    大对象:PretenureSizeThreshold。单位是字节。默认情况下是0,也就是不指定晋升大小,一切由运行情况决定。
    老年对象:MaxTenuringThreshold。默认15,也就是说最多经历15次GC,第16次GC的时候就会进入老年代。
    新生代回收频率高,每次耗时也短。老年代回收频率低,但是会消耗更多的时间。
    老年代满了触发的是FullGC,经常会伴随至少一次的MinorGC,比MinorGC慢10倍以上。

  • 永久代?NO!MetaSpace!

    在JDK1.6和JDK1.7的版本中,方法区可以理解成永久代,准确的说是JVM使用永久代来实现方法区。这里存储类信息和元数据。可以通过参数PermSize和MaxPerSize制定大小。
    但是这样会很明显面临一个问题,就是不够灵活,很容易溢出。
    所以在JDK1.8中,彻底移除了永久代 ,取而代之的是MetaSpace,使用本地内存存储类信息和元数据。
    此处参见下面两个链接的内容:
    Java PermGen 去哪里了?
    Java 8新特性探究(九)跟OOM:Permgen说再见吧

今天就到这里了,明天总结关于垃圾收集器的知识。
参考资料:
《深入理解Java虚拟机》
《实战Java虚拟机》

?

时间: 2024-12-13 23:28:49

Java内存与垃圾收集知识总结的相关文章

Java内存浅析

作为一名java初学者,我发现网上对Java内存这部分知识讲解粗细不一.深浅不定,理解起来难度较大.于是,自己动手整理了一份资料,以供交流学习. Java的编程环境如图所示. 从上图可以看出,Java虚拟机是程序运行的场所 .那么什么是虚拟机呢?要理解Java虚拟机,你首先必须意识到,当你说"Java虚拟机"时,可能指的是如下三种不同的东西:1.抽象规范.2.一个具体的实现  .3.一个运行中的虚拟机实例.每个Java程序都运行于某个具体的Java虚拟机实现的实例上. Java虚拟机在

关于java内存分析的探讨

这些天一直都想找个机会把Java内存方面的知识整理整理,毕竟任何知识都涉及到这方面.Java内存分析是java学习的一大重点. 下面我们进入正式话题讨论: 我们知道Java内存大致分为三块:如下图 我们先大致了解下java各分区的数据存放内容: 栈区:主要为方法服务,存在许许多多的方法栈帧,在方法栈帧里开辟局部变量开辟空间,基本类型按基本数据类型数据大小开辟空间,而引用类型则开辟四个字节大小. 堆区:主要存放创建的对象数据. 方法区:主要存放加载的类,静态变量,静态初始化块,常量,以及程序运行的

java从基础知识(四)java内存机制

Java内存管理:深入Java内存区域 上面的文章对于java的内存管理机制讲的非常细致,在这里我们只是为了便于后面内容的理解,对java内存机制做一个简单的梳理. 程序计数器:当前线程所执行的字节码的行号指示器,虚拟机下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需要依赖改变这个计数器的值来完成. 栈:保存局部变量.引用,方法调用结束即被释放.每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表.操作栈.动态链接.方法出口 等信息.

java虚拟机之垃圾收集器与内存分配策略

哪些内存需要回收? java内存运行时区域的各个部分,其中程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不絮的执行着出栈和入栈操作.每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的.因此这几个区域的内存分配和回收都具有确定性,所以这部分不需要过多考虑内存回收.但是方法区和堆不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也不一样,我们只有在程序处于运行期间才能知道会创建哪些对象,这部分的分配和回收都

Java内存知识整理

因主要是摘录,先列参考文献: http://ifeve.com/jmm-faq/ 1,我理解的Java内存模型是在多处理器,多线程的场景下保证在内存里的读写不会存在歧义. "Java内存模型描述了在多线程代码中哪些行为是合法的,以及线程如何通过内存进行交互.它描述了"程序中的变量" 和 "从内存或者寄存器获取或存储它们的底层细节"之间的关系.Java内存模型通过使用各种各样的硬件和编译器的优化来正确实现以上事情." 2,同步,同步并不仅仅是互斥.

Java虚拟机的内存管理----垃圾收集器

1.Serial收集器 优点,是简单而高效,单线程避免了线程交互的开销. 缺点,进行垃圾回收时需要Stop the world(暂停所有用户线程). 2.ParNew收集器 它是Serial收集器的多线程版本,新生代才有多线程并行收集.是CMS收集器(下文会介绍)的默认新生代收集器. ParNew在单CPU的情况下,会比Serial收集器效率更差,因为多线程交互的开销. 但是,如今的计算机普遍是多CPU多核,而ParNew默认的线程数量是CPU的数量.因此它更加适应潮流. 3.Parallel

Java虚拟机2:Java内存区域及对象

http://www.cnblogs.com/xrq730/p/4827590.html 几个计算机的概念 为以后写文章考虑,也为巩固自己的知识和一些基本概念,这里要理清楚几个计算机中的概念. 1.计算机存储单位 从小到大依次为位Bit.字节Byte.千字节KB.兆M.千兆GB.TB,相邻单位之间都是1024倍,1024为2的10次方,即 1Byte = 8bit,1K = 1024Byte,1M = 1024K,1G = 1024M,1T = 1024G 2.计算机存储元件 寄存器:中央处理器

java+内存分配及变量存储位置的区别

Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识.一般Java在内存分配时会涉及到以下区域: ◆寄存器:我们在程序中无法控制 ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象) ◆堆:存放用new产生的数据 ◆静态域:存放在对象中用static定义的静态成员 ◆常量池:存放常量 ◆非RAM存储:硬盘等永久

深入Java核心 Java内存分配原理精讲

栈.堆.常量池虽同属Java内存分配时操作的区域,但其适用范围和功用却大不相同.本文将深入Java核心,详细讲解Java内存分配方面的知识. Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识.一般Java在内存分配时会涉及到以下区域: ◆寄存器:我们在程序中无法控制 ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中 ◆堆:存