• JVM相关总结


    https://www.cnblogs.com/jiangym/p/15885161.html

    JVM内存模型(JMM)

    根据代码画出下面的JVM内存模型

    public class Math {
    
        public static final int initData = 666;
        public static User user = new User();
    
        public int compute(){
            int a = 1;
            int b = 2;
            int c = (a + b) * 10;
            return c;
        }
    
        public static void main(String[] args) {
            Math math = new Math();
            math.compute();
        }
    }
    class User{
    
    }
    View Code

     字节码class文件被“类装载子系统”,加载到运行时数据区,通过“字节码执行引擎”运行方法代码。

    main方法开始运行后,就会有一个线程;每有一个线程,都会开辟一个线程特有的“虚拟机栈”(最左面),

    compute()和main()在虚拟机栈里都会有一个自己的栈帧,方法对应的栈帧会在方法结束时被释放。

    存栈帧的顺序也是按栈的先进后出来存和销毁。嵌套调用

    (左上二图)局部变量,a,b都会放到局部变量里。

    操作数栈里也是栈结构,栈顶会弹出ab然后操作,乘积为30然后放入操作数栈。在字节码文件执行时有程序计数器,计数器的数也是“字节码执行引擎”来控制的

    在局部变量分配c的空间,然后从操作数里放到局部变量里。

    User对象是静态的,在方法区存栈帧,指向的对象在堆中。

    垃圾收集是“字节码执行引擎”开辟的一个线程,会根据可达性分析,以及三色标记算法,来处理。

    分代年龄是在对象头里存,对象头第一个字宽(1字宽=4字节=32bit)存markword,(32位机)前25位是hashcode,之后四位是分代标识,

    所以只能最多到1111即0-15次,就要去老年代。剩下三位与synchronized有关,存偏向锁和锁标志位。

    三色标记算法

    是为了在每次垃圾回收时,提升扫面效率,把已经扫过的节点忽略。

    把每个对象分为黑灰白,未扫过的为白,扫过但是子节点未扫全的为灰,子节点都扫全的为黑。

    扫描的线程有的时候会中断,会存在一个黑指向白的问题,导致下一次扫描的时候,白色扫不到,内存泄漏。

    CMS解决的办法是,把这种场景的黑变为灰,但是还不能彻底解决这个问题,且扫完之后整体再扫一遍。

    G1的解决办法是单独记录黑指向白的线,后续统一处理。

    线程间通信

    为提高性能,编译器和处理器常常会对指令重排序。

    happens -before原则

    //todo

     

    垃圾收集器

    CMS(老年代 oldGC)+parNew

    一般CMS设置fullGC的阈值是内存到65%,如果等到内存满了会使用Serial-old,这个stw会很长时间

    回收阶段

    1. 初始标记
      1. 标记根对象直接关联的对象 短暂stw
    2. 并发标记
      1. 找出所有与GCRoot能关联的对象,同时执行 不stw
    3. 并发预清理(重新标记)不一定执行
      1. 减少5工作量
    4. 并发可终止的预清理 不一定执行
    5. 重新标记
      1. 用户是并发的,怕期间修改,存在stw
    6. 并发清除
      1. 并发清除垃圾, 因为并发清除,所以不进行整理。
    7. 并发重置
      1. 清除CMS上下文信息

    G1

    把内存划分为若干个region,每个region在同一时刻,只属于一个代。

    • Eden
    • survivor
    • old
    • humongous 存大对象,连续的region

    YongGC

    mixedGC

    这个是设计巧妙的部分,老年代占比到阈值(一般45%),会回收所有yongRegion同时回收部分oldRegion区的。

    回收阶段

    1. 初始标记
      1. 标记根对象直接关联的对象 短暂stw
    2. 并发标记
      1. 找出所有与GCRoot能关联的对象,同时执行 不stw
    3. 最终标记 stw
    4. 筛选回收
      1. 根据用户期望,对回收成本进行排序,制定回收计划,并选择region回收

    回收过程

    region放到一个回收集(类似Set);把region存活对象放到空region里,删除region ---- 复制算法

    fullGC

    减少rullGC的办法:

    • 增加预留内存
    • 更早地垃圾回收
    • 增加并发阶段使用的线程数

    >jdk8 一般都不用CMS了,用G1。

    垃圾回收算法

    • 标记-清除
      • 存在内存碎片
    • 标记-整理
    • 复制
      • 2快内存,性能好,无碎片,内存利用率低。
    • 分代收集

    可达性分析

    对象是否有引用,可以用引用计数法和可达性分析,java使用可达性分析

    引用计数法,引用+1,释放-1,来标记,存在循环引用的问题。

    可达性分析:没有到gcroot的引用,即可回收。

    finalize关键字

    一旦垃圾回收器准备释放对象所占的内存空间, 如果对象覆盖了finalize()并且函数体内不能是空的, 就会首先调用对象的finalize(),  然后在下一次垃圾回收动作发生的时候真正收回对象所占的空间.

    finalize代码块里加东西要慎重,避免内存泄漏。

    最好使用try。catch。finally

    类加载机制

    类加载过程

    类加载的时候是按需加载的

    通过运行类时添加参数TraceClassLoading参数,获取类加载的时间线

     

    1.首先读取rt的jar包

    2.加载hello类

    3.加载hello类的main方法里的uesr类

    类加载过程(生命周期)

    加载;(验证;准备;解析) => 链接;初始化;使用;卸载

    1. 加载:查找并加载类的二进制数据。
      1. (把类的.class文件的二进制数据读入内存,存放在运行时数据区的方法区;类加载的最终结果是产生 堆区中描述对应类的Class对象);
    2. 链接:包括验证、准备和解析三个子阶段;
      1. 验证:验证class文件的二进制是否符合规范
      2. 准备:在准备阶段就赋值了。
      3. 解析: 把符号引用翻译成直接引用。
    3. 初始化:给类中的静态变量赋予正确的初始值;
      1. 创建类(new、反射、克隆、反序列化)
      2. 使用静态方法、非静态变量
      3. Class.forName("ATest"); 获取描述类的Class对象;
      4. 类初始化顺序,是一个面试点
    4. 使用
    5. 卸载
      1. 场景:
      2. 该类所有实例都被GC
      3. 加载该类的ClassLoader已被GC
      4. 该类的Class对象没有被引用,也没有通过反射访问该类的方法。

      注意:使用能在编译期能得知的final static修饰的常量,不会导致类的初始化;
        public static final int a = 2*3;//编译时常量,不会导致类初始化;
        public static final int a b = (int)(Math.random()*10)/10+1; // 不是编译时常量,会初始化;
    子类父类初始化过程:

    先对这个类进行加载和连接-> 如果有直接父类,先加载连接初始化这个父类->重复以上步骤直到所有父类初始化,初始化当前类;
    (先加载连接当前类,再加载连接初始化父类,再初始化当前类)

    初始化阶段

    变量给真正的值。把准备阶段给的默认值替换掉。

    静态代码块执行。

    创建对象时执行

    普通代码块执行

    构造器

    类加载执行顺序

    1. 静态常量
    2. 静态变量
    3. 静态初始化块
    4. 变量
    5. 初始化块
    6. 构造器

    有继承关系时,父子类加载顺序

    • 父类--静态变量
    • 父类--静态初始化块
    • 子类--静态变量
    • 子类--静态初始化块
    • 父类--变量
    • 父类--初始化块
    • 父类--构造器
    • 子类--变量
    • 子类--初始化块
    • 子类--构造器

    类加载器classLoad

    在类“加载”阶段,通过一个类的全限定名来获取描述该类的二进制字节流的这个动作的“代码”,被成为“类加载器”

    除了Java虚拟机自带的根类加载器以外,其余的类加载器有且只有一个父加载器;
    Java虚拟机自带加载器

    • 根(Bootstrap)类加载器:没有父类加载器。负责加载虚拟机核心类。
    • 扩展(Extension)类加载器:父加载器为根加载器;继承于java.lang.ClassLoader;
    • 系统(System)类加载器:也称应用类加载器,父加载器为扩展类加载器;加载classpath路径下指定的类库;继承于java.lang.ClassLoader,也是自定义加载器的默认父类;

    双亲委派

    类加载过程中,
    会先从最顶层加载器(一般是根加载器)开始往下,
    先判断父类加载器能不能加载,能加载则往下传递返回加载的类;
    不能加载则继续往下判断,如果都不能加载,则会抛出ClassNotFoundException异常;
    加载器之间的父子关系实际上是指加载器对象之间的包装关系,而不是类之间的继承关系;

    例如:

    双亲委派加载例子

    String类来加载;app传到Ext传到Boo,Boo发现rt下有类,就直接加载了;

    ext下的类加载;app传到Ext传到Boo,Boo发现加载不了;返回下级加载器;Ext发现ext目录下有,就直接加载了;

    用户定义User类加载;app传到Ext传到Boo,Boo发现加载不了;返回下级加载器;Ext发现加载不了,返回下级加载器;App加载器发现可以加载就加载了。

    若其他类加载不到,则会抛ClassNotFoundException。

    所以说双亲委派是对加载器来说的,总是往上层加载器抛,之前理解的不对,之前想的时子类和父类之间的加载关系。

    好处

    1.确保安全,避免核心类库被加载

    2.避免重复加载

    3.保证类的唯一性

    例如自己写String类

    程序会去RT的加载器加载,但是加载到之后发现main方法不存在,就会报错。

    调优

    执行 TOP 命令后

    场景:CPU持续100% 

    常见原因

    1. 宿主机CPU超卖
    2. 内存问题 大量FUllGC
    3. 代码存在死循环

    排查办法:

    1. 通过TOP命令可以查看到各个进程CPU,记录其PID
    2. 通过Jstat命令 打出垃圾收集日志,排查是否是内存溢出:
      1. Jstat -gcutil 12345 5000
      2. 查看YGC和FGC的次数和耗时。
      3. 如果是内存溢出,排查内存溢出位置。
      4. Jmap -dump :format=b,file = ../../heap.bin PID 
      5. 用mat等工具查看
    3. 如果不是内存溢出,则有可能是存在死循环。排查死循环位置。
      1. Top -P 【pid】 -H
      2. 查看CPU占比高的子线程 1234
      3. 转成16进制 printf "%x/n" 1234 = abc
      4. 打印进程堆栈 查看对应abc的具体报错
      5. sudo -u admin/opt/java/bin/jstack 2066 > a.txt

    cat proc/cupinfo 查看CPU的核数

    负载高

    CPU没满,但负载比较高

      等待磁盘IO的进程过多,进程队列过长。

    常见场景:

    • 磁盘读写请求多
    • Mysql有不走索引的语句或死锁

    内存异常

    mem是内存使用情况

    内存飙升常见场景:

    1. 内存溢出(堆、方法区、栈)
      1. 堆,fullGC也不起作用了
      2. 方法区:类加载过多
      3. 线程栈:递归太多,层级太深。
    2. 内存泄漏
      1. jmap -dump format=b file=heap.dump PID 
      2. 用mat等分析

    ThreadLocal 相关

    java引用分为四种。强软弱虚四种引用。

    强引用:

    new 一个对象 是强引用

    finalize()方法基本上不能重写,避免内存泄漏, 当一个对象被垃圾回收器clear的时候,会调用finalize

    重写是为了跟踪回收过程。

    System.gc() 建议gc,hotspots 会去回收

    软引用:

    SoftReference<> m = new SoftReference<>(new byte[1024*1024*10]); 

    加粗的部分为软引用

    byte[] b = new byte[1024*1024*12];

    这个是强引用,当执行这句话,空间不够时,上面的软引用会直接回收。

    软引用适合缓存的使用,例如缓存图片。

    弱引用:

    遇到gc就被回收

    虚引用:

    NIO的零拷贝 这种情况  分配的直接内存 在JVM之外 不能直接被垃圾回收器回收

    垃圾回收器里存个虚引用,回收时候有个队列,如果有相关需要删除的直接内存,则再释放。

    ThreadLocal 

    static ThreadLocal<Person> tl = new TheadLocal<>();

    现象:

      一个线程set一个对象到tl里,另一个线程获取不到

    原因要从tl.set()的源码入手:

      set是放到当前线程的threadLocalMap里了,所以本质是thread,person对象的一个键值对,放到当前线程的map里。

    所以另一个线程获取不到上一个线程存放的数据。key不同。

    应用:

    spring的@transactional注解中,若有两个dao层M1和M2,为了保证M1获取的数据库链接和M相同,

    吧M1的connection链接放到ThreadLocal里,M2从ThreadLocal里取,这样就保证了两次连接再一个事务里。

    线程池里的线程如果用到ThreadLocal了,必须要手动清理掉,防止内存泄漏

    threadLocalMap的key最初用的强引用,一直也回收不掉,越积累越多,后来编程弱引用了。

    用不到当前threadLocal了,要调用tl.remove(); 底层是threadLocalMap.remove(当前线程);

    相关资料

    https://www.bilibili.com/video/BV14K411F7S4?p=1

    https://www.bilibili.com/video/BV12b4y167Mb?p=2

    https://www.bilibili.com/video/BV12b4y167Mb?p=12

    https://www.bilibili.com/video/BV1s44y167Yj?from=search&seid=18316383145675461585&spm_id_from=333.337.0.0

    《Java面象对象编程》

  • 相关阅读:
    一步一步学Remoting之四:承载方式(2)<转>
    一步一步学Remoting之五:异步操作<转>
    NET3.0+中使软件发出声音[整理篇]<转>
    Ext Core手册
    一步一步学Remoting之一:从简单开始<转>
    asp.net 常用字符串过滤方法 <转>
    mssql性能优化<转>
    一步一步学Remoting之四:承载方式(1)<转>
    Javascript中最常用的61个经典技巧 <转>
    Js事件对象<转>
  • 原文地址:https://www.cnblogs.com/jiangym/p/15885161.html
Copyright © 2020-2023  润新知