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
和<
T>
tSoftRef = new SoftRefrence<
T>
(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虚拟机》
Java内存与垃圾收集知识总结的更多相关文章
- 求你了,再问你Java内存模型的时候别再给我讲堆栈方法区了…
GitHub 4.1k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 4.1k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 4.1k Star 的 ...
- Java内存原型分析:基本知识
转载: Java内存原型分析:基本知识 java虚拟机内存原型 寄存器:我们在程序中无法控制 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中 堆:存放用new产生的数据 静 ...
- java基础知识(四)java内存机制
Java内存管理:深入Java内存区域 上面的文章对于java的内存管理机制讲的非常细致,在这里我们只是为了便于后面内容的理解,对java内存机制做一个简单的梳理. 程序计数器:当前线程所执行的字节码 ...
- 硬件内存模型到 Java 内存模型,这些硬核知识你知多少?
Java 内存模型跟上一篇 JVM 内存结构很像,我经常会把他们搞混,但其实它们不是一回事,而且相差还很大的,希望你没它们搞混,特别是在面试的时候,搞混了的话就会答非所问,影响你的面试成绩,当然也许你 ...
- Java-100天知识进阶-Java内存-知识铺(四)
知识铺: 致力于打造轻知识点,持续更新每次的知识点较少,阅读不累.不占太多时间,不停的来唤醒你记忆深处的知识点. 1.Java内存模型是每个java程序员必须掌握理解的 2.Java内存模型的主要目标 ...
- 【Java虚拟机4】Java内存模型(硬件层面的并发优化基础知识--缓存一致性问题)
前言 今天学习了Java内存模型第一课的视频,讲了硬件层面的知识,还是和大学时一样,醍醐灌顶.老师讲得太好了. Java内存模型,感觉以前学得比较抽象.很繁杂,抽象. 这次试着系统一点跟着2个老师学习 ...
- java的线程安全、单例模式、JVM内存结构等知识学习和整理
知其然,不知其所以然 !在技术的海洋里,前路漫漫,我一直在迷失着自我. 欢迎访问我的csdn博客,我们一同成长! "不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!" 博 ...
- Java虚拟机的内存管理----垃圾收集器
1.Serial收集器 优点,是简单而高效,单线程避免了线程交互的开销. 缺点,进行垃圾回收时需要Stop the world(暂停所有用户线程). 2.ParNew收集器 它是Serial收集器的多 ...
- java内存回收需要了解的知识
你是否有过这样的经历,跑得好好的Java进程,突然就瘫痪了?多数Java进程瘫痪的原因可以从java虚拟机层面找到原因. 1.什么情况下会执行gc 为了了解我们的系统为什么会不停fgc,我们需要先了解 ...
随机推荐
- 采用TCP协议实现PIC18F97J60 ethernet bootloader
了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). TCP/IP Stac ...
- .NET 工具类ObjectDumper 打印对象
// Comes from the LINQ samples provided by Microsoft //Copyright (C) Microsoft Corporation. All righ ...
- 基于纹理的图片检索及demo(未启动)
基于纹理的图片检索及demo(未启动)
- 启动hadoop,没有启动namenode进程。log4j:ERROR setFile(null,true) call faild.
启动hadoop,没有启动namenode进程.log4j:ERROR setFile(null,true) call faild. 解决办法: cd /home/hadoop/hadoop-en ...
- 编程模式之观察者模式(Observer)
观察者模式由四个角色组成:抽象主题角色,抽象观察者角色,具体主题角色,抽象观察者角色,具体观察者角色. 抽象主题角色(Subject):把所有的观察者角色的引用保存在一个集合中,可以有任意数量的观察者 ...
- iOS 解压打包静态库命令
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Hannotate SC" } p.p2 { margin: 0.0px ...
- Loadrunner开发测试脚本
Loadrunner开发测试脚本 开发测试脚本可以通过录制,也可以手动开发,建议能录制的尽量录制,省时省力,不能录制的只能费力自己开发了,具体看项目情况来决定. 使用Loadrunner开发脚本过程中 ...
- java打包成jar,但不打包配置文件
有时候我们做java project的时候,都会打包成jar程序,为了方便部署会加个配置文件conf/pro.properties(conf文件夹与src文件夹同级) 但是不想打包进jar.其实用ec ...
- 邮箱验证 各种邮箱的smtp
常见邮箱的SMTP设置 QQ 邮箱举例:(地址test@qq.com)(账号test)(密码***)(SMTP服务smtp.qq.com)(端口25)(注意:请手动开通SMTP功能,通过网页登录qq邮 ...
- Python scipy.sparse矩阵使用方法
本文以csr_matrix为例来说明sparse矩阵的使用方法,其他类型的sparse矩阵可以参考https://docs.scipy.org/doc/scipy/reference/sparse.h ...