参考: https://blog.csdn.net/xxxx3/article/details/81009524
https://mp.weixin.qq.com/s/yz631xKPv-RUUE9t4xuWPQ
https://blog.csdn.net/justloveyou_/article/details/71216049
java垃圾回收,就是指jvm内存回收机制,这就要说到内存溢出了
jvm运行时数据区:分为5个部分,分别是 方法区,堆,栈,本地方法栈,程序计数器,
其中程序计数器是唯一一个不会内存溢出的地方,另外方法区和堆是所有线程共享的,其余三个是线程独有的
能够发生内存溢出(OutOfMemoryError)的地方:堆,栈,方法区,
通常栈的内存溢出叫
: java.lang.StackOverflowError 原因: 线程请求的栈深度超过虚拟机所允许的最大深度
堆的内存溢出是
: java.lang.OutOfMemoryError: Java heap space 原因: 堆内存空间满了,不能创建新的对象
方法区溢出:
OutOfMemoryError: PermGen space 原因: 虚拟机加载了大量的jar或class,导致方法区空间不足
还有一种线程错误: OutOfMemoryError:unable to create new native thread 可能原因(1)在程序里面使用了http-client,http-client 有两个超时参数,但实际只设置了一个参数,然后当对方的服务器宕机之后,httpClient一直处于等待状态,导致线程堆积。
(2)做了多线程,而每个线程的耗时过多导致的(新增线程的速度比处理完成线程的速度快)
JVM垃圾回收的场所:
虚拟机栈溢出(本地方法栈):
与线程栈相关的内存异常有两个:
a)StackOverflowError(方法调用层次太深,内存不够新建栈帧)
b)OutOfMemoryError(线程太多,内存不够新建线程)
堆(重点讲):
java.lang.OutOfMemoryError: Java heap space
导致堆溢出的可能有,
1,死循环,内存无法释放
2,创建太多对象
堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:所有的对象实例及数组都在对上进行分配。
jdk1.8后,字符串常量池从永久代中剥离出来,存放在其中。
存储的是我们new来的对象,不存放基本类型和对象引用。
由于创建了大量的对象,垃圾回收器主要工作在这块区域。
https://s4.51cto.com/images/blog/201808/21/b116170771ecb3117ae7fead03fcaa0d.png
jvm内存分为:堆和非堆(方法区)
堆内存分为年轻代(Young Generation)和年老代(Old Generation)
年轻代又分为生成区(Eden)和幸存区(Survivor)
幸存区由FromSpace和ToSpace组成
年轻代,FromSpace,ToSpace比例默认是8:1:1
Class文件中的信息被字节码执行引擎加载到了方法区,从而形成了运行时常量池。
方法区(重点讲):
方法区只是一个概念,可以由永久代(JDK1.7)或元空间(JDK1.8)实现,
JDK1.8的元空间并不在JVM中而是使用本地内存(有了元空间,方法区就不会OOM了)
元空间有俩参数,最大初始化内存和最大内存,
JVM是如何进行垃圾回收的:
当需要创建一个对象的时候,会去运行时常量池寻找该类的,如果没有被加载,就会先加载该类的信息,
然后生成新的对象首先放到年轻代(Eden),当Eden满了之后,就回触发Minor GC,存活下来的对象会被移动到幸存区(Survivor)(S0)
当S0满了会触发,会触发Minor
GC.对S0进行垃圾回收,存活的对象移到S1,S0被清空,经历一定次数(默认15)的Minor GC,会被移动到老年代(Old Generation)
老年代存储长期存活的对象,当老年代被占满的时候,会触发Major GC(等于Full GC) (关于Major GC和Full GC可以参考: https://blog.csdn.net/g7n3f/article/details/50829503)
给大对象分配内存的时候,Eden区已经没有足够的内存空间了,大对象就会直接进入老年代
Stop The World: Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)
所有的收集器都会STW,但是full gc(Major)的时间远远大于minor gc的时间
为了避免卡顿,就要尽量减少Major的发生
Full GC会清理整个内存空间针对整个新生代、老生代、方法区
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
垃圾回收器: 参考: https://blog.51cto.com/lizhenliang/2164876
串行收集器(Serial): 比较老的收集器,单线程。收集时,必须暂停应用的工作线程,直到收集结束。 (复制算法)
并行收集器(Parallel): 多条垃圾收集线程并行工作,在多核CPU下效率更高,应用线程仍然处于等待状态。(复制)
Serial Old :老年代 标记整理
CMS收集器(Concurrent Mark Sweep):CMS收集器是缩短暂停应用时间为目标而设计的,是基于标记-清除算法实现
Parallel Old: 老年代,标记整理
G1收集器(Garbage First):全部,复制标记整理
垃圾回收算法:
标记-清除(Mark-Sweep):
GC分为两个阶段,标记和清除。首先标记所有可回收的对象,在标记完成后统一回收所有被标记的对象。
同时会产生不连续的内存碎片。碎片过多会导致以后程序运行时需要分配较大对象时,无法找到足够的连续内存,而不得已再次触发GC。
复制(Copy):
将内存按容量划分为两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。
这样使得每次都是对半个内存区回收,也不用考虑内存碎片问题,简单高效。缺点需要两倍的内存空间。
标记-整理(Mark-Compact):
也分为两个阶段,首先标记可回收的对象,再将存活的对象都向一端移动,然后清理掉边界以外的内存。
此方法避免标记-清除算法的碎片问题,同时也避免了复制算法的空间问题。