jvm学习笔记

class文件大致加载过程

![image.png](https://img-blog.csdnimg.cn/img_convert/4dc88bed9d1660db4a13baa4b43655e1.png#align=left&display=inline&height=600&margin=[object Object]&name=image.png&originHeight=600&originWidth=1058&size=62221&status=done&style=none&width=1058)

常见的jvm

  • hotspot
  • jrocket——IBM
  • Microsoft vm
  • taobao vm
  • azul zing

class file format

编译:

javac xxx.java

查看:
  1. javap -v xxx.class
  2. idea插件jclasslib
文件标识符|子版本号|主要版本号|常量池元素个数
cafe babe 0000 0037 0010 0a00 0300 0d07
000e 0700 0f01 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0012
4c6f 6361 6c56 6172 6961 626c 6554 6162
6c65 0100 0474 6869 7301 000b 4c6a 766d
2f44 656d 6f31 3b01 000a 536f 7572 6365
4669 6c65 0100 0a44 656d 6f31 2e6a 6176
610c 0004 0005 0100 096a 766d 2f44 656d
6f31 0100 106a 6176 612f 6c61 6e67 2f4f
626a 6563 7400 2100 0200 0300 0000 0000
0100 0100 0400 0500 0100 0600 0000 2f00
0100 0100 0000 052a b700 01b1 0000 0002
0007 0000 0006 0001 0000 0009 0008 0000
000c 0001 0000 0005 0009 000a 0000 0001
000b 0000 0002 000c 

第三天

jdk+jre+jvm的关系

![image.png](https://img-blog.csdnimg.cn/img_convert/f678dcbb9ad0c39abb2b3c735695b621.png#align=left&display=inline&height=510&margin=[object Object]&name=image.png&originHeight=510&originWidth=977&size=41605&status=done&style=none&width=977)

类的加载过程(类文件+class对象)![image.png](https://img-blog.csdnimg.cn/img_convert/fec58056e0a60c4c5d46dca11eb8ff79.png#align=left&display=inline&height=490&margin=[object Object]&name=image.png&originHeight=490&originWidth=1025&size=50841&status=done&style=none&width=1025)

  1. loading:将字节码文件加载到内存中
  2. linking:负责初始化类之前的操作
    1. verification:验证文件是不是正规的class文件(验证字节码文件)
    2. prepare:将类属性都赋予默认值
    3. resolution:将符号引用转变为 内存引用
  3. initialization:将类中的属性赋予初始值

类加载器

分类

![image.png](https://img-blog.csdnimg.cn/img_convert/e719f6e3434a76993607ec3c5fc8f488.png#align=left&display=inline&height=532&margin=[object Object]&name=image.png&originHeight=532&originWidth=925&size=55737&status=done&style=none&width=925)

执行流程(双亲委派机制)

![image.png](https://img-blog.csdnimg.cn/img_convert/6d5cdf669da6996ee0841384aed1e0a3.png#align=left&display=inline&height=725&margin=[object Object]&name=image.png&originHeight=725&originWidth=860&size=104584&status=done&style=none&width=860)

不同类加载器的加载的路径
  1. 通过查看“Launcher”类的源码可知,对于不同的加载器的范围如下:
    • boostrap => sun.boot.class.path
    • extension => java.class.path
    • application => java.ext.dirs
  2. 运行响应的代码查看
public class Demo1 {
    public static void main(String[] args) {
        System.out.println(System.getProperty("sun.boot.class.path").replaceAll(";",System.lineSeparator()));
        System.out.println();
        System.out.println(System.getProperty("java.class.path").replaceAll(";",System.lineSeparator()));
        System.out.println();
        System.out.println(System.getProperty("java.ext.dirs").replaceAll(";",System.lineSeparator()));
    }
}
D:\Java\jdk1.8.0_291\jre\lib\resources.jar
D:\Java\jdk1.8.0_291\jre\lib\rt.jar
D:\Java\jdk1.8.0_291\jre\lib\sunrsasign.jar
D:\Java\jdk1.8.0_291\jre\lib\jsse.jar
D:\Java\jdk1.8.0_291\jre\lib\jce.jar
D:\Java\jdk1.8.0_291\jre\lib\charsets.jar
D:\Java\jdk1.8.0_291\jre\lib\jfr.jar
D:\Java\jdk1.8.0_291\jre\classes

D:\java\jdk1.8.0_291\jre\lib\charsets.jar
D:\java\jdk1.8.0_291\jre\lib\deploy.jar
D:\java\jdk1.8.0_291\jre\lib\ext\access-bridge-64.jar
D:\java\jdk1.8.0_291\jre\lib\ext\cldrdata.jar
D:\java\jdk1.8.0_291\jre\lib\ext\dnsns.jar
D:\java\jdk1.8.0_291\jre\lib\ext\jaccess.jar
D:\java\jdk1.8.0_291\jre\lib\ext\jfxrt.jar
D:\java\jdk1.8.0_291\jre\lib\ext\localedata.jar
D:\java\jdk1.8.0_291\jre\lib\ext\nashorn.jar
D:\java\jdk1.8.0_291\jre\lib\ext\sunec.jar
D:\java\jdk1.8.0_291\jre\lib\ext\sunjce_provider.jar
D:\java\jdk1.8.0_291\jre\lib\ext\sunmscapi.jar
D:\java\jdk1.8.0_291\jre\lib\ext\sunpkcs11.jar
D:\java\jdk1.8.0_291\jre\lib\ext\zipfs.jar
D:\java\jdk1.8.0_291\jre\lib\javaws.jar
D:\java\jdk1.8.0_291\jre\lib\jce.jar
D:\java\jdk1.8.0_291\jre\lib\jfr.jar
D:\java\jdk1.8.0_291\jre\lib\jfxswt.jar
D:\java\jdk1.8.0_291\jre\lib\jsse.jar
D:\java\jdk1.8.0_291\jre\lib\management-agent.jar
D:\java\jdk1.8.0_291\jre\lib\plugin.jar
D:\java\jdk1.8.0_291\jre\lib\resources.jar
D:\java\jdk1.8.0_291\jre\lib\rt.jar
D:\codes\java\review\out\production\review
D:\IntelliJIDEA2021.1.1\lib\idea_rt.jar

D:\Java\jdk1.8.0_291\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext

Process finished with exit code 0

自定义类加载器

继承classLoader重新实现findClass,读取的字节流使用defineClass

二叉树

#include<queue>
#include<map>
#include<cmath>

using namespace std;

class Node;
typedef void Visit(Node *node);

class Node{
public:
    int nodeValue;
    Node *l_child;
    Node *r_child;
public:
    Node(){
        l_child = nullptr;
        r_child = nullptr;
    }

    Node(int value){
        nodeValue = value;
        Node();
    }

    ~Node(){
        if(l_child != nullptr){
            delete l_child;
        }
        if(r_child != nullptr){
            delete r_child;
        }
    }

    bool isLeafNode() const{
        return r_child == nullptr && l_child == nullptr;
    }

    int degree() const{
        if(r_child == nullptr && l_child == nullptr){
            return 0;
        }
        if(r_child != nullptr && l_child != nullptr){
            return 2;
        }
        return 1;
    }

    int depth() const{
        int l_depth = 0;
        int r_depth = 0;

        if(l_child != nullptr){
            l_depth = l_child->depth();
        }
        if(r_child != nullptr){
            r_depth = r_child->depth();
        }
        return max(r_depth,l_depth) + 1;
    }

    int count() const{
        int l_count = 0;
        int r_count = 0;
        if(l_count != nullptr){
            l_count = l_child->count();
        }
        if(r_count != nullptr){
            r_count = r_child->count();
        }
        return r_count + l_count + 1;
    }

    void preOrderVisit(Visit visit){
        visit(this);
        if(l_child != nullptr){
            l_child->preOrderVisit(visit);
        }
        if(r_child != nullptr){
            r_child->preOrderVisit(visit);
        }
    }

    void inOrderVisit(Visit visit){
        if(l_child != nullptr){
            l_child->inOrderVisit(visit);
        }
        visit(this);
        if(r_child != nullptr){
            r_child->inOrderVisit(visit);
        }
    }

    void postOrderVisit(Visit visit){
        if(l_child != nullptr){
            l_child->postOrderVisit(visit);
        }
        if(r_child != nullptr){
            r_child->postOrderVisit(visit);
        }
        visit(this);
    }

    void levelOrderVisit(Visit visit){
        queue<Node *> nodeQueue;
        nodeQueue.push(this);
        while(!node.Queue.empty()){
            Node *node = nodeQueue.front();
            nodeQueue.pop();
            if(node->l_child != nullptr){
                nodeQueue.push(node->l_child);
            }
            if(node->r_child != nullptr){
                nodeQueue.push(node->r_child);
            }
            visit(node);
        }
    }

    void invert(){
        if(l_child != nullptr){
            l_child->invert();
        }
        if(r_child != nullptr){
            r_child->invert();
        }
        Node *node = l_child;
        l_child = r_child;
        r_child = node;
    }
};

class BinaryTree{
private:
    Node *_root;
private:
    Node *create(map<int,int> &tree,int i){
        if(tree.find(i) == tree.end()){
            return nullptr;
        }

        Node *node = new Node(tree[i]);
        node->l_child = create(tree,i * 2);
        node->r_child = create(tree,i * 2 + 1);
        return node;
    }

public:
    BinaryTree(){
        _root = nullptr;
    }
    BinaryTree(map<int,int> &tree){
        _root = create(tree, 1);
    }

    ~BinaryTree(){
        if(_root != nullptr){
            delete _root;
        }
    }

    int depth() const{
        if(_root == nullptr){
            return 0;
        }
        return _root->depth();
    }

    void preOrderVisit(Visit visit){
        if(_root != nullptr){
            _root->preOrderVisit(visit);
        }
    }

    void inOrderVisit(Visit visit){
        if(_root != nullptr){
            _root->inOrderVisit(visit);
        }
    }

    void postOrderVisit(Visit visit){
        if(_root != nullptr){
            _root->postOrderVisit(visit);
        }
    }

    void levelOrderVisit(Visit visit){
        if(_root != nullptr){
            _root->levelOrderVisit(visit);
        }
    }

    void invert(Visit visit){
        if(_root != nullptr){
            _root->invert();
        }
    }
};


第四天

jvm默认使用编译解释同时执行

  • -Xmix:指定以混合方式执行
  • -Xint:指定以解释方式执行
  • -Xcomp:指定以编译方式执行

jvm规定加载类的五种情况

  1. new getstatic putstatic invokestatic指令,访问final变量
  2. java.lang.reflect 对类进行反射调用
  3. 初始化子类的时候,父类首先初始化
  4. 动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时

双亲委派机制

打破(基于jvm在加载.class文件时会在内存中保存一份.class文件 + class对象)

  1. 如何:重写classloader的loadclass方法
  2. 为什么:由于双亲委派机制的存在在加载相同路径的类时总是获取内存中类加载过程中加载的,不能及时获取最新的
  3. 何时:
    1. jdk1.2之前必须
    2. ThreadContextClassloader
    3. tomat的classloader,热部署

对象创建过程:分配内存空间+默认值+初始化,类加载过程:加载类+默认值+初始化

DCL

volatile(可见性、有序性):防止由于指令重排导致其他线程读到脏数据


  • 第五天

硬件层数据一致性

![image.png](https://img-blog.csdnimg.cn/img_convert/5918cb488306c69787fccf530f0f92be.png#align=left&display=inline&height=448&margin=[object Object]&name=image.png&originHeight=448&originWidth=896&size=34749&status=done&style=none&width=896)

出现问题

  1. 锁总线(当内存中的数据被某个cpu操作时,其他cpu暂时无法再次访问内存)
  2. 一致性协议(通过不同的状态添加不同的标记)
    1. MESI:
      1. modified:被修改
      2. exclusive:仅一个cpu读取
      3. share:多个cpu同时读取
      4. invalid:当一个cpu在读取数据时另一个cpu在修改数据

基本单元

cpu从内存中数据读取的基本单元:cache line -> 64bytes,由此可能会导致为共享问题

  1. 问题描述:当两个相邻的单元储存着两个数值,此时有两个cpu想要去操作这两个数值,但是由于每次读取的是一个cache line就会导致把另一个不相干的数值读取进来,此时当修改一个数值时,就会导致另一个cpu操作另外一个数值无效
  2. 问题解决:空间换时间(定义多个Long类型变量填充缓存行)
public class Demo1 {
    public volatile long m = 0L; // 未填充cache line时间为2000左右
    public volatile long m = 0L,l1,l2,l3,l4,l5,l6; // 填充cache line(cache line padding) 时间为800左右
    // 在填充cache line后每次cpu都读取一个cache line
    // 在未填充cache line之前,每次读取d[0]时,由于d[0]的大小小于64byte于是就会把d[1]读取进来,于是当一个分布在不同cpu的线程访问不同的d的元素
    时就会导致数据一致性问题(锁总线、一致性协议),就会降低性能
    // 在填充cache line之后,每次读取d[0]时,由于d[0]的大小接近于64byte于是就会只读取当前元素并进行操作,于是当位于其他cpu的其他线程读取d[1]进行
        操作时互不影响,提高性能
    public static Demo1[] d = new Demo1[2];
    static{
        d[0] = new Demo1();
        d[1] = new Demo1();
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10000_0000L; i++) {
                d[0].m = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 10000_0000L; i++) {
                d[1].m = i;
            }
        });

        long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        long end = System.nanoTime();
        System.out.println((end - start) / 100_0000L);

    }
}

乱序

  1. 无序读,只能不影响最终的结果,cpu就会对所有的指令进行优化重排序
  2. 合并写(WC buffer:write combining buffer -> 4个字节):在每次cpu在将数据计算完成并放入内存中时会通过WCBuffer暂存数据,但是WCBuffer只有4个字节的大小,如果数据计算后的结果小于WCBuffer的大小就会直接放入其中并刷新回内存,否则就会先装满WCBuffer并刷新回内存与此同时CPU计算完成后剩下的数据会等着WCBuffer空闲后继续刷新回内存,于是就会存在性能问题

有序性保证

硬件内存屏障
  1. cpu级别(基于intel x86):
    1. sfence(storefence):位于sfence指令前的写命令先发生于指令后的写命令
    2. lfence(load fence):位于lfence指令前的读命令先发生于指令后的读命令
    3. mfence(storeload fence):位于mfence指令前的读写命令先发生于指令后的读写命令
  2. java汇编级别:lock指令
jvm规范

基于硬件的具体实现

  1. loadload
  2. storestore
  3. storeload
  4. loadstore

volatile实现

字节码

acc_volatile

jvm

命令前后加屏障

os

win

lock

Linux

屏障+lock

synchrinized实现

字节码

方法

acc_synchronized

代码块

moniterenter moniterexit

jvm

c c++ 调用操作系统底层的同步机制

OS

lock comxchg xxx

对象内存布局

对象的创建过程

  1. class loading
  2. class linking(verification prepare resolution)
  3. class initialization
  4. 申请内存
  5. 对象属性赋予默认值
  6. 调用构造方法
    1. 对象属性初始化
    2. 调用构造方法

对象的构成

普通对象
  1. 对象头:markword 8
  2. classpointer:类指针,指向该对象所属于的class
  3. 示例数据
  4. padding:填充对象所占的空间为8的整数倍
数组对象
  1. 对象头:markword 8
  2. classpointer:类指针,指向该对象所属的class
  3. 数组的长度
  4. 数组的数据
  5. padding:填充对象所占的空间为8的声整数倍

对象的大小

  1. 对象头:8byte
  2. classpointer:原8字节,jvm默认开启UseCompressClassPointer后为4byte
  3. Oops:Ordinary object pointer size,默认为8个字节,通过使用:XX:+UseCompressOops后压缩为一半
  4. 数据大小
  5. 填充8的整数倍

对象头的组成

![image.png](https://img-blog.csdnimg.cn/img_convert/a873f908d9d95c0fdd1895c31bc32503.png#align=left&display=inline&height=278&margin=[object Object]&name=image.png&originHeight=278&originWidth=846&size=158188&status=done&style=none&width=846)

  1. 锁:是否加锁2bit
  2. 偏向锁:1bit
  3. 分代年龄:4bit
  4. 一个对象一旦计算了identityhashcode就无法进入锁的的状态

对象定位

  1. 句柄池
  2. 直接指向

JMM

基本划分

![image.png](https://img-blog.csdnimg.cn/img_convert/d830214d8bd90635ded67b8428a2e069.png#align=left&display=inline&height=595&margin=[object Object]&name=image.png&originHeight=595&originWidth=661&size=46203&status=done&style=none&width=661)

方法区

    1. 8之前:永久代 permanent gen,fgc不回收
  • 1.8 之后:元空间meta space

jvm栈(子元素为栈帧)

局部变量表

包括参数列表、自定义参数、this(实例方法)

操作数栈

记录操作的值

动态链接

在检查到存在符号引用为转换成值引用的时候实现值引用转换

方法返回地址

记录返回的值应该返回到哪个地址

面试题

代码1:

public class Demo2 {

    public static void main(String[] args) {
        
        int i = 8;
        i = i++; // i++是对局部变量表上的变量值进行自增,而i = xx,是将操作数栈中栈顶的值弹出并赋值到i,这就是为什么结果还是8
        System.out.println(i); // 结果为8
    }
}

字节码1:

 0 bipush 8  // 像操作数栈中放入值
 2 istore_1	 // 将操作数栈栈顶的值弹出并赋予到局部变量表中下标为1的变量上
 3 iload_1   // 将局部变量表中下标为1的变量的值压入到操作数栈中
 4 iinc 1 by 1  // 将局部变量表中的下标为1的变量的值加一
 7 istore_1  // 将操作数栈栈顶的值弹出并赋予到局部变量表中下标为1的变量上
 8 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println : (I)V>
15 return

代码2:

public class Demo2 {

    public static void main(String[] args) {
        
        int i = 8;
        i = ++i; // ++i是对局部变量表上的变量值进行自增,而i = xx,是将操作数栈中栈顶的值弹出并赋值到i,这就是为什么结果是9
        System.out.println(i); // 结果为8
    }
}

字节码2:

 0 bipush 8  // 将8入操作数栈
 2 istore_1 // 将局部变量表中下标为1的变量赋予操作数栈栈顶的值
 3 iinc 1 by 1  // 将局部变量表中下标为1的变量的值加1
 6 iload_1 // 将局部变量表中下表为1的变量的值入操作数栈
 7 istore_1 // 将操作数栈中栈顶的元素的值弹出并赋予到局部变量表中下表为1的值
 8 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println : (I)V>
15 return


  • 第六条

jvm指令集

invoke系列

invoke_static

调用静态方法

invoke_virtual

调用支持多态的方法(包括final修饰的实例方法)

invoke_special

调用private方法或者构造方法

invoke_dynamic

通过lambda表达式相关的方法。注意:() -> {} 会在当前类中创建一个Lambda内部类并在此内部类中再次创建一个内部类

垃圾

定义

没有引用指向的对象

判定方式

引用技术(python)

不能解决循环引用问题

根可达

可以作为根的类型:

  1. 线程栈变量:一个方法调用另一个方法
  2. 静态变量
  3. 常量池
  4. JNI指针(java native interface)

垃圾收集算法

标记——清除(mark sweep)

先扫描再清除垃圾
适用于存活对象比较多的情况
需要遍历两次,效率较低,且会产生许多空间碎片

复制(copy)——新生代

将空间分为两块,先遍历确定哪些对象不是垃圾,然后复制到另一块空间,然后清除整块空间
需要遍历一次,效率较高,但是会改变内存中对象的地址,后续有点麻烦

标记——压缩(mark compact)

将不能清除的对象都集中在一起
遍历两次,效率较低,效果较好

分代垃圾收集算法

除了Epsilon ZGC Shenandoah 之外的都是使用逻辑分代模型
G1使用逻辑分代,物理不分代
除此之外都是逻辑分代、物理分代

  • 新生代:复制算法
  • 老年代:标记清楚、标记压缩

堆空间的逻辑分配

老年代:新生代=2:1
eden:survivor=8:2
![image.png](https://img-blog.csdnimg.cn/img_convert/9affce711e0a13cf08144d9ddd9bff1b.png#align=left&display=inline&height=574&margin=[object Object]&name=image.png&originHeight=574&originWidth=1104&size=64515&status=done&style=none&width=1104)

方法区(method area)

永久代 perm gen(1.8之前)

元空间 meta space(1.8之后)

对象的栈上分配

栈上分配

对象小
没有逃逸分析
可以标量替换

线程本地缓存TLAB(thread local allocate buffer)

永久代

eden

对象何时进入老年代

  1. 当年龄超过XX:MaxTenuringThreshold就放入老年代
  2. 动态分配:当survivor中存活对象总的年龄超过一半时就会进入老年代
  3. 分配担保:当survivor空间不够了时,空间担保分配进入老年代

常见的垃收集器

![image.png](https://img-blog.csdnimg.cn/img_convert/39835d1137e41a418cae9c230a9f5f0e.png#align=left&display=inline&height=473&margin=[object Object]&name=image.png&originHeight=473&originWidth=1350&size=59374&status=done&style=none&width=1350)

serial

单线程调用回收,stop-the-world

serialOld

单线程回收,stop-the-world

Parallel Scavenge

并发回收,stop-the-world

Parallel Old

并发回收,stop-the-world

ParallelNew

基于parallel scavenge增强,配合使用CMS

CMS(concurrent mark sweep)

并发的标记、清除垃圾

过程
  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清理
问题
  1. memory fragment(使用的是标记——清除算法)
    1. -XX:+UseCompactAtFullCollection -XX:+CMSGCsBeforeCompaction
  2. floating garbage(并发清理过程中)
    1. Concurrent Mode Failure
      1. 降低出发CMS的阈值:-XX:CMSInitiatingOccupancyFraction 默认92%
    2. PromotionFaild
      1. 解决方案类似,保持老年代有足够的空间

G1

1.8成熟,1.9默认
使用三色标记

ZGC

Shenandoah

Epsilon

使用GC

  1. -XX:+UseSerialGC => Serial + SerialOld
  2. -XX:+UseParallelGC => ParallelScavenge + SerialOld (ParallelScavenge +ParallelOld 1.8默认)
  3. -XX:+UseParallelOldGC => Parallel Scavenge + Parallel Old
  4. -XX:+UseConcMarkSweepGC => ParaNew + CMS + SerialOld(当cms执行的过程中导致碎片空间太多时就会采用SerialOld)
  5. -XX:+UseG1GC

JVM调优

命令介绍

分类
  • -xxxxx:表示标准命令
  • -x:xxx:表示特定版本使用的命令
  • -xx:xxx:表示不稳定,下一版本可能就不支持了
基本概念
  1. 吞吐量:用户程序执行时间 / (用户程序执行时间 + 垃圾回收时间)
  2. 响应时间:STW,stw越少响应时间越高
  3. 追求吞吐量:Parallel Scavenger + Parallel + Old,追求响应时间:(1.8)G1、ParallelNew + CMS,也可以在响应时间满足的情况下提升吞吐量
什么是调优
  1. 根据需求进行jvm规划和预调优
  2. 优化运行jvm环境
  3. 解决jvm运行过程中出现的各种问题(OOM)

问题解决
  1. CPU使用率达到100%
    1. 先通过top查看那个进程占用cpu资源最多top
    2. 找到进程中那个线程占用CPU资源最多top -Hp
    3. 通过jstack将线程的堆栈保存下来
    4. 查找哪个方法比较耗时间jstack
  2. CPU内存使用飚高
    1. 到处堆内存(jmap)
    2. 分析(jprofiler、jstat、jvisualvm。。)
  3. 监控CPU
    1. jprofiler、jstat、jvisualvm
具体
  1. 查看对象创建情况(OOM)
    1. jmap => OOM
      1. -histo 查看创建了多少对象
      2. -dump 创建转储文件,一般内存太大会有卡顿,当有多态服务器备份的时候,可以使用
    2. 图形界面:一般用于测试(jconsole、jvisualvm、jprofiler)
    3. arthas在线监控
  2. 查看线程运行情况
    1. jstack

并发回收算法

G1

三色标记(并发标记)

对于对象存在着不同的状态

  1. 概念
    1. 黑色:表示该对象本身以及对象内的属性都被标记为非garbage
    2. 灰色:表示该对象本身被标记为非garbage,但是对象内的属性并没有被标记成非garbage
    3. 白色:表示该对象没有被标记成非garbage
  2. 问题:
    1. 漏标:表示在标记过程中本来有指向某个白色对象的引用突然消失,且有黑色对象指向该对象
      1. 必要条件:当前白色对象被黑色标记指向,原来与灰色对象关联的引用消失
      2. 问题:因为黑色对象已经被标记完,于是在重新标记的时候就不会再次扫描黑色对象所指向的对象,于是白色对象就会被当做garbage被回收掉(没有引用指向)
      3. 解决方法:
        1. increment uodate(CMS):记录增加的引用,并修改当前对象的眼色,于是当再次标记的时候就会再次找到白色对象,并进行标记,就不会被回收,这个方式就会扫描整个堆去找到原黑色对象的指向
        2. SATB(snapshot at the begining)(G1):记录删除的引用,并放置到本身的一个栈中,结合RSet,在重新标记的过程中就会扫描该对象的栈中的记录并找到白色对象,白色对象就根据自身的Rset找到有黑色对象指向自己,于是就重新标记眼色不会被回收
        3. 为什么G1使用SATB:因为g1的实现是将堆分成不同的region,于是,为了减少扫描所有的region,所以就会使用satb结合RSet来只需要扫描跟自身有关的对象

颜色指针

card table

当进行fgc时需要扫描整个old区,比较耗时,因此基于bitmap的cardtable就诞生了,如果old中有对象指向y区则会被标记成dirtycard,在扫描的时候只需要扫描cardtable即可

CSet(collection set)

记录了该对象指向的其他对象引用

Rset(Remember Set)

记录了所以指向该对象的其他对象一样


纤程

线程 vs 纤程

  1. 一个在内核态、一个在用户态
  2. 一个轻量级、一个重量级
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jonny Jiang-zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值