本文重点介绍JVM内存模型,对象标识算法以及垃圾回收算法的原理,至于一些实际JVM优化操作或遇到的问题会在后续其他文章进行讲解。
一、JVM分为那些区域?每个区域存储什么内容?
JVM运行时(例如运行一个main方法),在操作系统中是一个进程,该进程在物理内存中开辟一块空间,在这块空间中又划分了很多区域,有些区域是线程共享的,有些区域是线程独享的。如下图所示:
永久区或方法区:存储类的信息,例如类名,属性,方法名,常量等等,由于这些内容几乎不发生变化,所以该区可以称作永久区。当该区域加载了大量类的时候,是会发生oom的,例如tomcat启动时加载了web项目的大量jsp文件,或者通过动态代理创建了非常多的代理类。
堆内存(很重要):是JVM中最大的一块内存区域,存储所有的对象实例,堆又分为Eden,from survivor,to survivor和年老代,该区域会在后面做详细介绍。
虚拟机栈:存放在编译期可以预知的所有基本类型,例如int、boolean、char等,其次该区域还存放每个线程在执行方法时的嵌套深度,例如methodA调用methodB,然后结束,那么嵌套深度就是2,如果有一些递归操作,嵌套深度非常深,也可能导致oom。
本地方法栈:存放native的方法。
程序计数器:记录每一个线程执行代码的行号,此区域是jvm中唯一不会发生oom的区域。
二、为什么需要垃圾回收?JVM中哪些区域会进行垃圾回收?什么样的对象会被垃圾回收?
通过第一部分内容,描述了每个区域存储的内容,那么为什么需要垃圾回收?简单来说,如果创建的一个对象不在被任何变量所引用了,那么这个对象对后续程序已经没有存在的意义了,那么就要把这个对象占用的内存进行释放,这个过程就是所谓的垃圾回收。
哪些区域会进行垃圾回收?
虚拟机栈、本地方法栈和计数器会自动随着方法或者线程的结束而自然的被回收,所以以上三个区域不需要程序员来考虑垃圾回收的问题。堆内存和永久区是垃圾回收的重点区域,每个接口的多个实现类需要的内存是不一样的,每个方法嵌套的深度不同需要的内存也不一样,每个线程在执行过程中随时有变量不被引用,因此这两部分的垃圾回收是动态的,程序员可以通过JVM参数的设置来改变垃圾回收的频率和方式等。
什么样的对象会被判断为对后续程序无用可回收的?通俗来讲就是不被引用的,那么如何判断一个对象没有被引用有一下几种算法:
1.引用计数算法
在JDK1.2以前的版本采用这种算法,请参考以下代码:
User u1 = new User();
User u2 = u1;
User u3 = new User();
User对象创建了两次,他们被引用的数量分别为2,1,如下图:
如果此时,u3=null ; u1 = null;那么如果此时正好垃圾回收,那么两个对象的引用数为1,0,则第二个对象被回收,这种简单的通过统计对象被引用的数量的算法就是引用计数法。改方法虽然简单,但是无法解决循环应用的问题,如一下代码:
User u = new User();
Car c = new Car();
u.setCar(c);
c.setUser(u);
关系如下图:
此时,即使u = null; c = null; 那么在垃圾回收时User和Car对象也不会被垃圾回收,因为对于User对象,除了u变量引用之外,还有Car对象里面的属性在引用,同理Car对象也如此,基于此问题,在jdk1.2以后,采用了可达性算法。
2. 可达性算法
可达性算法也叫做根搜索算法,官方给出的定义是判断 每个对象的引用是否可达到根,根这个东西比较抽象,读者不必纠结它具体是什么,可以理解为一个对象引用链路的头节点,可以参考以下代码:
public static void main(String[] args){
User u = new User();
Car c = new Car();
Wheel w = new Wheel();
u.setCar(c);
c.setWheel(w);
}
他们的关系图如下:
如上图所示,new User(),new Car(),new Wheel()对象都可达Root节点,那么此时如果我们把
c =null,w=null,new Car(),new Wheel()仍然不会被回收,因为他们都可以通过User 这个对象的链路来达到Root,但是此时如果再把u.setCar(null),那么new Car(),new Wheel()将被回收,因为User这条链路也断了,这就解决了所谓的循环引用的问题。
3.finalize方法的作用
这里在顺便提一下finalize方法,这个放在在java开发中已经很少被用到了。当一个对象被标记为不可达将要被回收了,在回收前JVM会去执行当前对象的finalize方法,如果此时在finalize方法中,又用了一个引用指向了当前对象,是当前对象编程可达,那么当前对象就不会被回收,也就是当前对象被解救了,但是一个对象的finalize方法只能被执行一次。
三、垃圾回收是如何执行的?有哪些算法?
通过第二部分,我们已经知道判断一个对象是否可以被回收的规则,那么本节详细说明JVM是如何对不可达对象进行回收。
在第二部分中我提到,虚拟机栈、本地方法栈和计数器会自动随着方法或者线程的结束而自然的被回收,他们的垃圾回收不需要我们考虑,而永久区被占满后会出发Full GC,他的垃圾回收也不需要我们考虑,所以这里重点介绍堆的垃圾回收算法。
在第一部分提到,堆分为eden,to survivor,form survivor和年老代,其中eden,to survivor,form survivor这三部分称作年轻代,他们三者的默认大小比例是8:1:1,至于为什么是8:1:1,是因为在堆的年轻代采用的垃圾回收算法是--复制算法。
1.复制算法
在堆的年轻代存放的对象都是生命周期很短的对象,在任何时刻来看堆上的对象,其实存活的对象在2%左右(这个不难理解,例如运行一个非常大的main方法,在运行过程中我们看这个main方法,在运行过的代码中,有大量的临时变量已经没有了引用不可达,都是已经死了的)。当堆新创建一个对象时,对象会创建在eden 和 from survivor中,一旦满了之后就要进行垃圾回收,在进行垃圾回收的时候,上面已经说到,活着的对象在2%左右,jvm会把这2%的对象复制到to survivor中,因为三者的比例为8:1:1,也就是说to survivor的大小为10%,这足以存下2%的对象,然后jvm会清空eden 和 from survivor,此后再创建的对象就会放在eden 和 to Survivor中,满了以后再重复上述动作。如果一个对象从from survivor到to survivor复制了几次,那么这个对象就是生命周期很长的对象,就会被扔到年老代。复制算法过程如下图:
由于年轻代的对象生命周期短,每次复制对象仅有2%,仅开辟10%的to survivor空间,就可以完成复制,所以可以采用复制算法,那么年老带的对象由于对象的生命周期很长,如果也采用复制算法,那么每次复制的对象可能占到90%左右,这会浪费非常大的空间同时消耗cpu资源,所以在年老代采用的是整理算法。
2.整理算法:
整理算法过程就是在年老代的内存中,把存活对象的内存进行整理,保证存活和死亡的空间都是连续的,然后在对死亡的部分进行清空,过程如下:
3. 标记算法
这种算法缺点比较明显,主要过程就是把内存中的死亡对象进行释放,从而会导致脆片较多,空闲区域不连续,如果创建大对象,例如大数组,那么仍然可能导致oom。
相关推荐
前段时间做JVM调优网上搜集的一些资料,里面有图形化讲解JVM内存使用和各个参数详细介绍等
jvm内存模型以及垃圾回收机制.pptx
jvm内存模型以及垃圾回收机制详述
jvm内存模型,jvm脑图,jvm调优,jvm垃圾回收算法,jvm垃圾回收器,逃逸算法等总结。
第二节:JVM内存模型 1.1 概念 1.2 JVM内存模型 1.3 Heap堆内存模型 第三节:定位垃圾对象的依据 1.1 引用计数法 1.2 可达性算法 第四节:垃圾回收算法 1.1标记清除算法 1.2复制算法 1.3 标记整理(标记压缩)...
JVM的内存模型,垃圾回收,GC
很好的学习资料,很详细的讲述了JVM性能调优,JVM内存模型,垃圾回收原理算法等等,很适合JAVA程序员阅读。
jvm相关代码仓库,包括类加载机制,字节码执行机制,JVM内存模型,GC垃圾回收,JV-jvm_practice
很久之前就一直在学习JVM,但是一直也没有好好的总结,最近终于有了空闲,将之前学习的内容整理成了一个PPT。 也希望大神们可以批评指正。... Java内存模型 内存分配策略 分代垃圾收集 虚拟机工具 垃圾收集器
JVM内存模型以及垃圾回收,初级人员必看
JVM内存模型架构图,核心部分包括: GC主要在新生区(伊甸园区)、老年区 新生区(伊甸园区(对象都是在这个区new出来的)、幸存区to、幸存区from:幸存区位置会互相交换,谁空谁是to) 老年区 永久区:存储的是...
从JVM内存模型、常用JVM参数、垃圾回收算法和垃圾回收器等几个角度学习JVM
总共110讲,非常适合jvm初学者,希望对大家有所帮助,奋斗吧少年
非常好的HM JVM课件,尤其是字节码的部门,非常良心,搭配HM JVM视频使用更加哦。博客地址https://blog.csdn.net/hancoder/article/details/105210258
Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象...本文主要对java垃圾回收机制以及jvm参数等方面做个综述,也算是自己做开发这几年对这方面的一个总结。
在 HotSpot 虚拟机中,分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)对象头(Header):包含两部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程...
* 在垃圾回收章节,不仅会介绍垃圾回收算法、分代垃圾回收机制,还会重点介绍 G1 垃圾回收器,辨析 Full GC 发生条件,jdk8以来对垃圾回收的优化,以及垃圾回收的调优法则。 * 在字节码与类加载技术章节,会从一个 ...
文件总结了JVM的体系结构:四大块,类装载子系统(class loader subsystem),执行引擎子系统(Executionengine子系统),垃圾回收系统(gc),运行时数据区(JVM内存)。
1、JVM参数推荐 2、Java运行时数据区 3、JVM内存模型 4、堆的内存划分 5、垃圾回收(GC) 6、JVM参数汇总