目标
如何判断对象可以回收:
1. 引用计数法:
对象没有一个引用计算器,就+1。
缺陷:循环引用。如AB对象互相引用,但没有其他对象引用AB对象时,AB对象本该回收却不能回收
2. 可达性分析(根搜索)算法:
从根对象的点作为起始进行向下搜索,当一个对象到根对象没有任何引用链相连,则证明此对象是不可用的 。
根对象GC Root:肯定不能被垃圾回收的对象。然后扫描堆中所有对象,判断是否被根对象直接或间接引用,如果引用了就不能回收。如果没有被直接/间接引用,就可以当做垃圾回收。
3. 哪些对象可以作为 GC Root ?
Memory Analyzer (MAT)堆分析工具:
需要先使用jmap分析出堆内存,拿到快照,再由MAT进行分析。
jmap -dump:format=b,live,file=1.bin 【进程号】。
把bin文件导入MAT后,可以通过java Basics–GC Roots查看根对象。
dump:要把当前堆内存情况存储为一个文件
format=b:转出文件的格式,b表示二进制
live:只关心存活的,不关心垃圾回收的。自动在进行快照前会进行一次垃圾回收。
file=1.bin:文件名
测试:
package demo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*/
public class Demo {
public static void main(String[] args) throws IOException {
List<Object> list = new ArrayList<>();
list.add("a");
list.add("b");
System.out.println(1);
System.in.read();
list=null;
System.out.println(2);
System.in.read();
System.out.println("end...");
}
}
Microsoft Windows [版本 10.0.17134.619]
(c) 2018 Microsoft Corporation。保留所有权利。
C:\eclipse-workspace-photon\test-project>jps
1824
14132 AssestPayApplication
21960 RemoteMavenServer
16316 Jps
23916 Demo
C:\eclipse-workspace-photon\test-project>jmap -dump:format=b,live,file=1.bin 23916
Dumping heap to C:\eclipse-workspace-photon\test-project\1.bin ...
Heap dump file created
C:\eclipse-workspace-photon\test-project>jmap -dump:format=b,live,file=2.bin 23916
Dumping heap to C:\eclipse-workspace-photon\test-project\2.bin ...
Heap dump file created
C:\eclipse-workspace-photon\test-project>
- GC roots包括:
-
System Class:java.lang包下核心类,object,String类等
-
Native Stack:操作系统方法所引用的java对象
-
Thread:活动线程,栈针内局部变量所引用的对象
当执行第二次快照保存时:2.bin中,ArrayList已被回收,因为被置为null
-
Busy Monitor:正在被加锁的对象
-
4. 五种引用
https://www.jianshu.com/p/825cca41d962
我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。 很多系统的缓存功能都符合这样的应用场景。
我们把引用分为4种,用法如下:
Strong:默认通过Object o=new Object();这种方式赋值的引用
Soft、Weak、Phantom:这三种则都是继承Refrence。
如:SoftReference<byte[]> cacheRef = new SoftReference<>(410241024);
说明:
- 强引用Strong:
只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。我们平时new的对象都是强引用。强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用 对象来解决内存不足的问题。
演示强引用内存溢出:
package demo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2 {
private static final int _4MB=4*1024*1024;
public static void main(String[] args) {
//强引用
ArrayList<byte[]> list = new ArrayList<>();
for (int i=0;i<5;i++){
list.add(new byte[_4MB]);
}
}
}
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xmx20m -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo2
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
[GC (Allocation Failure) [PSYoungGen: 2494K->504K(6144K)] 14782K->13172K(19968K), 0.0014825 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) --[PSYoungGen: 4600K->4600K(6144K)] 17268K->17372K(19968K), 0.0010212 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4600K->4481K(6144K)] [ParOldGen: 12772K->12722K(13824K)] 17372K->17204K(19968K), [Metaspace: 3176K->3176K(1056768K)], 0.0078632 secs] [Times: user=0.09 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 4481K->4481K(6144K)] 17204K->17204K(19968K), 0.0010667 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4481K->4480K(6144K)] [ParOldGen: 12722K->12709K(13824K)] 17204K->17189K(19968K), [Metaspace: 3176K->3176K(1056768K)], 0.0073114 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 6144K, used 4743K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 84% used [0x00000000ff980000,0x00000000ffe21fa0,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 13824K, used 12709K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
object space 13824K, 91% used [0x00000000fec00000,0x00000000ff8694e0,0x00000000ff980000)
Metaspace used 3208K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 339K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
java.lang.OutOfMemoryError: Java heap space
at demo.Demo2.main(Demo2.java:17)
Process finished with exit code 1
- 软引用(SoftReference):
不必须配合引用队列使用,仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象可以配合引用队列来释放软引用自身。软引用是用来描述一些还有用但并非必须的对象。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,由于弱引用本身也占内存,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
package demo;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2 {
private static final int _4MB=4*1024*1024;
public static void main(String[] args) {
//强引用
// ArrayList<byte[]> list = new ArrayList<>();
// for (int i=0;i<5;i++){
// list.add(new byte[_4MB]);
// }
soft();
}
public static void soft(){
//软引用
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref=new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());//正常显示
list.add(ref);
System.out.println(list.size());
}
//内存不够进行了垃圾回收,软引用垃圾回收后内容扔不足就会把软引用扔掉
System.out.println("循环结束"+list.size());
for (SoftReference<byte[]> ref:list) {
//前4个都变为null
System.out.println(ref.get());
}
}
}
第五次循环一次full gc将软引用对象全部回收
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xmx20m -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo2
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
[B@6bc168e5
1
[B@7b3300e5
2
[B@2e5c649
3
[GC (Allocation Failure) [PSYoungGen: 2494K->488K(6144K)] 14782K->13180K(19968K), 0.0098736 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[B@136432db
4
[GC (Allocation Failure) --[PSYoungGen: 4809K->4809K(6144K)] 17502K->17578K(19968K), 0.0007970 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4809K->4502K(6144K)] [ParOldGen: 12768K->12630K(13824K)] 17578K->17132K(19968K), [Metaspace: 3180K->3180K(1056768K)], 0.0061679 secs] [Times: user=0.11 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 4502K->4502K(6144K)] 17132K->17132K(19968K), 0.0006207 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4502K->0K(6144K)] [ParOldGen: 12630K->733K(8704K)] 17132K->733K(14848K), [Metaspace: 3180K->3180K(1056768K)], 0.0061986 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[B@5f5a92bb
5
循环结束5
null
null
null
null
[B@5f5a92bb
Heap
PSYoungGen total 6144K, used 4320K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 76% used [0x00000000ff980000,0x00000000ffdb80b0,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8704K, used 733K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
object space 8704K, 8% used [0x00000000fec00000,0x00000000fecb7668,0x00000000ff480000)
Metaspace used 3191K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 337K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
将对象被回收的软引用本身也做清理:
package demo;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2 {
private static final int _4MB=4*1024*1024;
public static void main(String[] args) {
//强引用
// ArrayList<byte[]> list = new ArrayList<>();
// for (int i=0;i<5;i++){
// list.add(new byte[_4MB]);
// }
softQueue();
}
public static void soft(){
//软引用
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref=new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());//正常显示
list.add(ref);
System.out.println(list.size());
}
//内存不够进行了垃圾回收,软引用垃圾回收后内容扔不足就会把软引用扔掉
System.out.println("循环结束"+list.size());
for (SoftReference<byte[]> ref:list) {
//前4个都变为null
System.out.println(ref.get());
}
}
public static void softQueue(){
ReferenceQueue<byte[]> queue=new ReferenceQueue<>();//创建引用队列
//软引用
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
//当软引用所关联的的byte[]回收时,软引用自身就会被加入到queue中去。遍历时,就先到queue中查找
SoftReference<byte[]> ref=new SoftReference<>(new byte[_4MB],queue);
System.out.println(ref.get());//正常显示
list.add(ref);
System.out.println(list.size());
}
//每次取一个
Reference<?extends byte[]> poll=queue.poll();
while(poll!=null){
list.remove(poll);
//取下个
poll=queue.poll();
}
//内存不够进行了垃圾回收,软引用垃圾回收后内容扔不足就会把软引用扔掉
System.out.println("循环结束"+list.size());
for (SoftReference<byte[]> ref:list) {
//前4个都变为null
System.out.println(ref.get());
}
}
}
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xmx20m -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo2
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
[B@6bc168e5
1
[B@7b3300e5
2
[B@2e5c649
3
[GC (Allocation Failure) [PSYoungGen: 2494K->488K(6144K)] 14782K->13132K(19968K), 0.0437902 secs] [Times: user=0.01 sys=0.00, real=0.04 secs]
[B@136432db
4
[GC (Allocation Failure) --[PSYoungGen: 4809K->4809K(6144K)] 17454K->17454K(19968K), 0.0174478 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
[Full GC (Ergonomics) [PSYoungGen: 4809K->4580K(6144K)] [ParOldGen: 12644K->12552K(13824K)] 17454K->17132K(19968K), [Metaspace: 3186K->3186K(1056768K)], 0.0146108 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 4580K->4580K(6144K)] 17132K->17148K(19968K), 0.0054596 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4580K->0K(6144K)] [ParOldGen: 12568K->733K(9216K)] 17148K->733K(15360K), [Metaspace: 3186K->3186K(1056768K)], 0.0098571 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[B@7382f612
5
循环结束1
[B@7382f612
Heap
PSYoungGen total 6144K, used 4263K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffda9f78,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 9216K, used 733K [0x00000000fec00000, 0x00000000ff500000, 0x00000000ff980000)
object space 9216K, 7% used [0x00000000fec00000,0x00000000fecb7780,0x00000000ff500000)
Metaspace used 3197K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 337K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
- 弱引用(WeakReference):
不必须配合引用队列使用,仅有弱引用引用该对象时(没有任何强引用关联他),在垃圾回收时,无论内存是否充足,都会回收弱引用对象。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。由于弱引用本身也占内存,可以配合引用队列来释放弱引用自身。
package demo;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo3 {
private static final int _4MB=4*1024*1024;
public static void main(String[] args) {
List<WeakReference<byte[]>> list= new ArrayList<>();//弱引用
for (int i = 0; i < 5; i++) {
WeakReference<byte[]> ref=new WeakReference<>(new byte[_4MB]);
list.add(ref);
System.out.println("第"+(i+1)+"次循环");
for ( WeakReference<byte[]> w:list) {
System.out.println(w.get()+"");
}
System.out.println();
}
System.out.println("循环结束:"+list.size());
}
}
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xmx20m -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo3
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
第1次循环
[B@7b3300e5
第2次循环
[B@7b3300e5
[B@2e5c649
第3次循环
[B@7b3300e5
[B@2e5c649
[B@136432db
[GC (Allocation Failure) [PSYoungGen: 2607K->504K(6144K)] 14895K->13148K(19968K), 0.0011492 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
第4次循环
[B@7b3300e5
[B@2e5c649
[B@136432db
[B@7382f612
[GC (Allocation Failure) [PSYoungGen: 4712K->488K(6144K)] 17357K->13180K(19968K), 0.0009199 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
第5次循环
[B@7b3300e5
[B@2e5c649
[B@136432db
null
[B@1055e4af
循环结束:5
Heap
PSYoungGen total 6144K, used 4864K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc6218,0x00000000fff00000)
from space 512K, 95% used [0x00000000fff80000,0x00000000ffffa020,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 13824K, used 12692K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
object space 13824K, 91% used [0x00000000fec00000,0x00000000ff8652b0,0x00000000ff980000)
Metaspace used 3196K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 337K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
-
虚引用(PhantomReference):
必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存。
创建ByteBuffer实现类对象时,创建一个Cleaner虚引用对象,ByteBuffer会分配一块直接内存,并将直接内存地址传递给虚引用对象,将来ByteBuffer被垃圾回收,直接内存还没有回收,让虚引用对象进入引用队列,会由ReferenceHandler线程来定时扫描cleaner,调用Unsage。freeMemory来释放直接内存 -
终结器引用(FinalReference):
所有类都继承于Object父类,Object都有一个finalize 方法,当对象重写了finalize 方法,并没有被强引用,当垃圾回收时,,终结器引用入队(被引用对象暂时没有被回收)再由 FinalizerHandler 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象,工作效率低,不推荐使用释放资源
垃圾回收算法
- 标记、清除Mark-Sweep
- 标记、整理Mark-Compact
- 复制Copying
- 分代Generational
1 标记+清除
标记:哪些对象可以当成垃圾(不被GC Root间接引用的)+清除(是否标记的那些空间)。没有需要就标记为不需要了。
缺点:效率不高,两个过程效率都不高。会造成内存碎片。空闲的区域都是小碎片,放不了大的对象。空间碎片太多可能会导致后续事宜中无法找到足够的连续内存而提前触发另一次垃圾搜集动作。
2 标记+整理
清除碎片的过程中会把后面的对象往前移到可用的内存
定义:Mark Compact 没有内存碎片
缺点:速度慢,对象内存地址变了,引用这些对象的变量需要改变地址
3 复制
复制算法非常适合生命周期比较短的对象,因为每次GC总能回收大部分的对象,复制的开销比较哦小
根据IBM的专门研究,98%的Java对象只会存活1个GC周期,对这些对象很适合用复制算法,而且不用1:1的划分工作区和复制区的空间。
将可用内存划分为两块(两块Survivor区 To和From),每次只使用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来半块内存空间一次性清除掉,整理过程。清掉整块的速度非常快,但是浪费内存,一半不可用,
优点:即新生代和老年代。不会有内存碎片
缺点:需要占用双倍内存空间,在对象存活率较高的时候,效率有所下降。如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
4 分代垃圾回收机制
https://blog.csdn.net/hollis_chuang/article/details/91349868
分为:新生代+老年代
- 新生代:伊甸园Eden+幸存区From+幸存区To。Oracle Hotspot虚拟机默认比例是8:1:1。每次只有10%的内存是浪费的。可以通过-XX:SurvivorRatio=8调整,但是有自适应比较,可以通过-XX:-UseAdaptiveSizePolicy关掉。-Xmn可以设置新生代空间大小,但一般不设置Xmn
- 老年代:经历N次垃圾回收都存活的对象
新生代老年代默认比例:-XX:NewRatio=,(默认)老年代:新生代=2
-
分类过程:
- 对象首先分配在伊甸园区域,新生代空间(伊甸园)不足时,触发 minor gc,伊甸园和 from 中存活的对象复制到 to 中,存活的对象年龄+1,交换 from与to标识。当对象寿命超过== 最大寿命是15(4bit)阈值时,会晋升至老年代。当To区也满的时候也会放到老年代。minor gc 会引发 stop the world==,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。
- 当老年代空间不足,会先尝试触发== minor gc==,如果之后空间仍不足,那么触发 full gc,对新生代和老年代全部区域做一次垃圾清理。STW(Stop the World)的时间更长
老年区Full GC后还是不能保存对象,就触发内存不足OOM异常“OutOfMemoryError”。 - 经历多次GC后,存活的对象会在From和To之间来回存放,而这里面的一个前提则是这两个空间有足够的大小来存放这些数据,在GC算法中,会计算每个对象年龄的大小,如果达到某个年龄后发现总大小已经大于了幸存区空间的50%,那么这是就需要调整阈值,不能再继续等到默认的15次GC后才晋升。因为这样会导致幸存区空间不足,所以需要调整阈值,让这些存活对象尽快完成晋升。
-
相关参数
堆初始大小 -Xms
堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例 -XX:SurvivorRatio=ratio
晋升阈值 -XX:MaxTenuringThreshold=threshold
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC -
GC分析
package demo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
*/
public class Demo4 {
private static final int _512KB = 512*1024;
private static final int _1MB = 1*1024*1024;
private static final int _6MB = 6*1024*1024;
private static final int _7MB = 7*1024*1024;
private static final int _8MB = 8*1024*1024;
public static void main(String[] args) {
}
}
先运行一下:
- def new generation:新生代
total :9216K划分了10M,因为eden :from :to =8:1:1,因为to要空着,所以,只有9M可用 - tenured generation:老年代
- Metaspace:元空间
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo4
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
Heap
def new generation total 9216K, used 2882K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 35% used [0x00000000fec00000, 0x00000000feed0a48, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3182K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 336K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
- 加入一个7M左右的对象
package demo;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
*/
public class Demo4 {
private static final int _512KB = 512*1024;
private static final int _1MB = 1*1024*1024;
private static final int _6MB = 6*1024*1024;
private static final int _7MB = 7*1024*1024;
private static final int _8MB = 8*1024*1024;
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
list.add(new byte[_7MB]);
}
}
由于eden 区放不下,所以触发了一次minor gc,然后将对象放入eden 区
[GC (Allocation Failure) [DefNew: 2718K->821K(9216K), 0.0028769 secs] 2718K->821K(19456K), 0.0029872 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
GC (Allocation Failure):分配失败触发minor gc
DefNew:新生代
2718K(回收前新生代占用大小)->821K(回收后新生代占用大小)(9216K)(新生代总大小)
0.0028769 secs:花费时间
2718K->821K(19456K):回收后整个堆的内存占用情况
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo4
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
[GC (Allocation Failure) [DefNew: 2718K->821K(9216K), 0.0028769 secs] 2718K->821K(19456K), 0.0029872 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 8372K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 92% used [0x00000000fec00000, 0x00000000ff35faf8, 0x00000000ff400000)
from space 1024K, 80% used [0x00000000ff500000, 0x00000000ff5cd5d0, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3183K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 336K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
- 放512k
package demo;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
*/
public class Demo4 {
private static final int _512KB = 512*1024;
private static final int _1MB = 1*1024*1024;
private static final int _6MB = 6*1024*1024;
private static final int _7MB = 7*1024*1024;
private static final int _8MB = 8*1024*1024;
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
list.add(new byte[_7MB]);
list.add(new byte[_512KB]);
}
}
只回收一次,第一次放入7MB不够,回收一下,第二次放入512kb,空间够了,没有触发回收
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo4
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
[GC (Allocation Failure) [DefNew: 2718K->821K(9216K), 0.0016833 secs] 2718K->821K(19456K), 0.0017425 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 8884K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 98% used [0x00000000fec00000, 0x00000000ff3dfb08, 0x00000000ff400000)
from space 1024K, 80% used [0x00000000ff500000, 0x00000000ff5cd5d0, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3183K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 336K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
- 再放入512kb
package demo;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
*/
public class Demo4 {
private static final int _512KB = 512*1024;
private static final int _1MB = 1*1024*1024;
private static final int _6MB = 6*1024*1024;
private static final int _7MB = 7*1024*1024;
private static final int _8MB = 8*1024*1024;
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
list.add(new byte[_7MB]);
list.add(new byte[_512KB]);
list.add(new byte[_512KB]);
}
}
新生代垃圾回收后仍空间不足,将7MB的对象提前晋升老年代
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo4
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
[GC (Allocation Failure) [DefNew: 2718K->821K(9216K), 0.0016486 secs] 2718K->821K(19456K), 0.0017095 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 8829K->512K(9216K), 0.0047081 secs] 8829K->8428K(19456K), 0.0047349 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 1106K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 7% used [0x00000000fec00000, 0x00000000fec94930, 0x00000000ff400000)
from space 1024K, 50% used [0x00000000ff400000, 0x00000000ff480048, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 7916K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 77% used [0x00000000ff600000, 0x00000000ffdbb068, 0x00000000ffdbb200, 0x0000000100000000)
Metaspace used 3183K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 336K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
- 大对象直接晋升老年代
package demo;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
*/
public class Demo4 {
private static final int _512KB = 512*1024;
private static final int _1MB = 1*1024*1024;
private static final int _6MB = 6*1024*1024;
private static final int _7MB = 7*1024*1024;
private static final int _8MB = 8*1024*1024;
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);
}
}
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo4
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
Heap
def new generation total 9216K, used 2882K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 35% used [0x00000000fec00000, 0x00000000feed0a48, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000)
Metaspace used 3183K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 336K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 0
再放入8M对象
package demo;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenyc
* @create 2020-10-14 10:53
*
* -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
*/
public class Demo4 {
private static final int _512KB = 512*1024;
private static final int _1MB = 1*1024*1024;
private static final int _6MB = 6*1024*1024;
private static final int _7MB = 7*1024*1024;
private static final int _8MB = 8*1024*1024;
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);
list.add(new byte[_8MB]);
}
}
放入第二8mb对象时,先触发一次minor回收,发现新生代和老年代都空间不足,再次触发一次full gc后,仍不足,直接内存溢出
"C:\Program Files\Java\jdk1.8.0_45\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -javaagent:C:\Users\chenyuancheng\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;C:\eclipse-workspace-photon\test-project\jvm\target\classes;C:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" demo.Demo4
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
[GC (Allocation Failure) [DefNew: 2718K->821K(9216K), 0.0014666 secs][Tenured: 8192K->9012K(10240K), 0.0025054 secs] 10910K->9012K(19456K), [Metaspace: 3176K->3176K(1056768K)], 0.0040511 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 9012K->8997K(10240K), 0.0021811 secs] 9012K->8997K(19456K), [Metaspace: 3176K->3176K(1056768K)], 0.0022215 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 382K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 4% used [0x00000000fec00000, 0x00000000fec5fac8, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 8997K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 87% used [0x00000000ff600000, 0x00000000ffec97e0, 0x00000000ffec9800, 0x0000000100000000)
Metaspace used 3209K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 339K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at demo.Demo4.main(Demo4.java:24)
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'
Process finished with exit code 1
垃圾回收器
1. 串行Serial
单线程
堆内存较小,适合个人电脑
-XX:+UseSerialGC = Serial + SerialOld
Serial :新生代用复现算法
SerialOld:老年代用标记+整理算法
因为对象内存地址要移动,所以要STW
2. 吞吐量优先ParallelGC
多线程
让单位时间内,STW 的时间最短,例如1小时内共回收两次,每次 0.2 秒 ,共 0.4秒
垃圾回收时间占比最低,这样就称吞吐量高
多线程并行,和CPU核数相同,会在安全点暂停所有用户线程
1.8JDK默认开启此垃圾回收器,即ParallelGC
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC | UseParallelGC :新生代垃圾回收算法,复制算法,UseParallelOldGC:老年代垃圾回收算法,标记+整理算法 |
-XX:+UseAdaptiveSizePolicy | 采用自适应调整新生代大小,动态调整eden、from 和to的比例 |
-XX:GCTimeRatio=ratio | 调整吞吐量目标,垃圾回收时间与总时间占比, |
-XX:MaxGCPauseMillis=ms | STW暂时ms时间,默认200ms |
-XX:ParallelGCThreads=n | 控制垃圾回收线程数 |
-XX:PrintCommandLineFlags | 打印jvm启动参数 |
-XX:MaxTenuringThreshold=n | 在可以自动调节对象晋升(Promote)到老年代阈值的GC中,设置改阈值的最大值。 该参数的默认值为15,CMS中默认值为6,G1中默认是15,存活的对象会在From Survivor 和 To Survivor之间来回存放, 而这里面的前提则是这两个空间有足够的大小来存放这些数据,在GC算法中,会计算每个对象年龄的大小,如果达到某个年龄的大小 如果达到某个年龄后发现总大小已经大于了Survivor空间的50%,那么这时就需要调整阈值, 不能在继续等到默认的15次GC后才完成晋升 因为这样会导致Survivor空间不足,所有需要调整阈值,让这些存活对象尽快完成晋升。 |
GCTimeRatio:公式 1/1+ratio ,ratio默认99(一般设置19),例如程序工作了100分钟,那么只有1分钟可以用于垃圾回收,如果达不到这个目标,那么ParallelGC会调整堆的大小,来达到此目标,一般是增大堆内存,GC不频繁了,以达到目标,GCTimeRatio与:MaxGCPauseMillis一般是冲突的,堆大了,每一次回收的时间就变大了
3. 响应时间优先CMS(JDK9中被废弃)
CMS(Concurrent Mark Sweep)收集器,以获取最短回收停顿时间为目标,多数应用于互联网或者B/S系统的服务器端上
CMS是基于“标记—清除”算法实现的,整个过程分为4个步骤:
1.初始标记(CMS inital work)
2.并发标记(CMS concurrent mark)
3.重新标记(CMS remark)
4.并发清除(CMS concurrent sweep)
尽可能让单次 STW 的时间最短 ,例如1小时内回收5次,每次 0.1 秒 ,共 0.5秒
其中,初始标记、重新标记这两个步骤仍然需要stop the world
初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。
并发标记阶段就是进行GC Roots Tracing的过程;
重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
CMS垃圾回收器
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld | UseConcMarkSweepGC:标记+清除算法,并发执行,执行在老年代,但当并发失败时,例如产生了很多内存碎片,当加入一个比较大的对象时,发现新生代和老年代都无法存储这个对象,空降不足,就会退化到SerialOld ,单线程垃圾回收器,标记+整理算法,垃圾回收的时间会飙升,这是此垃圾回收器最大的问题,UseParNewGC,复制算法,工作在新生代 |
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads | ParallelGCThreads并行的垃圾回收线程数,一般为cpu核数,ConcGCThreads并发的GC线程数,一般为ParallelGCThreads的四分之一 |
-XX:CMSInitiatingOccupancyFraction=percent | 浮动垃圾,当并发清理时产生的垃圾,所以CMS不能像其他垃圾回收器一样,当堆内存不足的时候再去清理,那样新产生的浮动垃圾就没地方放了,所以要当老年代内存占整个内存达到一定比例时就回收,给浮动垃圾留出空间,一般会在65%-75%左右 |
-XX:+CMSScavengeBeforeRemark | 在重新标记的阶段,新生代可能又有对象要引用老年代的内存地址,但新生代中存在大量要回收的对象,如果每个对象都查找一遍,会浪费大量时间,所以在重新标记阶段之前,先用UseParNewGC复制算法先进行一次新生代的垃圾回收,再进行重新标记,这样扫描的对象就少很多 |
用户线程和垃圾回收线程可以并发的运行,在垃圾回收的一些阶段无需STW,减少了STW的时间
以 CMS 为例
CMS 的老年代内存越大越好
先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
-XX:CMSInitiatingOccupancyFraction=percent
优点:并非收集,低停顿
缺点:
CMS收集器对CPU资源非常敏感
CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Faliure”失败而导致另一次Full GC的产生,如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,要是CMS运行期间预留的内存无法满足程序需要时,虚拟机将启动后备预案:临时启用Serail Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了,所以说参数-XX:CMSInitatingOccupancyFractio设置太高很容易导致大量“Concurrent Mode Faliure”失败,性能反而降低。
搜集结束时会有大量的空间碎片产生,空间碎片过多时,将会给大对象分配带来很大麻烦,往往出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前进入一次Full GC,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数(默认是开启的),用于在CMS搜集器顶不住要进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片文件问题没有了,但是停顿时间不得不变长。
-
Phase 1: initial Mark这个是CMS两次stop-the-world事件的其中一次,这个阶段的目标是:标记那些直接被GC root引用或者被年轻代存活对象引用的所有对象
-
Phase 2: Concurrent Mark
在这个阶段Garbage Collector会遍历老年代,然后标记所有存活的对象,他会根据上个阶段找到的GC roots遍历查找,并发标记阶段,他会与用户的应用程序并发运行。并不是老年代所有的存活对象都会被标记,因为标记期间用户的程序可能会改变一些引用
-
Phase 3: Concurrent Preclean
这也是一个并发阶段,与应用的线程并发运行,并不会stop应用的线程,在并发运行的过程中,一些对象的引用可能会发生变化,但是这种情况发生时,JVM将包含这个对象区域(Card)标记为dirty,这也就是Card Marking
在pre-clean阶段,那些能够从dirty对象也会被标记,这些标记做完之后,dirty card 标记就会被清除了 -
Phase 4: Concurrent Aborable Preclean
这也是一个并发阶段,但是同样不会影响用户的应用线程,这个阶段是为了尽量承担STW(stop-the-world)中最终标记阶段的工作。这个阶段持续时间依赖于很多的因素,由于这个阶段是在做很多相同的工作,直接满足一些条件(比如:重复迭代的次数,完成的工作量或者时钟时间等) -
Phase 5: Final Remark
这是第二STW阶段,也是CMS中的最后一个,这个阶段的目标是标记老年代所有的存活对象,由于之前的阶段是并发执行的,GC线程可能跟不上应用程序的变化,为了完成标记老年代所有存活对象的目标,STW就非常有必要了。
通常CMS的final Remark阶段会在年轻代尽可能干净的时候运行,目的是为了减少持续STW发生的可能性(年轻代存活对象过多的话,也会导致老年代涉及的存活对象会很多)。这个阶段会比前面的几个阶段更复杂一些
标记阶段完成
经历过这5个阶段之后,老年代所有存活的对象都被标记过了,现在可以通过清除算法去清理那些老年代不再使用的对象 -
Phase 6: Concurrent Sweep
这里不需要STW,它是与用户的应用程序并发运行,这个阶段是:清除那些不再使用的对象,回收它们的占用空间为将来使用 -
Phase 7: Concurrent Reset
这个阶段也是并发执行的,它会重设CMS内部的数据结构,为下次CMS做准备
启动参数
-verbose:gc
-Xmx20m
-Xms20m
-Xmn10m
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:+UseConcMarkSweepGC
package com.cfm880.jvm_study.gc;
public class MyTest5 {
public static void main(String[] args) {
int size = 1024 * 1024;
byte[] myAlloc1 = new byte[4 * size];
System.out.println("111111");
byte[] myAlloc2 = new byte[4 * size];
System.out.println("222222");
byte[] myAlloc3 = new byte[3 * size];
System.out.println("333333");
byte[] myAlloc4 = new byte[3 * size];
System.out.println("444444");
}
}
> Task :MyTest5.main()
111111
[GC (Allocation Failure) [ParNew: 4603K->327K(9216K), 0.0032163 secs] 4603K->4425K(19456K), 0.0032554 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
222222
333333
[GC (Allocation Failure) [ParNew (promotion failed): 7656K->7752K(9216K), 0.0031638 secs][CMS: 8196K->8192K(10240K), 0.0026763 secs] 11754K->11548K(19456K), [Metaspace: 2636K->2636K(1056768K)], 0.0058690 secs] [Times: user=0.03 sys=0.01, real=0.01 secs]
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8192K(10240K)] 14620K(19456K), 0.0003429 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-mark-start]
444444
[CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Final Remark) [YG occupancy: 6918 K (9216 K)][Rescan (parallel) , 0.0003188 secs][weak refs processing, 0.0000074 secs][class unloading, 0.0001260 secs][scrub symbol table, 0.0002089 secs][scrub string table, 0.0001128 secs][1 CMS-remark: 8192K(10240K)] 15110K(19456K), 0.0008074 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 6918K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 84% used [0x00000007bec00000, 0x00000007bf2c1928, 0x00000007bf400000)
from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
concurrent mark-sweep generation total 10240K, used 8192K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 2643K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
BUILD SUCCESSFUL in 0s
4. G1
- 定义:Garbage First
2004 论文发布
2009 JDK 6u14 体验
2012 JDK 7u4 官方支持
2017 JDK 9 默认,废弃了之前的CMS垃圾回收器 - 适用场景
- 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
- 适合超大堆内存,会将堆划分为多个大小相等的 Region
- 整体上是 标记+整理 算法,两个区域之间是 复制 算法
- 特点是逻辑分代,物理不分代
- 相关 JVM 参数
-XX:+UseG1GC | 1.8需要开关开启UseG1GC |
-XX:G1HeapRegionSize=size | 每个Region的大小,并非最终值 |
-XX:MaxGCPauseMillis=time | 设置G1收集过程目标时间,默认200ms,不是硬性要求 |
-XX:G1NewSizePercent | 新生代最小值,默认是50% |
-XX:G1MaxNewSizePercent | 新生代最大值,默认是60% |
-XX:ParallelGCTgreads | STW期间,并行GC线程数 |
-XX:ConcGCThreads=n | 并发标记阶段,并行执行的线程数 |
-XX:InitialingHeapOccupancyPercent | 设置触发标记周期的Java堆占用率阈值,默认值是50%,这里的Java堆占比指的是non_young_capacity_bytes,包括old+humongous |
4.1 G1 垃圾回收阶段
G1提供了两种GC模式,Young GC和Mixed GC,这两种都是完全的Stop The World的。
-
Young GC:选定所有年轻代里的Region。通过控制年轻代的Region个数,即年轻代内存大小,来控制Young GC的时间开销。
-
Mixed GC:选定所有年轻代里的Region,外加根据Global Concurrent marking 统计德出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region
-
Mixed GC不是Full GC,它只回收部分老年代的Region,如果Mixed GC是在无法跟上程序上分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial old GC(Full GC)来收集整个GC heap。所以本质上,G1是不提供Full GC的
-
global concurrent marking的执行过程类似于CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。
-
global concurrent marking 的执行过程分为4个步骤:
- 初始标记(initial mark, STW):它标记了从GC Root开始直接可达的对象。
- 并发标记(Concurrent Marking):这个阶段从GC Root开始对heap中对象进行标记,标记线程与应用程序线程并发执行,并且搜集各个Region的存活对象信息。
- 重新标记(Remark STW):标记那些在并发标记阶段发生变化的对象,将被回收。
- 清理(Cleanup):清除空Region(没有存活对象的),加入到free list.
-
第一阶段inital mark是公用了Young GC的暂停,这是因为他们可以复用root scan操作,所以可以说global concurrent marking 是伴随Young GC而发生的。
第4阶段Cleanup只是回收了没有存活对象的Region,所以它不需要STW. -
G1在运行过程中的主要模式
- Young GC(不同于CMS)
- 并发阶段
- 混合模式
- Full GC(一般是G1出现问题是发生)
4.2 Young Collection
- 触发时机:Eden空间满时会被触发。
- 针对区域:Young GC主要是对Eden区进行GC
- 初始标记:先找到根对象,然后做可达性分析
- Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。
- Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。
- 最终Eden空间的数据为空,GC停止工作,应用线程继续执行。
- E到S有STW,时间短
-
巨型对象
- 一个对象大于 region 的一半时,称之为巨型对象
- G1 不会对巨型对象进行拷贝
- 回收时被优先考虑
- G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生,老年代card table引用此巨型对象为0,即可新生代垃圾回收时回收
- 代垃圾回收时处理掉
-
Young Collection 跨代引用
新生代回收的跨代引用(老年代引用新生代)问题
新生代的根对象有一部分来自老年代,这时如果遍历老年代很耗时,所以使用card table,如果老年代对象引用了新生代对象,就把老年代card table这块标记为dirty card。这样就不要找整个老年代了,减少搜索范围。下面粉色的是脏卡区。而E也知道有哪些脏卡引用它,记录在remember set中
4.3 Young Collection + CM
- 在 Young GC 时会进行 GC Root 的初始标记
- 初始标记是找到根对象,并发标记是顺着根对象找到其他对象。初始标记是新生代GC时发生,并发标记是老年代占用比例达到阈值时
- 触发时机:老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),进行并发标记(不会 STW),由下面的 JVM 参数决定
XX:InitialingHeapOccupancyPercent=percent控制,默认45%
并发标记必须在堆空间占满前完成,否则退化为 FullGC
JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent,无法调整,45%可能会导致频繁full GC或者频繁的并发标记
JDK 9 可以动态调整,-XX:InitiatingHeapOccupancyPercent 用来设置初始值,进行数据采样并动态调整
总会添加一个安全的空档空间
4.4 Mixed Collection
- 什么时候发生Mixed GC?
由一些参数控制,另外也控制这哪些老年代Region会被选入CSet(收集集合)
-
G1HeapWastePercent:
在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次Young GC之后和再次发生Mixed GC之前,会检查垃圾占比是否到达此参数,只有到达了,下次才会发生Mixed GC
G1MixeGCLiveThresholdPercent:old generation region 中的存活对象的占比,只有在此参数之下(少部分存活,大多数被回收,剩余的被复制),才会被选入CSet
G1MixedGCCountTraget:一次global concurrent marking之后,最多执行Mixed GC次数
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old generation region数量(收集老年代region的数量)
当越来越多的对象晋升到老年代时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region,这里需要注意:是一部分老年代(回收价值高的,这也是为什么叫g1的原因 ),而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制,根据的是最大暂停时间。也要注意的是Mixed GC并不是Full GC。
-
先将E区和S区的幸存对象复制到新的S区中,符合晋升条件的晋升到O区
-
将部分0区(不是全部0区,会根据最大暂停时间即-XX:MaxGCPauseMillis=ms,有选择的一些0区回收,回收价值最大的,如果时间足够则回收所有O区)和S区的对象复制到新的0区
它的GC步骤分2步:
全局并发标记(global concurrent marking)会 STW
拷贝存活对象(evacuation)会 STW
-XX:MaxGCPauseMillis=ms (垃圾回收最大暂停时间)
4.5 Full GC
SerialGC
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
ParallelGC
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
CMS
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足:
垃圾回收速度跟不上垃圾产生速度时,并发收集失败,此时CMS会退化为串行收集,
G1
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足:
- 触发时机:老年代与整个堆占比达到阈值45%,触发并发标记及混合收集。如果并发收集比垃圾产生快,这时还不叫full GC。但也会有重标记、拷贝的过程,暂停时间短,当垃圾产生快并发收集慢时,才会发生full gc
- JDK 8u20 字符串去重
优点:节省大量内存
缺点:略微多占用了 cpu 时间,新生代回收时间略微增加
-XX:+UseStringDeduplication(默认打开)
将所有新分配的字符串放入一个队列
当新生代回收时,G1并发检查是否有字符串重复
如果它们值一样,让它们引用同一个 char[]
注意,与 String.intern() 不一样- String.intern() 关注的是字符串对象
而字符串去重关注的是 char[]
在 JVM 内部,使用了不同的字符串表
- String.intern() 关注的是字符串对象
- JDK 8u40 并发标记类卸载
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸
载它所加载的所有类 -XX:+ClassUnloadingWithConcurrentMark 默认启用