jvm笔记

一塔 2021-04-20 PM 19℃ 0条

1 内存结构

虚拟机栈

定义

  1. 虚拟机栈:每个线程运行时所需要的内存
  2. 每个栈由多个栈帧组成,栈帧即每个方法调用所需要的内存
  3. 每个线程只有一个活动栈帧,对应当前正在执行的方法

问题?

  1. 垃圾回收是否涉及栈内存?

不会,栈内的方法在执行的时候会自动出栈,即释放了方法所占用的内存,所以不需要进行垃圾回收

  1. 方法内的局部变量是线程安全的吗?

    • 如果方法内的局部变量没有逃离方法的作用范围(即该局部变量自始至终都只是在内部代码块使用,没有作为返回值,或者入参),那么它是线程安全的
    • 如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全

栈内存溢出

线程诊断

  • 用top命令定位哪个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack 进程id

jstack发现死锁

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007eff5c004e28 (object 0x00000000e345d778, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007eff5c0062c8 (object 0x00000000e345d788, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at DeadLockDemo.lambda$main$1(DeadLockDemo.java:23)
    - waiting to lock <0x00000000e345d778> (a java.lang.Object)
    - locked <0x00000000e345d788> (a java.lang.Object)
    at DeadLockDemo$$Lambda$2/303563356.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)
"Thread-0":
    at DeadLockDemo.lambda$main$0(DeadLockDemo.java:11)
    - waiting to lock <0x00000000e345d788> (a java.lang.Object)
    - locked <0x00000000e345d778> (a java.lang.Object)
    at DeadLockDemo$$Lambda$1/471910020.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

本地方法栈

调用本地方法时提供的内存

堆内存诊断

  1. jps

    • 查看当前系统中有哪些java进程
  2. jmap工具

    • 查看堆内存占用情况
  3. jconsole工具

    • 图形界面,可以连续监测
  4. jvisualvm

    • 更强大的可视化工具,也可以抓取堆转储

测试代码:

public class JMapTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(20000);
        byte[] array = new byte[1024 * 1024 * 10];

        System.out.println("2...");
        Thread.sleep(20000);
        array = null;
        System.gc();

        System.out.println("3...");

        Thread.sleep(40000);
    }
}

使用jps命令查看进程 : jps

16164 Jps
14664 JMapTest
9496
11244 Launcher

jmap查看堆信息jmap -heap 14664

第一次执行:(1...)

Eden Space:
capacity = 34078720 (32.5MB)
used = 3409696 (3.251739501953125MB) // System.out.println("1...");的时候使用情况
free = 30669024 (29.248260498046875MB)
10.005352313701923% used

第二次执行:(2...),分配了10M的内存之后

Eden Space:
capacity = 34078720 (32.5MB)
used = 13895472 (13.251754760742188MB) //System.out.println("2...");
free = 20183248 (19.248245239257812MB)
40.77463003305289% used

第三次执行:(3...),gc之后

Eden Space:
capacity = 34078720 (32.5MB)
used = 1363184 (1.3000335693359375MB) //System.out.println("3...");
free = 32715536 (31.199966430664062MB)
4.000103290264423% used

方法区

常量池

/**
 * javap -v Demo1 查看反编译二进制字节码
 * E:\idea-work\spring-framework\jvm-demo\target\classes\com\halouit\constantpool>javap -v Demo1
 */
public class Demo1 {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

StringTable

  1. 查看字符串的字节码
//StringTable ["a","b","ab"] hashtable,不能扩容
public class Demo2 {
    //常量池信息都会被加载到运行时常量池
    public static void main(String[] args) {
        // ldc           #2  ldc会把符号 a  变为 字符串 “a”
        String a = "a";
        String b = "b";
        String c = "c";
    }
}

public static void main(java.lang.String[]); //先看main方法
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1

0: ldc           #2                  // String a
2: astore_1
3: ldc           #3                  // String b
5: astore_2
6: ldc           #4                  // String c
8: astore_3
9: return

LineNumberTable:
line 5: 0
line 6: 3
line 7: 6
line 8: 9
LocalVariableTable: //类似于栈帧
Start Length Slot Name Signature

   0      10     0  args   [Ljava/lang/String;
   3       7     1     a   Ljava/lang/String;
   6       4     2     b   Ljava/lang/String;
   9       1     3     c   Ljava/lang/String;
  1. 当输入String ab = a + b;//拼接ab 之后
//StringTable ["a","b","ab"] hashtable,不能扩容
public class Demo2 {
    //常量池信息都会被加载到运行时常量池
    public static void main(String[] args) {
        // ldc           #2  ldc会把符号 a  变为 字符串 “a”
        String a = "a";
        String b = "b";
        String c = "c";
        String ab = a + b;//拼接ab
    }
}

反编译之后

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: ldc #2 // String a
2: astore_1 //存储 "a"
3: ldc #3 // String b
5: astore_2 //存储 "b"
6: ldc #4 // String c
8: astore_3
9: new #5 // class java/lang/StringBuilder 创建对象
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."":()V
16: aload_1 //astore_1相反的过程,取出存入的值a
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: return
LineNumberTable:
line 8: 0
line 9: 3
line 10: 6
line 11: 9
line 12: 29
LocalVariableTable: //类似栈帧,存放值
Start Length Slot Name Signature

0      30     0  args   [Ljava/lang/String;
3      27     1     a   Ljava/lang/String;
6      24     2     b   Ljava/lang/String;
9      21     3     c   Ljava/lang/String;

29 1 4 ab Ljava/lang/String;
}

观察上方的操作步骤,等价于执行过程

new StringBuilder() ->append->append->toString()

StringBuilder.toString()如下:

    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

new String(value, 0, count);//重新创建得一个对象,保存在堆中

由此可以得出:

String ab1 = "ab";

String ab2 = a + b; //变量相加,执行的操作是在堆中用StringBuilder.append(char)

ab1 ≠ab2,因为ab1保存在常量池中,而ab2保存在堆中。

添加"ab"

    public static void main(String[] args) {
        String ab2 = "a" + "b";
        String ab3 = "ab";
        System.out.println(ab2 == ab3);
    }

输出:true

截取:

   ​      29: ldc           #9                  // String ab ,**ldc**从常量池 #9  中取值
   ​      31: astore        5
   ​      33: ldc           #9                  // String ab,**ldc**从常量池 #9  中取值
   ​      35: astore        6

故ab2和ab3都是从常量池中取出#9的值ab,所以输出:true

StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder
  • 字符串常量的拼接原理是编译优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池 (1.8)
public class Demo3 {
    public static void main(String[] args) {
        String s = new String("a") + new String("b");
        String intern = s.intern();//当常量池中没有该字符串的时候会将其放入
        String s1 = "ab";
        String s2 = new String("ab");
        //常量池中的字符串第一次使用时才变成对象
        System.out.println(intern == s);
        System.out.println(intern == s1);
        System.out.println(intern == s2);

    }
}

输出:

true
true
false
public class Demo4 {
    public static void main(String[] args) {
        String s1 = "a" + "b";//运行时优化为"ab",常量池中的对象
        String s2 = new String("ab");//堆中的对象

        String s3 = s2.intern();//常量池已经存在"ab"了直接从常量池中取出的对象

        System.out.println(s1 == s2);//false
        System.out.println(s1 == s3);//true
        System.out.println(s2 == s3);//false

        String x = new String("c") + new String("d");
        String x2 = "ab";
        x2.intern();
        System.out.println(x == x2);//false

        String x3 = new String("e") + new String("f");
        x3.intern();
        //最开始常量池没有ef,则将堆中的ef放入常量池,此时的x3即表示堆中的对象,也表示常量池的对象
        
        String x4 = "ef";//常量池中已存在,直接取出
        System.out.println(x3 == x4);//true
    }
}

内存结构图

内存图

1.8之后,StringTable是放在堆中

/**
 * -Xmx10m -XX:-UseGCOverheadLimit
 */
public class Demo5 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 260000; i++) {
            String a = "a" + i;
            String intern = a.intern();
            list.add(intern);
        }
    }
}

输出:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.halouit.constantpool.Demo5.main(Demo5.java:10)

StringTable垃圾回收

/**
 * Stringtable 垃圾回收
 * -Xmx10m -XX:PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class Demo6 {
    public static void main(String[] args) {
       
    }
}

gc信息:

Heap
 PSYoungGen      total 2560K, used 2048K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 100% used [0x00000000ffd00000,0x00000000fff00000,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
 Metaspace       used 3075K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 324K, capacity 392K, committed 512K, reserved 1048576K
Disconnected from the target VM, address: '127.0.0.1:51984', transport: 'socket'

SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     13022 =    312528 bytes, avg  24.000
Number of literals      :     13022 =    564088 bytes, avg  43.318
Total footprint         :           =   1036704 bytes
Average bucket size     :     0.651
Variance of bucket size :     0.649
Std. dev. of bucket size:     0.805
Maximum bucket size     :         6
==================================================================
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :      1732 =     41568 bytes, avg  24.000
Number of literals      :      1732 =    156832 bytes, avg  90.550
==================================================================
Total footprint         :           =    678504 bytes
Average bucket size     :     0.029
Variance of bucket size :     0.029
Std. dev. of bucket size:     0.171
Maximum bucket size     :         3

可以看出StringTable statistics:中字符串初始1732个字符串

修改代码后

public class Demo6 {
    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            ("a" + i).intern();
        }
    }
}

Allocation Failure由于内存不足,进行了gc

[GC (Allocation Failure) [PSYoungGen: 2048K->496K(2560K)] 2048K->724K(9728K), 0.0010769 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :     11439 =    274536 bytes, avg  24.000
Number of literals      :     11439 =    694888 bytes, avg  60.747

循环了10000次,由于进行了gc,把某些没有引用的字符串回收了,所以不是10000+1732个,而是11439个。

直接内存

Direct Memory

  • 常见于NIO操作,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不收JVM内存回收管理

2 垃圾回收

2.1 常见算法

  1. 引用计数法
  2. 可达性分析算法

2.1.1 可达性分析算法

通过GC Root引用了哪些对象


发现彩蛋,输入":"+ 你想要的表情名称即可打出表情。如:laghing,:laughing:,:joy::joy:

2.1.1.1 查看GC Roots的方法

  1. 下载Eclipse Memory Analyzer
  2. 转储运行时文件

    /**
     * 使用Eclipse Memory分析
*/

public class Demo1 {

   public static void main(String[] args) throws IOException {
       List<String> list = new ArrayList<>();
       for (int i = 0; i < 25000; i++) {
           String x = new String("") + i;
           list.add(x);
       }
       System.out.println("创建了对象");
       System.in.read();
       list = null;
       System.out.println("进行了gc");
       System.in.read();
   }

}




1. 先使用jps找到进程id

2. 再运行jmap,转储``jmap -dump:format=b,live,file=1.bin 21387``

3. 参数解释:


dump:format=b 转化为二进制
live 存活对象
file=1.bin 输出文件名为1.bin
21387 进程id


3. 使用工具查看

![memory](http://minio.halouit.com/images/note/EclipseMemory%E6%9F%A5%E7%9C%8BGCRoots.png)

4. Gc Roots主要引用内容:
- 系统类
- jni
- 用户活动线程
- 监视器锁

![gc roots主要引用](http://minio.halouit.com/images/note/GCRoots%E6%89%80%E5%BC%95%E7%94%A8%E7%9A%84%E4%B8%BB%E8%A6%81%E5%86%85%E5%AE%B9.png)

5. 在list被回收之前的引用

![list回收前](http://minio.halouit.com/images/note/%E6%AD%A3%E5%9C%A8%E8%BF%90%E8%A1%8C%E7%9A%84%E7%BA%BF%E7%A8%8B.png)

6. lsit回收之后,发现已经没有list了

![lsit回收之后](http://minio.halouit.com/images/note/Gc%E5%9B%9E%E6%94%B6%E5%90%8E%E7%9A%84ArrayList.png)

### 2.1.2 四种引用

1. 强引用
2. 软引用
3. 弱引用
4. 虚引用
5. 终结器引用

![四种引用](http://minio.halouit.com/images/note/%E5%9B%9B%E7%A7%8D%E5%BC%95%E7%94%A8.PNG)

引用解释:

![解释](http://minio.halouit.com/images/note/%E5%BC%95%E7%94%A8%E8%A7%A3%E9%87%8A.PNG)

**强引用**在有引用的时候未进行gc

/**

  • 强引用:-Xmx20m

*/
public class Demo2 {

private static final int _4M = 4 * 1024 * 1024;

public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        list.add(new byte[_4M]);
    }
}

}


输出:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at com.halouit.gcdemo.Demo2.main(Demo2.java:12)

**软引用**,在内存不足时再次触发垃圾回收

/**

  • 强引用:-Xmx20m
  • 弱引用:-Xmx20m -XX:+PrintGCDetails -verbose:gc

*/
public class Demo2 {

private static final int _4M = 4 * 1024 * 1024;

public static void main(String[] args) {
    soft();
}

private static void soft() {
    List<SoftReference<byte[]>> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {

        SoftReference<byte[]> softReference = new SoftReference<>(new byte[_4M]);
        list.add(softReference);
        System.out.println(softReference.get());
    }
    System.out.println("============");
    for (int i = 0; i < 5; i++) {
        System.out.println(list.get(i).get());
    }
}

}


在前3次分配内存都充足时,没有垃圾回收,在第四次进行了一次新生代垃圾回收,第5次,进行了多次垃圾回收。**最后只剩下最后一次添加的对象**。

[B@39a054a5
[B@71bc1ae4
[B@6ed3ef1
[GC (Allocation Failure) [PSYoungGen: 2185K->496K(6144K)] 14473K->13020K(19968K), 0.0010132 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[B@2437c6dc
[GC (Allocation Failure) --[PSYoungGen: 4817K->4817K(6144K)] 17342K->17374K(19968K), 0.0009242 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4817K->4540K(6144K)] [ParOldGen: 12556K->12440K(13824K)] 17374K->16980K(19968K), [Metaspace: 3072K->3072K(1056768K)], 0.0058034 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 4540K->4540K(6144K)] 16980K->16988K(19968K), 0.0013496 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4540K->0K(6144K)] [ParOldGen: 12448K->581K(8704K)] 16988K->581K(14848K), [Metaspace: 3072K->3072K(1056768K)], 0.0064262 secs] [Times: user=0.03 sys=0.01, real=0.01 secs]

[B@1f89ab83

null
null
null
null
[B@1f89ab83
Heap
PSYoungGen total 6144K, used 4319K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 76% used [0x00000000ff980000,0x00000000ffdb7fb0,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8704K, used 581K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
object space 8704K, 6% used [0x00000000fec00000,0x00000000fec91410,0x00000000ff480000)
Metaspace used 3080K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 324K, capacity 392K, committed 512K, reserved 1048576K


弱引用在内存不够时直接回进行垃圾回收,如:

VM:``-Xmx20m -XX:+PrintGCDetails -verbose:gc``
private static void weak() {
    List<WeakReference<byte[]>> list = new ArrayList<>();
    for (int i = 0; i < 6; i++) {

        WeakReference<byte[]> ref = new WeakReference<>(new byte[_4M]);
        list.add(ref);
        for (WeakReference<byte[]> b : list) {
            System.out.print(b.get() + "\t");
        }
        System.out.println("===");
    }
}

输出:

[B@71bc1ae4 ===
[B@71bc1ae4 [B@6ed3ef1 ===
[B@71bc1ae4 [B@6ed3ef1 [B@2437c6dc ===

                     ****************内存分配失败,进行垃圾回收******************

[GC (Allocation Failure) [PSYoungGen: 2185K->496K(6144K)] 14473K->13052K(19968K), 0.0011570 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[B@71bc1ae4 [B@6ed3ef1 [B@2437c6dc [B@1f89ab83 ===

                                 ****************内存分配失败,进行垃圾回收,此时回收没有成功,触发了FullGC******************

[GC (Allocation Failure) --[PSYoungGen: 4817K->4817K(6144K)] 17374K->17390K(19968K), 0.0009698 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4817K->0K(6144K)] [ParOldGen: 12572K->596K(8192K)] 17390K->596K(14336K), [Metaspace: 3077K->3077K(1056768K)], 0.0071492 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

                                 ****************FullGC,把前面4个对象给回收了******************

null null null null [B@e73f9ac ===
null null null null [B@e73f9ac [B@61064425 ===
Heap
PSYoungGen total 6144K, used 4320K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 76% used [0x00000000ff980000,0x00000000ffdb8040,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8192K, used 4692K [0x00000000fec00000, 0x00000000ff400000, 0x00000000ff980000)
object space 8192K, 57% used [0x00000000fec00000,0x00000000ff0953a8,0x00000000ff400000)
Metaspace used 3084K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 325K, capacity 392K, committed 512K, reserved 1048576K




## 2.2 清除算法

### 标记清除

### 标记整理



回收:

![分代回收](http://minio.halouit.com/images/note/%E5%88%86%E4%BB%A3%E5%9B%9E%E6%94%B6.PNG)

**垃圾回收过程**:

/**

  • VM:-Xms20m -Xmx20m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc

*/
public class Demo3 {

private static final int _7M = 7 * 1024 * 1024;

public static void main(String[] args) {

}

}


初始化:

新生代新生代总共10M,开始的时候程序已经使用了2772KB。

Heap
def new generation total 9216K, used 2572K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 31% used [0x00000000fec00000, 0x00000000fee830e8, 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 3066K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 324K, capacity 392K, committed 512K, reserved 1048576K


分配7M内存

public class Demo3 {

private static final int _7M = 7 * 1024 * 1024;

public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();
    list.add(new byte[_7M]);
}

}




**垃圾回收细节**:

``[GC (Allocation Failure) [DefNew: 2405K->669K(9216K), 0.0015498 secs] 2405K->669K(19456K), 0.0016082 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ``

- ``[GC (Allocation Failure) [DefNew: 2405K->669K(9216K)`` 分配内存失败,触发垃圾回收,新生代占用空间从2405K变为669K,(9216K)表示新生代和``to``或者``from``区总计内存
- ``2405K->669K(19456K)`` 表示:堆内存占用,从2405K到669K,总计19456K
- ``tenured generation``表示:老年代

[GC (Allocation Failure) [DefNew: 2405K->669K(9216K), 0.0015498 secs] 2405K->669K(19456K), 0.0016082 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 8247K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 92% used [0x00000000fec00000, 0x00000000ff366830, 0x00000000ff400000)
from space 1024K, 65% used [0x00000000ff500000, 0x00000000ff5a7488, 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 3076K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 324K, capacity 392K, committed 512K, reserved 1048576K


**大对象直接进入老年代**

/**

  • VM:-Xms20m -Xmx20m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc

*/
public class Demo3 {

private static final int _7M = 7 * 1024 * 1024;
private static final int _8M = 8 * 1024 * 1024;

public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();

// list.add(new byte[_7M]);

    //大对象,当新生代内存不足,不会进行垃圾回收,直接进入老年代(前提是老年代的内存充足)
    list.add(new byte[_8M]);
}

}


输出:

Heap
def new generation total 9216K, used 2569K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 31% used [0x00000000fec00000, 0x00000000fee82708, 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)


第2次内存不足时,堆内存溢出
public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();

// list.add(new byte[_7M]);

    //大对象,当新生代内存不足,不会进行垃圾回收,直接进入老年代(前提是老年代的内存充足)
    list.add(new byte[_8M]);
    list.add(new byte[_8M]);
}

输出:

GC (Allocation Failure) [DefNew: 2405K->669K(9216K), 0.0018233 secs 10597K->8860K(19456K), [Metaspace: 3069K->3069K(1056768K)], 0.0045061 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 8860K->8844K(10240K), 0.0021803 secs] 8860K->8844K(19456K), [Metaspace: 3069K->3069K(1056768K)], 0.0022239 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 410K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 5% used [0x00000000fec00000, 0x00000000fec66800, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 8844K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 86% used [0x00000000ff600000, 0x00000000ffea3290, 0x00000000ffea3400, 0x0000000100000000)
Metaspace used 3101K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 327K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at com.halouit.gcdemo.Demo3.main(Demo3.java:18)

# 3 垃圾回收器

1. 串行
2. 吞吐量优先
   - 多线程
   - 内存较大,多核CPU
   - 在一段时间内,STW时间最短,如1分钟内总计0.2 + 0.2 = 0.4
3. 响应时间优先
   - 多线程
   - 内存较大,多核CPU
   - 尽可能在单次中STW时间最短如:1分钟内 平均每次 0.1+ 0.1+ 0.1+ 0.1+ 0.1 = 0.5S
   - ![image-20210424122402679](JVM.assets/image-20210424122402679.png)

## G1

适用场景

- 同时注重吞吐量和低延迟,默认的暂停目标是200ms
- 超大堆内存,会将堆划分为多个大小相等的Region
- 整体上是标记+整理算法,两个区域之间是复制算法

相关JVM参数

-XX:+UseG1GC (jdk9以前需要手动开启,9之后是默认垃圾收集器)

-XX:G1HeapRegionSize=size

-XX:MaxGCPauseMillis=time

#### 新生代收集 + 并发标记

- 在Young GC时会进行GC Root的初始标记

- 老年代占用空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定

  -XX:InitiatingHeapOccupancyPercent=percent(默认45%)

MIxed Collection

- 最终标记(Remark)会STW

- 拷贝存活会STW

**JDK8u20字符串去重**

-XX:+UseStringDepulication

当新生代回收时,JVM判断字符串是否存在相同的,主要是根据char[],如果有重复,则让它门引用同一个char[]。



在命令行中通过此命令可以查看GC参数``"C:\Program Files\Java\jdk1.8.0_271\bin\java" -XX:+PrintFlagsFinal -version |findStr "GC"``

输出如下:

uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}

uintx AutoGCSelectPauseMillis                   = 5000                                {product}
 bool BindGCTaskThreadsToCPUs                   = false                               {product}
uintx CMSFullGCsBeforeCompaction                = 0                                   {product}
uintx ConcGCThreads                             = 0                                   {product}

## 3.2 垃圾回收调优

目标:

- 低延迟:CMS、G1、ZGC
- 高吞吐量:ParallelGC

进行GC调优之前先检查自己的代码

查看Full前后的内存占用,考虑一下几个问题

- 数据是不是太多?
  - 查询数据库中的整张表或者查询表中的全部字段而非需要的字段

- 数据表示是不是太臃肿
  - 比如能不用包装就用基本类型,对象大小(最小16字节)Integer 24 int 4
- 是否存在内存泄露
  - 如static Map map,会导致有些内存空间一直内占用而不会释放。
标签: jvm

非特殊说明,本博所有文章均为博主原创。

评论啦~