目录
1、什么是垃圾
2、如何定位垃圾
reference count
Root Searching
3、常见的垃圾回收算法
Mark-Sweep(标记清除)
Copying(拷贝算法)
Mark-Compact(标记压缩)
4、JVM内存分代模型(用于分代垃圾回收算法)
堆内存逻辑分区
5、垃圾回收器
Serial
Parallel Scavenge
ParNew
SerialOld
Parallel Old
CMS
6、JVM参数
7、思维导图
内存分配与回收方式:
C语言:malloc、free
C++:new、delete
Java:new 自动回收内存
自动回收内存系统不容易出错,手动回收内存,容易出现以下的错误:
忘记回收
多次回收
垃圾的定义:没有任何引用指向的一个对象或者多个对象(循环引用)。
当把成员变量设置为空(null)之后,不再指向任何引用对象,那么该对象就被称作垃圾:
还有一种情况,多个对象之间互相引用,但是没有其他的引用指向这个循环的对象。
当引用计数变为0的时候,这个对象就成为垃圾了。但是引用计数不能解决对象循环引用
如下:
每个引用计数都是1,但是它们全部是垃圾,所以用引用计数的方式的话,这些垃圾就找不到了,会发生内存泄漏。
根可达或者根搜索算法。
通过程序找到一些根对象,通过根对象找到它所连接的那些对象不是垃圾,其他的都是垃圾。
Roots:线程栈变量、静态变量、常量池、JNI指针
将可回收的对象标记为非垃圾。
缺点:位置不连续,产生内存碎片。
内存一分为二,将存活对象复制到未使用的内存中,原内存全部标记为可使用;新分配内存时先分配存活对象所在的那段内存,垃圾回收时,重复上述操作。
特点:没有碎片,但是内浪费空间。最大的问题:内存浪费。
将存活对象依次复制到垃圾对象和未使用的区域中,结合了标记清除和拷贝的做法,但是效率比copy略低。
三种方法找垃圾的效率是一致的,区别在于找到垃圾后对其进行整理的方式。拷贝算法是内存拷贝,是线性地址的拷贝,速度很快的,效率很高。但是压缩算法却不这么简单,因为任意一个内存进行移动时,如果是多线程, 都要进行线程同步;如果是单线程,那单线程的效率本来就低。 所以任何一块内存挪动都要进行线程同步,所以效率肯定是很低的。
目前,生产环境中普遍使用的是JDK1.7或JDK1.8,根据JDK版本不同,分代也不同。
JVM中分代:新生代+老年代+永久代(JDK1.7)/元数据区(JDK1.8)Metaspace。
之所以新生代中按照这个比例分配,是因为eden区在GC的时候,90%的对象都会被回收,剩下的存活对象在survivor区是可以放下的。
当创建一个对象时,默认会去找eden区, 如果对象特别大,eden区装不下则直接进入老年代。
新生代 = Eden + 2个survivor区(survivor0、survivor1):
老年代:
GC Tuning:尽量减少FGC。
垃圾回收的时候,程序是无法执行的。stop-the-world(STW)是停止程序运行,回收线程开始运行,回收结束后程序再接着运行。
并行回收,多个线程同时进行垃圾回收。
配合CMS的年轻代并行回收。
单线程回收算法用于old区域
多线程回收算法用于old区域
ConcurrentMarkSweep,用于回收老年代,在垃圾回收的同时程序也能运行。(黄色的表示垃圾回收线程,蓝色表示程序执行线程)
调优针对的是Serial、Parallel Scavenge和Serial Old、Parallel New,因为JDK1.8默认的垃圾回收:Parallel Scavenge + Parallel Old。
JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
JVM参数分类:
参考文档:
https://blogs.oracle.com/jonthecollector/our-collectors
https://blogs.oracle.com/jonthecollector/why-not-a-grand-unified-garbage-collector