jdk1.8的jvm内存模型最新总结

因为马上要准备实习面试了,而jvm又是面试常考的,我现在来总结一下jvm相关知识,而对于不同版本的jdk,又有许多不同的地方,为此我看了许多博客发现越看越晕,于是我只搜索了最近一年发表的jvm相关博客,并进行了整理。下面我将从这几个方面来介绍(都以jdk1.8为原型)

  • 1.java内存模型
  • 2.常见gc算法
  • 3.何时进行gc
  • 4.4种引用类型

一.jvm内存模型

首先看看官方的内存模型图片:图片来自《Java虚拟机规范(第2版)》

这是大家所熟知的java内存模型,分为5个区域,下面说说我对他的认识:

1.程序计数器:首先它是线程私有的,是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,因为jvm是多线程的,它为了确保线程切换后能恢复到正确的执行位置。

2.虚拟机栈:首先它也是线程私有的,当一个线程调用一个方法的时候,都会创建一个栈帧(Statck Frame),栈帧里面存放着当前方法的局部变量以及对象的引用和方法出口等。所以我们在用递归的时候经常会出现栈溢出的这种情况——线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出)。

3.本地方法栈:与虚拟机栈类似,不过只能执行native修饰的方法。

4.堆:是jvm内存最大的一块区域,它是线程所共享的,所有通过new出来的对象实例都存放在着,包括数组,数组也是一个特殊的类嘛。堆又称gc堆,按照jvm回收策略又分为老年代和新生代。jdk1.7及其以上把字符串常量池放在了堆中。

5.方法区:这个区域也是争议最大的,我先说说它的作用,首先它是线程共享的,它的作用是,用于存储类信息、常量池、静态变量,JIT编译后的代码等数据。常量池它又分为字符串常量池(堆中),class文件常量池,运行时常量池(这两种常量池后面介绍)。方法区其实它只是一个概念,实际是不存在的,在jdk1.8之前方法区也叫做永久代,而在jdk1.8引入一个元空间概念,方法区是由元空间实现的,而元空间是在本地内存中,意思它不使用原来的jvm中的内存。因此,默认情况下,元空间的大小仅受本地内存限制,但也可以通过参数来指定元空间的大小。虽然改了个名字,但是方法区的本质作用它还是没变的。

什么是class文件常量池

        在java代码的编译期间,我们编写的.java文件就被编译为.class文件格式的二进制数据存放在磁盘中,其中就包括class文件常量池。class 文件中存在常量池(非运行时常量池),其在编译阶段就已经确定。class文件常量池主要存放两大常量:字面量符号引用。什么是字面量和符号引用这为以后加载到jvm内存中打下基础;

什么是运行时常量池

     运行时常量池是方法区的一部分,所以也是全局共享的。我们知道,jvm在执行某个类的时候,必须经过加载、连接(验证,准备,解析)、初始化,在第一步的加载阶段,虚拟机需要完成下面3件事情:

  • 通过一个类的“全限定名”来获取此类的二进制字节流
  • 将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构
  • 在内存中生成一个类代表这类的java.lang.Class对象,作为方法区这个类的各种数据访问的入口

  这里需要说明的一点是,类对象和普通的实例对象是不同的,类对象是在类加载的时候生成的,普通的实例对象一般是在调用new之后创建。

  上面第二条,将class字节流代表的静态储存结构转化为方法区的运行时数据结构,就是将class文件加载到运行时常量池

       运行时常量池的作用是存储 Java class文件常量池中的符号信息。运行时常量池 中保存着一些 class 文件中描述的符号引用,同时在类加载的“解析阶段”还会将这些符号引用所翻译出来的直接引用(直接指向实例对象的指针)存储在 运行时常量池 中。


二.gc算法

1.引用计数法:(Java没有采用)

引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

2. 标记-清除法 (jvm老年代回收)

标记-清除算法分为两个阶段,标记(mark)和清除(sweep).

在标记阶段,collector从mutator根对象开始进行遍历,对从mutator根对象可以访问到的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象。

而在清除阶段,collector对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的header信息,则就将其回收。

优点:解决了循环引用问题

缺点:会产生内存碎片

3.标记-整理算法

 标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

4.copy算法(适用于新生代)

将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另外一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾回收。

优点:在垃圾对象多的情况下,效率较高。清理后,内存无碎片。

缺点:在垃圾对象少的情况下,不适用,如:老年代内存。分配的2块内存空间,在同一个时刻,只能使用一块,内存使用率较低。

5.分代收集算法(现在jvm普遍使用的算法)

jvm将对象划分为3个年龄段 新生代,老龄代,永久代,根据不同年龄代采取不同算法。

年轻代:(例如:方法的局部变量等)又细分为3个区域,一个Eden区和两个Survivor区,它们之间的比例为(8:1:1),这个比例也是可以修改的。通常情况下,对象主要分配在新生代的Eden区上,少数情况下也可能会直接分配在老年代中。Java虚拟机每次使用新生代中的Eden和其中一块Survivor(From),在经过一次Minor GC(Minor GC年轻代回收算法)后,将Eden和Survivor中还存活的对象一次性地复制到另一块Survivor空间上(这里使用的复制算法进行GC),最后清理掉Eden和刚才用过的Survivor(From)空间。将此时在Survivor空间存活下来的对象的年龄设置为1,以后这些对象每在Survivor区熬过一次GC,它们的年龄就加1,当对象年龄达到某个年龄(默认值为15)时,就会把它们移到老年代中。 

老年代:(例如:缓存对象、单例对象等)老年代中使用“标记-清除”或者“标记-整理”算法进行垃圾回收,回收次数相对较少,每次回收时间比较长

永久代:(例如:加载过的类信息)永久代指的是虚拟机内存中的方法区,而在jdk1.8后已经移除了这个概念,而是叫做元空间。

移除永久代和增加元空间的改变:在永久代时期经常由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。,理论上取决于32位/64位系统可虚拟的内存大小。


三.何时进行gc

首先需要知道,GC又分为 minor GC(年轻代gc) 和 Full GC (也称为 Major GC 老年代gc)

这个时间是不确定的

1.那么对于 Minor GC 的触发条件:大多数情况下,直接在 Eden 区中进行分配。如果 Eden区域没有足够的空间,那么就会发起一次 Minor GC;

2.对于 Full GC(Major GC)的触发条件:也是如果老年代没有足够空间的话,那么就会进行一次 Full GC。

3.调用Systm.gc() ,如果是强引用不会被回收,软引用空间不够才会回收,弱引用调用System.gc();会被回收,虚引用,不管回不回收丢拿不到它的引用

不过不到万不得已不要调用,因为jvm有自己的gc策略,根本不需要我们来手动。


四.强引用,软引用,弱引用,虚引用

4种引用类型

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值