jvm简单了解

文章目录

  • JVM
    • JVM引言
        • 1. 什么是JVM
        • 2. 学习JVM有什么用?
        • 3. 常见的JVM
        • 4. 学习路线
    • 内存结构
      • 1. 程序计数器
      • 2. 虚拟机栈
        • 2.1 定义
        • 2.2 栈内存溢出
        • 2.3 线程诊断
      • 3. 本地方法栈
      • 4. 堆
        • 4.1 定义
        • 4.2 堆内存溢出
        • 4.3 堆内存诊断
      • 5. 方法区
        • 5.1 定义
        • 5.2 组成
        • 5.3 方法区内存溢出
        • 5.4 运行时常量池
        • 5.5 StringTable
        • 5.6 StringBuilder特性
        • 5.7 面试题
        • 5.8 StringTable的位置
        • 5.9 StringTable垃圾回收
        • 5.10StringTable性能调优
      • 6. 直接内存
        • 6.1 定义
        • 6.2 直接内存导致内存溢出
        • 6.3 释放原理
      • 7. jvm的垃圾回收
        • 7.1如何判断对象可以回收
          • 7.1.1 引用计数法
          • 7.1.2 可达性分析算法
          • 7.1.3 四种引用
        • 7.2 垃圾回收算法
          • 7.2.1 标记清除
          • 7.2.2 标记整理
          • 7.2.3 复制
        • 7.3 分代垃圾回收
          • 7.3.1 相关VM参数
        • 7.4 垃圾回收器
          • 7.4.1 串行
          • 7.4.2 吞吐量优先
          • 7.4.3 响应时间优先
          • 7.4.4 G1
            • 1. G1垃圾回收阶段
            • 2. Young Collection
            • 3. Young Collection+CM
            • 4. Mixed Collection
            • 5. Full GC
            • 6. Young Collection跨代引用
            • 7. Remark(重新标记阶段)
            • 8. JDK 8u20字符串去重
            • 9. JDK 8u40 并发标记类卸载
            • 10. JDK 8u60 回收巨型对象
            • 11.JDK 9 并发标记起始时间的调整
            • 12. JDK 9 更高效的回收
        • 7.5 垃圾回收调优(目前不学了,以后再学,哈哈哈哈哈)
          • 7.5.1 调优领域
          • 7.5.2 确定目标
          • 7.5.3 最快的GC
          • 7.5.4 新生代调优
          • 7.5.5 老年代调优
          • 7.5.6 案例
    • 类加载与字节码技术
        • 1. 类文件结构
          • 1.1 文件结构
        • 2. 字节码指令
        • 3. 编译期处理
        • 4. 类加载阶段
        • 5. 类加载器
        • 6. 运行期优化

JVM

JVM引言

1. 什么是JVM

  • 定义

java virtual machine -java程序的运行环境(java二进制的字节码的运行环境)

  • 好处
  1. 一次编写,到处运行
  2. 自动内存管理,垃圾回收功能
  3. 数组下边越界检查
  4. 多态
  • 比较

jvm简单了解_第1张图片

2. 学习JVM有什么用?

  • 面试
  • 理解底层的实现原理
  • 中高级程序员的必备技能

3. 常见的JVM

  • JVM是一套规范,

jvm简单了解_第2张图片

4. 学习路线

jvm简单了解_第3张图片

内存结构

1. 程序计数器

Program Counter Register(程序计数器)

  • 记住了下一条jvm指令的执行地址
  • 特点
    • 线程私有
    • 不会存在内存溢出

2. 虚拟机栈

2.1 定义

Java Virtual Machine Stacks(java虚拟机栈)

  • 每个线程运行所需要的内存称为虚拟机栈
  • 每个栈由多个栈祯组成,对应着每次方法调用所占的内存
  • 每一个线程只能有一个活动栈,对应着当前正在执行的方法

问题辨析

  • 垃圾回收是否会涉及栈内存?

不会,占内存只是一次次的方法调用产生的栈祯内存,而栈祯内存在方法调用结束后会被弹出栈(会被自动的回收掉),垃圾回收只会回收堆内存中的无用对象,占内存不需要进行垃圾回收的处理

  • 占内存分配的于大越好吗?
    • 下面是各种系统默认栈内存的大小,windows的栈内存是取决于虚拟内存的大小

jvm简单了解_第4张图片

栈内存并不是越大越好的,例如虚拟内存有500M,我们每一个栈内存设置为1M,可以运行500个五百个线程,每个栈内存设置2M,则只能运行250个线程,所以栈内存设置的过大会导致运行线程的数量减少,栈内存设置的比较大只能可以进行更多次的方法递归调用,所以栈内存使用默认值就好。

  • 方法内的局部变量是否是线程安全?
  1. 如果方法内局部变量没有逃离方法的作用访问,他就是线程安全的
  2. 如果是局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全

2.2 栈内存溢出

什么情况下导致占内存溢出

  • 栈祯过多导致栈内存溢出,调用方法次数过多,导致栈内存无法分配新的栈祯内存,(递归调用会导致栈内存溢出)

jvm简单了解_第5张图片

  • 栈祯过大导致栈内存溢出
  • java,lang.StackOverflowError:栈内存溢出会抛出此异常

2.3 线程诊断

  • 案例一:CPU占用过多

定位:在Linux系统下

  • 使用top定位哪一个进程对CPU的占用过高

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

  • jstack 进程id

    • 可以根据线程id找到有问题的线程,进一步定位到问题问题代码的源码行号

    jvm简单了解_第6张图片

  • 案例二:程序运行很长时间没有结果

定位:Linux系统下

​ 例如死锁

1583160468711

3. 本地方法栈

Native Method Stack

  • java虚拟机调用本地方法的时候需要给本地方法提供的一个内存空间
  • 本地方法是指java调用C++或C语言写的程序来与系统进行交互(因为java是无法直接与系统进行交互的,所以要借助一些C++和C语言编写的代码,这些用C++和C语言编写的代码被称为本地方法)

4. 堆

4.1 定义

Heap堆

  • 通过new关键字创建的对象都会使用堆内存

特点

  • 它是线程共享的,堆中的对象都要考虑线程安全问题
  • 有垃圾回收机制:不在被引用的对象会被回收

4.2 堆内存溢出

堆内存溢出抛的异常:java.lang.OutOfMemoryError:Java heap space

jvm简单了解_第7张图片

-Xmx 内存大小:可以设置对内存的大小

jvm简单了解_第8张图片

4.3 堆内存诊断

  1. jps工具

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

    • 查看堆内存占用情况
    • jmap -heap 进程ID
  3. jconsole工具

    • 图形界面的,多功能检测工具,可以连续检测
  4. jvisualvm

    • 图形界面的可视化工具

案例

  1. 垃圾回收以后,内存占用仍然很高

说明占用内存的对象仍然在被使用

  • 运行以下代码
public class demo01{

    public static void main(String[] args) throws  InterruptedException{
        List<Student> students = new ArrayList<Student>();
        for (int i = 0;i < 200; i++){
            students.add(new Student());
        }
        Thread.sleep(1000000000L);
    }
}

class  Student{
    private byte[] big = new byte[1024*1024];
}
  • 在控制台输入jvisualvm,启动工具
  • 双击要查看的进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x8IfemFl-1583486219286)(D:\桌面\JVM.assets\1583201021827.png)]

  • 进入后点击监视

jvm简单了解_第9张图片

  • 点击堆dump
  • 点击左侧的查找

jvm简单了解_第10张图片

  • 列出来了前二十个

jvm简单了解_第11张图片

  • 我们发现ArrayList占用了200多兆内存
  • 我们点击ArrayList

jvm简单了解_第12张图片

  • 查看里面的属性,找出占用内存的地方

5. 方法区

5.1 定义

  • 所有java虚拟机线程的共享的区域
  • 里面存储跟类结构相关的数据
    • 成员变量
    • 方法数据
    • 成员方法以及构造器方法代码部分
    • 运行时常量池
  • 方法区在虚拟机启动时被创建
  • 方法区逻辑上是堆得组成部分
  • 方法去申请内存时发现不足,也会让java虚拟机抛出OutofMemoryError

5.2 组成

jvm简单了解_第13张图片

5.3 方法区内存溢出

  • 1.8以前会导致永久代内存溢出
* 演示永久代内存溢出  java.lang.OutOfMemoryError:PermGen space
* -XX:MaxPermSize=8m //设置永久代大小
  • 1.8以后会导致元空间内存溢出
* 演示元空间内存溢出 java.lang.OutOfMemoryError:Metaspace
* -XX:MaxMetaspaceSize=8m	//设置元空间大小

场景

  • spring
  • mybatis

动态生成的类过多导致方法区内存溢出

5.4 运行时常量池

  • 常量池:就是一张常量表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量(hello world这种字符串或者一些整数布尔这种基本数据类型)等

使用java提供的javap命令可以将.class文件进行简单的反编译,加上-v参数,是显示详细信息

package com.lld.demo;

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/3 11:27
 * @Version 1.0
 */
//二进制字节码中包含了:类的基本信息,常量池,类方法的定义,包含了虚拟机的指令
public class demo03 {

    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

jvm简单了解_第14张图片

  • 运行时常量池:常量池是*.class文件中的,当该类被加载,他的常量池信息会被放入运行时常量池,并将里面的符号地址变为真实地址

在运行时常量池中,会将#2,#3这种字符转换成真实的在内存中的地址

5.5 StringTable

  • 字符串创建过程
package com.lld.demo;

/**
 * @ClassName demo06
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/3 13:27
 * @Version 1.0
 */
public class demo06 {

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
    }
}

jvm简单了解_第15张图片

  • String s4 = s1+s2;

分析下面代码中String s4 = s1+s2;的工作流程

判断s3与s4是否是同一个对象

package com.lld.demo;

/**
 * @ClassName demo06
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/3 13:27
 * @Version 1.0
 */
public class demo06 {

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";

       //等价于new StringBuilder.append("a").append("b").toString 等价于new String("ab")
         String s4 = s1 + s2;
    }
}

jvm简单了解_第16张图片

  • String s4 = “a” + “b”;

分析下面代码中String s4 = “a” + “b”;的工作流程

判断s3和s4是否是同一个对象

package com.lld.demo;

/**
 * @ClassName demo06
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/3 13:27
 * @Version 1.0
 */
public class demo07 {

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";

        String s4 = "a" + "b";//javac在编译期间进行了优化,已经确定a和b是一个常量,将他们进行拼接不会出现别的变化,所以还是ab
    }
}

jvm简单了解_第17张图片

5.6 StringBuilder特性

  • 常量池中的字符串仅是符号,第一次用到时才变成为对象

  • 利用串池的机制,来避免重复创建字符串对象

  • 字符串变量拼接的原理是StringBuilder(1.8)

  • 字符串常量拼接的原理是编译期优化

  • 可以使用intern方法,主动将串池中还没有的字符串放入串池

    • 1.8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回。
    • 1.6将这个字符串对象尝试放入串池,如果有则并不会放入,,如果没有则将此对象复制一份,放入串池,会把串池中的对象返回。

5.7 面试题

package com.lld.demo;

/**
 * @ClassName demo05
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/3 13:06
 * @Version 1.0
 */
public class demo05 {

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();

        //问
        System.out.println(s3 == s4);
        System.out.println(s3 == s5);
        System.out.println(s3 == s6);

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

        String x2 = new String("c") + new String("d");
        String x1 = "cd";
        x2.intern();

        //问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
        System.out.println(x1 == x2);
    }
}

jvm简单了解_第18张图片

jvm简单了解_第19张图片

5.8 StringTable的位置

jvm简单了解_第20张图片

package com.lld;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName demo
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/4 14:25
 * @Version 1.0
 *在jdk1.6下设置:-XX:MaxPermSize=10m//设置永久代的内存大小只有10m
 * 在jdk1.8下设置:-Xmx10m//设置堆空间的内存大小只有10m
 *                  -XX:-UseGcOverheadLimit:前面用-UseGCOver..,表示关闭UseGcOverheadLimit(为了测试,具体作用不知)
 */
public class demo {
    public static void main(String[] args) {
        //创建一个list集合来存储字符串
        List<String> list = new ArrayList<String>();
        int i = 0;
        for (int j = 0; j< 2600000;j++){
            //String.valueOf(j):把基本数据类型转换成字符串,intern()放入串池
            list.add(String.valueOf(j).intern());
            i++;
        }

        System.out.println(i);
    }
}

  • 测试1.6StringTable的位置(串池在永久代)

jvm简单了解_第21张图片

  • 测试1.8StringTable的位置(串池在堆空间)

jvm简单了解_第22张图片

5.9 StringTable垃圾回收

垃圾回收只会在内存紧张时触发,内存宽裕时不会触发

  • 认识结构
package com.lld;

/**
 * @ClassName demo1
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/4 14:57
 * @Version 1.0
 * 演示StringTable的垃圾回收
 * -Xmx10m:设置堆内存大小
 * -XX:+PrintStringTableStatistics:打印字符串表的统计信息(通过其可以看到串池中字符串的个数以及字符串大小等信息)
 * -XX:+PrintGCDetails -verbose:gc:打印垃圾回收的详细信息(如果发生了垃圾回收,就把垃圾回收的次数以及详细时间打印出来)
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class demo1 {

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

jvm简单了解_第23张图片

  • 测试垃圾回收
package com.lld;

/**
 * @ClassName demo1
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/4 14:57
 * @Version 1.0
 * 演示StringTable的垃圾回收
 * -Xmx10m:设置堆内存大小
 * -XX:+PrintStringTableStatistics:打印字符串表的统计信息(通过其可以看到串池中字符串的个数以及字符串大小等信息)
 * -XX:+PrintGCDetails -verbose:gc:打印垃圾回收的详细信息(如果发生了垃圾回收,就把垃圾回收的次数以及详细时间打印出来)
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class demo1 {

    public static void main(String[] args) {
        for (int i = 0;i < 100000; i++){
            String.valueOf(i).intern();
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ivf4Qrcj-1583486219292)(D:\桌面\JVM.assets\1583307219373.png)]

5.10StringTable性能调优

StringTable的底层是一个hash表,hash表的性能跟他的大小密切相关,如果hash表的空间比较大,桶的个数就比较分散,hash碰撞的几率就会变少,查找的速度就会变快,反之,桶的个数就比较少,hash碰撞的几率就会增高,导致链表长度较长,查找的速度就会收到影响

  • StringTable的调优主要是调整桶的个数

设置一个较小的值

jvm简单了解_第24张图片

测试设置StringTable的最大和最小值

jvm简单了解_第25张图片

设置一个较大的值测试

jvm简单了解_第26张图片

用到的四个参数解释

1583336466108

代码

package com.lld;

/**
 * @ClassName demo02
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/4 23:22
 * @Version 1.0
 */
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 演示串池大小对性能的影响
 * -Xms500m:为jvm启动时分配的内存,比如-Xms200m,表示分配200M
 * -Xmx500m:为jvm运行过程中分配的最大内存,比如-Xms500m,表示jvm进程最多只能够占用500M内存
 * -XX:+PrintStringTableStatistics:在JVM进程退出时,打印出StringTable的统计信息输出到gclog中.
 * -XX:StringTableSize=1009:配置字符串常量池中的StringTable大小,默认:60013 (Number of buckets in the interned String table) ,StringTable数据结构是hashtable,这个值就是hashtable的size大小,建议设置成大一点的质数
 */
public class demo02 {

    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) {
            String line = null;
            long start = System.nanoTime();
            while (true) {
                line = reader.readLine();
                if (line == null) {
                    break;
                }
                line.intern();
            }
            System.out.println("cost:" + (System.nanoTime() - start) / 1000000);
        }
    }
}

  • 考虑是否将字符串对象是否入池

如果字符串存在大量重复,可以将字符串对象入池,以减少重复,减少内存的使用

6. 直接内存

不属于java虚拟机的内存,属于系统内存

6.1 定义

Direct Memory

  • 常见于NIO操作,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受JVM的控制

案例

  • io(不使用直接内存)

jvm简单了解_第27张图片

  • nio(使用直接内存)

jvm简单了解_第28张图片

jvm简单了解_第29张图片

  • 代码
package com.lld;

/**
 * @ClassName demo03
 * @Description TODO
 * @Author LLD
 * @Date 2020/3/5 9:48
 * @Version 1.0
 */
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 演示 ByteBuffer 作用
 */
public class demo03 {
    static final String FROM = "G:\\study video\\黑马最新49期视频\\30-SSM分布式案例-互联网商城-阶段项目-必看(共197集)\\11.单点登录系统实现&cookie跨域问题详解\\09.登录的表现层的开发__rec.avi";
    static final String TO = "G:\\a.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io 用时:1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
    }

    private static void directBuffer() {
        long start = System.nanoTime();
        try (FileChannel from = new FileInputStream(FROM).getChannel();
             FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                to.write(bb);
                bb.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
    }

    private static void io() {
        long start = System.nanoTime();
        try (FileInputStream from = new FileInputStream(FROM);
             FileOutputStream to = new FileOutputStream(TO);
        ) {
            byte[] buf = new byte[_1Mb];
            while (true) {
                int len = from.read(buf);
                if (len == -1) {
                    break;
                }
                to.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io 用时:" + (end - start) / 1000_000.0);
    }
}

6.2 直接内存导致内存溢出

  • 案例

jvm简单了解_第30张图片

  • 代码

    package com.lld;
    
    /**
     * @ClassName demo04
     * @Description TODO
     * @Author LLD
     * @Date 2020/3/5 10:00
     * @Version 1.0
     */
    
    import java.nio.ByteBuffer;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 演示直接内存溢出
     */
    public class demo04 {
        static int _100Mb = 1024 * 1024 * 100;
    
        public static void main(String[] args) {
            List<ByteBuffer> list = new ArrayList<>();
            int i = 0;
            try {
                while (true) {
                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
                    list.add(byteBuffer);
                    i++;
                }
            } finally {
                System.out.println(i);
            }
            // 方法区是jvm规范, jdk6 中对方法区的实现称为永久代
            //                  jdk8 对方法区的实现称为元空间
        }
    }
    
    

6.3 释放原理

  • gc不能释放直接内存
  • 直接内存释放是通过主动调用一个底层类Unsafe的freeMemory(参数:内存地址值)这个方法来进行释放的
  • DirectByteBuffer底层调用了一个Cleaner(虚引用)类来监测ByteBuffer,当DirectByteBuffer被释放时就会由Referencehandle的线程通过Cleaner的clean方法调用Unsafe的freeMemory方法释放内存
  • system.gc()方法是显式的垃圾回收(也被称为 Full GC),使用这种显式垃圾回收会对系统性能有一定的影响,所以经常通过-XX:+DisableExplicitGC命令来禁用显式的垃圾回收(即system.gc无效),这时在管理直接内存时通常手动使用Unsafe的freeMemory来进行直接内存的释放

7. jvm的垃圾回收

7.1如何判断对象可以回收

7.1.1 引用计数法

当一个对象被引用一次,引用计数就加一,被引用两次,引用计数就为二,当一个变量停止引用,引用计数就减一,当引用计数为零时,表明该对象没有被引用了,就可以将该对象当一个垃圾进行回收

  • 弊端

据说Python早期就是用引用计数法

jvm简单了解_第31张图片

7.1.2 可达性分析算法

根对象:表示不可能被回收的对象

在垃圾回收时首先对堆内存进行扫描,被根对象直接或者间接引用的对象不能进行回收,反之,则进行回收

回收过程

  • java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能沿着GC Root对象为起点的引用链来找到该对象,如果找不到,表示可以进行回收

那些对象可以作为GC Root对象?

通过MAT工具

  • 系统类里面的hashmap,String等
  • 调用操作系统方法时所引用的对象也是根对象
  • synchronized 引用的对象是根对象
  • 栈祯内所使用的一些对象可以被作为根对象

栈祯内局部变量所引用的对象都可以作为根对象

7.1.3 四种引用
  1. 强引用

    • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
  2. 软引用(实际使用看P54的视频)

    • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用
      对象

    • 可以配合引用队列来释放软引用自身

    • 软引用的使用方法

      List<SoftReference<Byte[]>> list = new ArrayList<SoftReference<Byte[]>>();
      //list --强引用--> SoftReference --弱引用--> Byte
       * 演示软引用
       * -Xmx20m :-设置堆内存大小
       * -XX:+PrintGCDetails :–打印GC详细信息
       * -verbose:gc : -在控制台输出GC情况
      

    jvm简单了解_第32张图片

    • 配合软引用队列使用

    因为有了引用队列,当软引用指向的对象被回收后,该软引用就会进入引用队列,在下面我们将进入引用队列的数据在list集合中进行了移除

    jvm简单了解_第33张图片

  3. 弱引用

    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    • 可以配合引用队列来释放弱引用自身
    List<WeakReference<Byte[]>> list = new  ArrayList<WeakReference<Byte[]>>();
    // List --强引用--> WeakReference --弱引用-->Byte
    
  4. 虚引用

    • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,
      由 Reference Handler 线程调用虚引用相关方法释放直接内存
  5. 终结器引用

    • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象
      暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize
      方法,第二次 GC 时才能回收被引用对象

jvm简单了解_第34张图片

7.2 垃圾回收算法

7.2.1 标记清除

Mark Sweep

  • 优点:速度快

  • 缺点:容易产生内存碎片,当对象被释放后,不会重新对内存进行整理,导致一个较大的对象存储不下,但是实际内存空间是足够的

  • 图解

jvm简单了解_第35张图片

7.2.2 标记整理

Mark Compact

  • 优点:解决了标记清除造成内存碎片的缺点

  • 缺点:速度较慢,对象需要移动,对象移动后,内存地址发生改变,引用该对象的一些变量的值也相应的需要改变

  • 图解

jvm简单了解_第36张图片

7.2.3 复制

Copy

  • 优点:没有内存碎片产生

  • 缺点:占用双倍内存空间

  • 图解

jvm简单了解_第37张图片

7.3 分代垃圾回收

理解

  1. 将堆内存划分成两个区域,一个叫新生代,一个叫老年代
  2. 新生代又划分为三个区域:伊甸园,幸存区from,幸存区to
  3. 长时间使用的对象放入老年代
  4. 用完了就可以丢弃的对象放入新生代中
  5. 根据生命周期的不同特点进行不同的垃圾回收策略,老年代的垃圾回收策略很久才发生一次,新生代的垃圾回收策略进行的比较频繁
  6. 新生代处理的朝生夕死的对象,老年代存放比较有价值的,存活时间更长的对象

工作流程

  1. 创建一个新的对象时(寿命默认为0),默认会采用伊甸园的一块空间,当新的对象向伊甸园申请内存空间,发现内存空间已经满了的时候,会触发一次新生代的垃圾回收(称为Minor GC)
  2. Minnor GC会引发一次stop the world(当触发垃圾回收时,会暂停用户的其他线程,由垃圾回收线程对伊甸园,幸存区from进行清理,当立即回收完成,用户其他线程才会继续运行)
  3. 对新生代采用垃圾回收后幸存的对象会被移到幸存区To,让幸存的对象寿命加一,
  4. 完成一次Minor GC会交换一次幸存区from和幸存区to的位置
  5. 当伊甸园再次满了,触发第二次垃圾回收,会将伊甸园幸存的对象放入幸存区to,寿命加一,幸存区form里面的幸存对象也放入幸存区to,寿命加一
  6. 当幸存区from里面的对象的寿命达到一定程度的时候(最大为15),会将该对象放入老年代(老年代回收频率低)
  7. 当老年代空间不足时,先触发一次Minor GC,当垃圾清理后,空间还是不足,这时,触发Full GC(也会触发stop the world)。
  8. 当触发了Full GC后,空间还不够,这时就会出发OutOfMemoryError

jvm简单了解_第38张图片

7.3.1 相关VM参数
含义 参数
堆初始大小 -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

7.4 垃圾回收器

7.4.1 串行
  • 单线程
  • 堆内存较小,适合个人电脑

jvm简单了解_第39张图片

7.4.2 吞吐量优先
  • 多线程
  • 适合堆内存较大,需要多核cpu支持
  • 尽可能让单位时间内stop the world时间最短

jvm简单了解_第40张图片

7.4.3 响应时间优先
  • 多线程
  • 适合堆内存较大,需要多核cpu支持
  • 尽可能让单词stop the world时间最短

jvm简单了解_第41张图片

7.4.4 G1

定义:Garbage First

  • 2004 论文发布

  • 2009 JDK 6u14 体验

  • 2012 JDK 7u4 官方支持

  • 2017 JDK 9 默认

适用场景

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

相关 JVM 参数

  • XX:+UseG1GC
  • XX:G1HeapRegionSize=size
  • XX:MaxGCPauseMillis=time
1. G1垃圾回收阶段

jvm简单了解_第42张图片

2. Young Collection

jvm简单了解_第43张图片

jvm简单了解_第44张图片

jvm简单了解_第45张图片

3. Young Collection+CM
  • 在进行Young GC时会进行GC Root的初始标记
  • 老年代占用堆空间比例达到阈值的时候,进行并发标记(不会STW),由下面的JVM参数决定

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

jvm简单了解_第46张图片

4. Mixed Collection

会对E,S,O进行全面回收

  • 最终标记(Remark)会STW
  • 拷贝存活(Evacuation)会STW
  • XX:MaxGCPauseMillis=ms

jvm简单了解_第47张图片

为了达到回收时间短的目标,会选择性回收垃圾最多的老年代区域

5. Full GC
  • SerialGC

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
  • G1

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
      • 并发标记阶段
      • 混合收集阶段

    并发收集失败(当收集垃圾的速度比不上新产生垃圾的速度时,算作并发收集失败,并发收集退化为串行)以后,才会进行Full GC

6. Young Collection跨代引用
  • 新生代回收的跨代引用(老年代引用新生代)问题

jvm简单了解_第48张图片

  • 卡表与 Remembered Set
  • 在引用变更时通过 post -write barrier + dirty card queue
  • concurrent refinement threads 更新 Remembered Set

jvm简单了解_第49张图片

7. Remark(重新标记阶段)

pre -write barrier(写屏障) + satb_mark_queue(队列)

jvm简单了解_第50张图片

8. JDK 8u20字符串去重
  • 优点:节省大量内存
  • 缺点:略微多占用了 cpu 时间,新生代回收时间略微增加

XX:+UseStringDeduplication

String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
  • 将所有新分配的字符串放入一个队列
  • 当新生代回收时, G1并发检查是否有字符串重复
  • 如果它们值一样,让它们引用同一个 char[]
  • 注意,与 String.intern() 不一样
    • String.intern() 关注的是字符串对象
    • 而字符串去重关注的是 char[]
    • 在 JVM 内部,使用了不同的字符串表
9. JDK 8u40 并发标记类卸载
  • 所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸
    载它所加载的所有类

XX:+ClassUnloadingWithConcurrentMark 默认启用

10. JDK 8u60 回收巨型对象
  • 一个对象大于 region 的一半时,称之为巨型对象
  • G1 不会对巨型对象进行拷贝
  • 回收时被优先考虑
  • G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生代垃圾回收时处理掉
11.JDK 9 并发标记起始时间的调整
  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC

  • JDK 9 之前需要使用 - XX:InitiatingHeapOccupancyPercent

  • JDK 9 可以动态调整

    • XX:InitiatingHeapOccupancyPercent 用来设置初始值
    • 进行数据采样并动态调整
    • 总会添加一个安全的空档空间
12. JDK 9 更高效的回收
  • 250+ 增强
  • 180+bug 修复
  • https://docs.oracle.com/en/java/javase/12/gctuning

7.5 垃圾回收调优(目前不学了,以后再学,哈哈哈哈哈)

jvm简单了解_第51张图片

一项比较高级的技能

7.5.1 调优领域
7.5.2 确定目标
7.5.3 最快的GC
7.5.4 新生代调优
7.5.5 老年代调优
7.5.6 案例

类加载与字节码技术

jvm简单了解_第52张图片

1. 类文件结构

1.1 文件结构

2. 字节码指令

3. 编译期处理

4. 类加载阶段

5. 类加载器

6. 运行期优化

你可能感兴趣的:(jvm简单了解)