JVM内存模型以及垃圾回收算法的基本认识

 

在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的;如下图所示

JVM内存模型以及垃圾回收算法的基本认识_第1张图片

 

 

程序计数器:字节码的行号指示器。

作用:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

 

虚拟机栈

私有,生命周期=线程;java执行的内存模型,每次方法调用的数据都是通过栈传递的

组成:由一个个栈帧组成,每个栈帧有:局部变量表、操作数栈、动态链接、方法出入口信息

局部变量表:存放各种数据类型、对象引用

 

本地方法栈

和虚拟机栈相似,区别:虚拟机栈为虚拟机执行方法服务,本地方法栈为虚拟机使用到的Native方法服务

 

内存中最大的一块,虚拟机启动时创建

目的:存放对象实例,数组

垃圾回收的主要区域

 

方法区,存放

1.类的信息(名称、修饰符等)、

2.类中的静态常量、

3.类中定义为final类型的常量、

4.类中的Field信息、

5.类中的方法信息

分为常量池

字符串、final变量

类和接口名,字段名,描述符,方法名

静态变量

父类静态成员变量,子类静态成员变量,父类构造方法,子类构造方法

 

元空间:类信息;编译代码

 

主内存和工作内存

JVM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成

那么两个线程之间如何能够达到可见性,可以参考文章volatile的使用:https://blog.csdn.net/Goligory/article/details/89648177

 

内存溢出原因:

垃圾过多jvm没有及时回收

1.引用变量过多使用static

2.大量的递归和无限递归(递归中新建对象)

3.大量循环(循环中新建对象)/ 死循环产生过多实体

4.数据库查询一次性查所有记录超过10万条可能造成溢出

5.String 是否使用过多+的操作

启动参数内存值设定过小

 

栈溢出原因:

1.是否递归调用,递归调用方法

2.大量循环/死循环

3.全局变量是否过多

4.数组,list,map是否过大

堆溢出:递归new对象

 

内存泄漏

申请内存后,无法释放已申请的内存空间,内存泄漏堆积后果很严重

1.常发性内存泄漏:多次执行

2.偶发性内存泄漏:

3.一次性内存泄漏:只执行一次,如构造函数中分配内存没有释放

4.隐式内存泄漏:程序不停分配内存,结束后才释放内存,严格说没有泄漏,但是如果不及时释放也可能导致耗尽所有内存

泄漏原因

长生命周期对象持有短生命周期对象导致持有其引用不能回收,发生内存泄漏

1.静态集合类引起内存泄漏:如HashMap,vector

2.监听器,释放对象时没有删除

3.各种链接没有close

4.单例模式持有外部对象引用

 

 

============GC垃圾回收=================================================================

垃圾回收我们要熟悉,我们要知道它是如何工作的,为什么这么工作,好处是什么?

堆是GC的主要区域

jdk1.8之后永久代改成了元空间,元空间使用的是直接内存

JVM内存模型以及垃圾回收算法的基本认识_第2张图片

 

JVM如何确定哪些对象应该进行回收?

两种方式

1.引用计数法:给对象加计数器,用时+1,不用-1,但是无法解决对象之间循环引用的问题,java没用

2.可达性分析算法:

通过一些被称为引用链GC Roots的对象作为起点,从这些节点开始向下搜索,走过的路径被成为Reference Chain,当一个对象到GC Roots没有任何引用链相连时(即从GC Roots到该节点不可达),证明该对象是不可用的

可作为GC Root的对象包括一下几种:

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

知道了需要回收的对象,那什么时候回收?怎么回收?


什么时候进行回收?

cpu空闲时自动回收

堆内存满了后

主动调用System.gc()

 

3大回收算法

1.标记/清除算法【最基础】
2.复制算法
3.标记/整理算法
jvm采用`分代收集算法`对不同区域采用不同的回收算法。

 

  • 标记-清除算法:先标记,后清除,标记所有需要回收的对象,后统一回收带有标记得对象,简单,但是缺乏效率,空间问题,标记清除后产生大量不连续的内存碎片,当程序运行时需要分配较大对象时无法找到足够的连续内存而造成空间浪费
  • 标记-整理算法:和标记-清除算法类似,区别在于标记-清除对于剩下的对象不进行操作造成内存碎片,标记-整理在清楚后把剩下的对象进行整理,不会产生内存碎片

JVM内存模型以及垃圾回收算法的基本认识_第3张图片

 

新生代:复制算法

Eden,From Survivor,To Survivor

java对象一般在新生代出生,新生代也是GC的频繁区域。深入理解JVM虚拟机上说98%的对象存活率很低,适用于复制算法

它优化了标记/清除算法的效率和内存碎片问题,且JVM不以5:5分配内存【由于存活率低,不需要复制保留那么大的区域造成空间上的浪费,因此不需要按1:1【原有区域:保留空间】划分内存区域,而是将内存分为一块Eden空间和From Survivor、To Survivor【保留空间】,三者默认比例为8:1:1

目标尽快收集掉生命周期短的对象,一般情况Eden区中新生成的对象首先放在新生代。垃圾回收时,先将eden区存活的对象复制到survivor0区,然后清空eden区,当survivor0区满了时,将eden区和survivor0区存活对象复制到survivor1区,然后清空eden和survivor0区,交换survivor0区和survivor1区角色,下次垃圾回收时会扫描eden区和survivor1区,如此往复。

如果当survivor1区也不足以放eden区和survivor0区的存活对象,就将存活对象直接存放到老年代。如果老年代也满了,就会触发一次FullGC,也就是新生代、老年代都回收。

新生代发生的GC也叫MinorGC,MinorGC发生频率比较高,不一定等eden区满了才触发

 

老年代:标记-清除;标记-整理

  • 堆大小:新生代+老年代。堆大小可根据-xms(堆初始容量),-xmx(堆最大容量)来指定
  • 新生代默认Eden:from:to=8:1:1
  • jvm每次只跟Eden和其中一块Survivor区域为对象服务,无论什么时候,总有一块Survivor是闲着的
  • 新生代实际可用的内存空间为9/10的新生代空间

老年代存放的都是生命周期较长的对象,在新生代中经历了n次垃圾回收后仍然存活的对象就会被放到老年代中

 

永久代

主要存放静态文件,如java类,方法等。永久代对垃圾回收没有显著影响。但是反射,动态代理,CGLib等可能动态生成class文件,这时需要设置比较大的永久代空间存放

大对象、长期存活的对象进入老年代

 

注意:jdk1.8废弃了永久代,提供了相似的元空间技术

去永久代的原因有: 

(1)字符串存在永久代中,容易出现性能问题和内存溢出。 

(2)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。 

(3)永久代会为 GC 带来不必要的复杂度,并且回收效率偏低

 

JVM参数

堆内存最小和最大指定

-Xms2G -Xmx5G

 

将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。

 

显式新生代内存  两种指定方式

1.-XX:newSize=256m

-XX:MaxNewSize=1024m

2.-Xmn256m

 

显示指定元空间大小

jdk8要指定metabase大小,因为使用直接内存,虚拟机可能会耗尽系统内存

 

垃圾回收器

1.串行垃圾回收器

2.并行垃圾回收器

3.CMS垃圾回收器

4.G1垃圾回收器

堆参数

JVM内存模型以及垃圾回收算法的基本认识_第4张图片

 

回收器参数

 

JVM内存模型以及垃圾回收算法的基本认识_第5张图片

常用命令

 

JVM内存模型以及垃圾回收算法的基本认识_第6张图片

GC调优原则

多数的 Java 应用不需要在服务器上进行 GC 优化; 多数导致 GC 问题的 Java 应用,都不是因为我们参数设置错误,而是代码问题; 在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合); 减少创建对象的数量; 减少使用全局变量和大对象(占用连续内存空间); GC 优化是到最后不得已才采用的手段; 在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多

 

调优策略

1.新对象预留在新生代,通过-Xmn调价新生代大小,最大限度降低新对象直接进入老年代的情况

2.大对象进入老年代

3.合理设置老年代对象的年龄-XX:MaxTenuringThreshold

4.设置稳定的堆大小

5.注意:满足下面说法则不需要GC优化

MinorGC 执行时间不到50ms; Minor GC 执行不频繁,约10秒一次; Full GC 执行时间不到1s; Full GC 执行频率不算频繁,不低于10分钟1次

 

 

 

 

引用:https://www.cnblogs.com/shenjianjun/p/9512949.html

           GC https://www.cnblogs.com/ITPower/p/7929010.html

这个引用蛮全的推荐:https://www.jianshu.com/p/76959115d486

你可能感兴趣的:(【JVM】,jvm,垃圾回收,内存模型)