Java线程安全与对象头结构信息

news2025/7/9 19:29:52

文章目录

  • 一 线程安全问题
    • 1.1 什么是线程安全问题?
    • 1.2 自增运算真的线程安全吗?
    • 1.3 Synchronized锁表现三种形势?
      • 1.3.1 synchronized同步方法
      • 1.3.2 synchronized同步代码块
      • 1.3.3 synchronized静态方法
      • 1.3.4 总结
  • 二 Java对象结构与内置锁
    • 2.1 Java对象结构
      • 2.2.1 对象头
      • 2.2.2 对象体
      • 2.2.3 对齐字节
      • 2.2.4 注意点
    • 2.2 Mark Word 详细介绍
    • 2.3 JOL工具查看对象的布局

Java内置锁是一个互斥锁,这就意味着最多只有一个线程能够获得该锁,当线程B尝试去获得线程A持有的内置锁时,线程B必须等待或者阻塞,直到线程A释放这个锁,如果线程A不释放这个锁,那么线程B将永远等待下去。线程进入同步代码块或方法时会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁保护的同步代码块或方法。
aee901184d71990bfc44d6f3e75a667d_v2-f944239c363fd924bbc47d819ea416c5_1440w_source=172ae18b.jpg

一 线程安全问题

1.1 什么是线程安全问题?

白话:当多个线程启动,对进程中同一资源进行操作时,可能会发生线程安全问题。
专业术语:当多个线程并发访问某个Java对象(Object)时,无论系统如何调度这些线程,也无论这些线程将如何交替操作,这个对象都能表现出一致的、正确的行为,那么对这个对象的操作是线程安全的。

1.2 自增运算真的线程安全吗?

案例

package class02;

/**
 * @description:
 * @author: shu
 * @createDate: 2022/11/3 19:55
 * @version: 1.0
 */
public class NotSafePlus {
    public Integer num=0;
    

    public Integer getNum() {
        return num;
    }

    public void setNum() {
        this.num ++;
    }
}
package class02;

/**
 * @description:
 * @author: shu
 * @createDate: 2022/11/3 19:57
 * @version: 1.0
 */

import java.util.concurrent.CountDownLatch;


public class PlusTest {
    static final int MAX_TREAD = 10;
    static final int MAX_TURN = 1000;

    public static void main(String[] args) throws InterruptedException {
        //倒数闩,需要倒数MAX_TREAD次
        CountDownLatch latch = new CountDownLatch(MAX_TREAD);

        NotSafePlus counter = new NotSafePlus();
        Runnable runnable = () ->
        {
            for (int i = 0; i < MAX_TURN; i++) {
                counter.setNum();
            }
            // 倒数闩减少一次
            latch.countDown();
        };
        for (int i = 0; i < MAX_TREAD; i++) {
            new Thread(runnable).start();
        }
        latch.await(); // 等待倒数闩的次数减少到0,所有的线程执行完成
        System.out.println("理论结果:" + MAX_TURN * MAX_TREAD);
        System.out.println("实际结果:" + counter.getNum());
        System.out.println("差距是:" + (MAX_TURN * MAX_TREAD - counter.getNum()));
    }
}

image.png
我们可以看出实际上在多线程环境中他并不是线程安全的,为啥?

原因

  • 实际上,一个自增运算符是一个复合操作,至少包括三个JVM指令:内存取值寄,存器增加1和,存值到内存。
  • 这三个指令在JVM内部是独立进行的,中间完全可能会出现多个线程并发进行。

JVM 字节码原因分析参考:https://blog.csdn.net/m0_49102380/article/details/123497368

改变

package class02;

/**
 * @description:
 * @author: shu
 * @createDate: 2022/11/3 19:55
 * @version: 1.0
 */
public class NotSafePlus {
    public Integer num=0;


    public Integer getNum() {
        return num;
    }

    public synchronized void setNum() {
        this.num ++;
    }
}

image.png

  • 临界区资源表示一种可以被多个线程使用的公共资源或共享数据,但是每一次只能有一个线程使用它。
  • 一旦临界区资源被占用,想使用该资源的其他线程则必须等待。
  • 我们可以使用synchronized关键字同步代码块,对临界区代码段进行排他性保护,对此保证线程安全。

1.3 Synchronized锁表现三种形势?

  • 在Java中,线程同步使用最多的方法是使用synchronized关键字。每个Java对象都隐含有一把锁,这里称为Java内置锁(或者对象锁、隐式锁)。使用synchronized(syncObject)调用相当于获取syncObject的内置锁,所以可以使用内置锁对临界区代码段进行排他性保护。
  • 由于每一个Java对象都有一把监视锁,因此任何Java对象都能作为synchronized的同步锁。

  • 对象锁:synchronized(this)以及非static的synchronized方法。锁的是这个对象。
  • 类锁:synchronized(类名.class)和static 的synchronized方法。锁的是那个写了synchronized关键字的方法或者代码块。(static方法可以直接类名.方法名()调用,无法使用this,所以它锁的不是this,而是类的Class对象)

1.3.1 synchronized同步方法

package class02.Synchronized;

/**
 * @description: synchronized
 * @author: shu
 * @createDate: 2022/11/9 14:18
 * @version: 1.0
 */
public class SynchronizedMethod{


    public static void main(String[] args) {

        SynchronizedMethod t=new SynchronizedMethod();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t.test("线程1");
            }
        }).start();


        SynchronizedMethod t1=new SynchronizedMethod();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t1.test("线程2");
            }
        }).start();


    }


    /**
     * synchronized 同步方法,对于普通方法,锁的拥有者是当前实例,不同实例并不互相影响
     * @param name
     */
    public synchronized void test(String name){
        System.out.println(name+"正在运行ing");
        try {
            Thread.sleep(2000);
        }catch (Exception e){
        }
        System.out.println(name+"运行结束end");
    }
}

image.png
我们可以从结果可以看出,线程没有阻塞运行,因此对于普通方法,如果是不同的对象实例锁是不起作用的

1.3.2 synchronized同步代码块

package class02.Synchronized;

/**
 * @description: 同步代码块
 * @author: shu
 * @createDate: 2022/11/9 14:28
 * @version: 1.0
 */
public class SynchronizedCode {
    public static void main(String[] args) {

        SynchronizedCode t=new SynchronizedCode();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t.test("线程1");
            }
        }).start();


        SynchronizedCode t1=new SynchronizedCode();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t1.test("线程2");
            }
        }).start();


    }


    /**
     * synchronized 同步代码块
     * @param name
     */
    public synchronized void test(String name){
        Object o=new Object();
        synchronized(o.getClass()) {
            System.out.println(name + "正在运行");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
            }
            System.out.println(name + "运行结束");
        }
    }
}

image.png
我们看出,线程阻塞运行,依次获取锁对象

1.3.3 synchronized静态方法

package class02.Synchronized;

/**
 * @description: 静态同步代码
 * @author: shu
 * @createDate: 2022/11/9 14:34
 * @version: 1.0
 */
public class SynchronizedStaticMethods {
    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedStaticMethods.test("线程1");
            }
        }).start();

        
        new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedStaticMethods.test("线程2");
            }
        }).start();


    }


    /**
     * synchronized 对于静态同步方法,锁的是当前类的Class对象。
     * @param name
     */
    public static synchronized void test(String name){
        System.out.println(name+"正在运行");
        try {
            Thread.sleep(2000);
        }catch (Exception e){
        }
        System.out.println(name+"运行结束");
    }
}

image.png
我们可以看出,当前线程也是阻塞运行的

1.3.4 总结

synchronized方法和synchronized同步块有什么区别呢?

  • synchronized方法是一种粗粒度的并发控制,某一时刻只能有一个线程执行该synchronized方法。
  • synchronized代码块是一种细粒度的并发控制,处于synchronized块之外的其他代码是可以被多个线程并发访问的。
     public class TwoPlus{
     
         private int sum1 = 0;
         private int sum2 = 0;
         private Integer sum1Lock = new Integer(1); // 同步锁一
         private Integer sum2Lock = new Integer(2); // 同步锁二
     
         public void plus(int val1, int val2){
            //同步块1
             synchronized(this.sum1Lock){
                 this.sum1 += val1;
             }
            //同步块2
            synchronized(this.sum2Lock){
                 this.sum2 += val2;
             }
         }
     }

二 Java对象结构与内置锁

2.1 Java对象结构

Java对象(Object实例)结构包括三部分:对象头、对象体和对齐字节
Java对象结构.png

2.2.1 对象头

说明

  • Mark Word(标记字)字段主要用来表示对象的线程锁状态,另外还可以用来配合GC存放该对象的hashCode。
  • Class Pointer(类对象指针)字段是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例。
  • Array Length(数组长度)字段占用32位(在32位JVM中)字节,这是可选的,只有当本对象是一个数组对象时才会有这个部分。

2.2.2 对象体

对象体包含对象的实例变量(成员变量),用于成员属性值,包括父类的成员属性值。这部分内存按4字节对齐。

说明

  • 对象体用于保存对象属性值,是对象的主体部分,占用的内存空间大小取决于对象的属性数量和类型。

2.2.3 对齐字节

  • 对齐字节也叫作填充对齐,其作用是用来保证Java对象所占内存字节数为8的倍数HotSpot VM的内存管理要求对象起始地址必须是8字节的整数倍。
  • 对象头本身是8的倍数,当对象的实例变量数据不是8的倍数时,便需要填充数据来保证8字节的对齐。

说明

  • 对齐字节并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。当对象实例数据部分没有对齐(8字节的整数倍)时,就需要通过对齐填充来补全。

2.2.4 注意点

  • Mark Word、Class Pointer、Array Length等字段的长度都与JVM的位数有关。
  • Mark Word的长度为JVM的一个Word(字)大小,也就是说32位JVM的Mark Word为32位,64位JVM的Mark Word为64位。
  • Class Pointer(类对象指针)字段的长度也为JVM的一个Word(字)大小,即32位JVM的Mark Word为32位,64位JVM的Mark Word为64位。
  • 对于对象指针而言,如果JVM中的对象数量过多,使用64位的指针将浪费大量内存,通过简单统计,64位JVM将会比32位JVM多耗费50%的内存。
  • 为了节约内存可以使用选项+UseCompressedOops开启指针压缩。UseCompressedOops中的Oop为Ordinary object pointer(普通对象指针)的缩写。

手动开启Oop对象指针压缩

java -XX:+UseCompressedOops mainclass

手动关闭Oop对象指针压缩

java -XX:-UseCompressedOops mainclass
  • Array Length字段的长度也随着JVM架构的不同而不同:在32位JVM上,长度为32位;在64位JVM上,长度为64位。64位JVM如果开启了Oop对象的指针压缩,Array Length字段的长度也将由64位压缩至32位。

2.2 Mark Word 详细介绍

Java内置锁涉及很多重要信息,这些都存放在对象结构中,并且存放于对象头的Mark Word字段中。

32 位Mark Word结构

epub_38103745_30.jpg

64 位Mark Word结构

epub_38103745_31.jpg

lock:锁状态标志位

lock:锁状态标记位,占两个二进制位,由于希望用尽可能少的二进制位表示尽可能多的信息,因此设置了lock标记。该标记的值不同,整个Mark Word表示的含义就不同。

biased_lock:对象是否启用偏向锁标记

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

组合

epub_38103745_32.jpg

age : Java对象分代年龄

age:4位的Java对象分代年龄。在GC中,对象在Survivor区复制一次,年龄就增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,因此最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

identity_hashcode:对象标识HashCode(哈希码)

identity_hashcode:31位的对象标识HashCode(哈希码)采用延迟加载技术,当调用Object.hashCode()方法或者System.identityHashCode()方法计算对象的HashCode后,其结果将被写到该对象头中。当对象被锁定时,该值会移动到Monitor(监视器)中。

thread:54位的线程ID值为持有偏向锁的线程ID

epoch:偏向时间戳。

ptr_to_lock_record:占62位,在轻量级锁的状态下指向栈帧中锁记录的指针。

ptr_to_heavyweight_monitor:占62位,在重量级锁的状态下指向对象监视器的指针。

2.3 JOL工具查看对象的布局

  • 依赖包
<!--Java Object Layout -->
<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.11</version>
</dependency>
  • 工具类
package com.shu;

import java.io.*;

/**
 * byte数组工具类实现byte[]与文件之间的相互转换
 */
public class ByteUtil {
    /**
     * 字节数据转字符串专用集合
     */
    private static final char[] HEX_CHAR =
            {'0', '1', '2', '3', '4', '5', '6',
                    '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * 字节数据转十六进制字符串
     *
     * @param data 输入数据
     * @return 十六进制内容
     */
    public static String byteArrayToString(byte[] data) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < data.length; i++) {
            // 取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移
            stringBuilder.append(HEX_CHAR[(data[i] & 0xf0) >>> 4]);
            // 取出字节的低四位 作为索引得到相应的十六进制标识符
            stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);
            if (i < data.length - 1) {
                stringBuilder.append(' ');
            }
        }
        return stringBuilder.toString();
    }

    /**
     * byte转换hex函数
     *
     * @param byteArray
     * @return
     */
    public static String byteToHex(byte[] byteArray) {
        StringBuffer strBuff = new StringBuffer();
        for (int i = 0; i < byteArray.length; i++) {
            if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
                strBuff.append("0").append(
                        Integer.toHexString(0xFF & byteArray[i]));
            } else {
                strBuff.append(Integer.toHexString(0xFF & byteArray[i]));
            }
            strBuff.append(" ");
        }
        return strBuff.toString();
    }

    /**
     * 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。
     */
    public static byte[] readFileByBytes(String fileName) {
        File file = new File(fileName);
        InputStream in = null;
        byte[] txt = new byte[(int) file.length()];
        try {
            // 一次读一个字节
            in = new FileInputStream(file);
            int tempByte;
            int i = 0;
            while ((tempByte = in.read()) != -1) {
                txt[i] = (byte) tempByte;
                i++;
            }
            in.close();
            return txt;
        } catch (IOException e) {
            e.printStackTrace();
            return txt;
        }
    }

    /**
     * 获得指定文件的byte数组
     */
    public static byte[] getBytes(String filePath) {
        byte[] buffer = null;
        try {
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1000];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer;
    }

    /**
     * 根据byte数组,生成文件
     */
    public static void saveFile(byte[] bfile, String filePath) {
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file = null;
        try {
            File dir = new File(filePath);
            //判断文件目录是否存在
            if (!dir.exists() && dir.isDirectory()) {
                dir.mkdirs();
            }
            file = new File(filePath);
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);
            bos.write(bfile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }


    public static final int UNICODE_LEN = 2;


    /**
     * int转换为小端byte[](高位放在高地址中)
     *
     * @param iValue
     * @return
     */
    public static byte[] int2Bytes_LE(int iValue) {
        byte[] rst = new byte[4];
        // 先写int的最后一个字节
        rst[0] = (byte) (iValue & 0xFF);
        // int 倒数第二个字节
        rst[1] = (byte) ((iValue & 0xFF00) >> 8);
        // int 倒数第三个字节
        rst[2] = (byte) ((iValue & 0xFF0000) >> 16);
        // int 第一个字节
        rst[3] = (byte) ((iValue & 0xFF000000) >> 24);
        return rst;
    }

    /**
     * long转成字节
     *
     * @param num
     * @return
     */
    public static byte[] long2bytes(long num) {
        byte[] b = new byte[8];
        for (int i = 0; i < 8; i++) {
            b[i] = (byte) (num >>> (56 - (i * 8)));
        }
        return b;
    }

    /**
     * bytes2bytes 大端转小端
     *
     * @param input
     * @return
     */
    public static byte[] bytes2bytes_LE(byte[] input) {
        int len = input.length;
        byte[] b = new byte[len];
        for (int i = 0; i < len; i++) {
            b[i] = input[len - 1 - i];
        }
        return b;
    }

    /**
     * long转成字节 小端
     *
     * @param num
     * @return
     */
    public static byte[] long2bytes_LE(long num) {
        byte[] b = long2bytes(num);
        return bytes2bytes_LE(b);
    }

    /**
     * 转成long
     *
     * @param b 字节
     * @return
     */
    public static long bytes2long(byte[] b) {
        long temp = 0;
        long res = 0;
        for (int i = 0; i < 8; i++) {
            res <<= 8;
            temp = b[i] & 0xff;
            res |= temp;
        }
        return res;
    }

    public static int bytes2int(byte[] bytes) {
        int num = bytes[0] & 0xFF;
        num |= ((bytes[1] << 8) & 0xFF00);
        num |= ((bytes[2] << 16) & 0xFF0000);
        num |= ((bytes[3] << 24) & 0xFF000000);
        return num;
    }

    /**
     * 转换String为byte[]
     *
     * @param str
     * @return
     */
    public static byte[] string2Bytes_LE(String str) {
        if (str == null) {
            return null;
        }
        char[] chars = str.toCharArray();

        byte[] rst = chars2Bytes_LE(chars);

        return rst;
    }


    /**
     * 转换字符数组为定长byte[]
     *
     * @param chars 字符数组
     * @return 若指定的定长不足返回null, 否则返回byte数组
     */
    public static byte[] chars2Bytes_LE(char[] chars) {
        if (chars == null)
            return null;

        int iCharCount = chars.length;
        byte[] rst = new byte[iCharCount * UNICODE_LEN];
        int i = 0;
        for (i = 0; i < iCharCount; i++) {
            rst[i * 2] = (byte) (chars[i] & 0xFF);
            rst[i * 2 + 1] = (byte) ((chars[i] & 0xFF00) >> 8);
        }

        return rst;
    }


    /**
     * 转换byte数组为int(小端)
     *
     * @return
     * @note 数组长度至少为4,按小端方式转换,即传入的bytes是小端的,按这个规律组织成int
     */
    public static int bytes2Int_LE(byte[] bytes) {
        if (bytes.length < 4)
            return -1;
        int iRst = (bytes[0] & 0xFF);
        iRst |= (bytes[1] & 0xFF) << 8;
        iRst |= (bytes[2] & 0xFF) << 16;
        iRst |= (bytes[3] & 0xFF) << 24;

        return iRst;
    }


    /**
     * 转换byte数组为int(大端)
     *
     * @return
     * @note 数组长度至少为4,按小端方式转换,即传入的bytes是大端的,按这个规律组织成int
     */
    public static int bytes2Int_BE(byte[] bytes) {
        if (bytes.length < 4)
            return -1;
        int iRst = (bytes[0] << 24) & 0xFF;
        iRst |= (bytes[1] << 16) & 0xFF;
        iRst |= (bytes[2] << 8) & 0xFF;
        iRst |= bytes[3] & 0xFF;

        return iRst;
    }


    /**
     * 转换byte数组为Char(小端)
     *
     * @return
     * @note 数组长度至少为2,按小端方式转换
     */
    public static char Bytes2Char_LE(byte[] bytes) {
        if (bytes.length < 2)
            return (char) -1;
        int iRst = (bytes[0] & 0xFF);
        iRst |= (bytes[1] & 0xFF) << 8;

        return (char) iRst;
    }


    /**
     * 转换byte数组为char(大端)
     *
     * @return
     * @note 数组长度至少为2,按小端方式转换
     */
    public static char Bytes2Char_BE(byte[] bytes) {
        if (bytes.length < 2)
            return (char) -1;
        int iRst = (bytes[0] << 8) & 0xFF;
        iRst |= bytes[1] & 0xFF;

        return (char) iRst;
    }

    public static String byte2BinaryString(byte nByte) {
        StringBuilder nStr = new StringBuilder();
        for (int i = 7; i >= 0; i--) {
            int j = (int) nByte & (int) (Math.pow(2, (double) i));
            if (j > 0) {
                nStr.append("1");
            } else {
                nStr.append("0");
            }
        }
        return nStr.toString();
    }

}
  • 对象编写
package com.shu;

/**
 * @description:
 * @author: shu
 * @createDate: 2022/11/10 9:31
 * @version: 1.0
 */
import org.openjdk.jol.info.ClassLayout;


public class ObjectLock
{
    private Long amount = 0L; //整型字段占用4字节

    public void increase()
    {
        synchronized (this)
        {
            amount++;
        }
    }

    /**
     * 输出十六进制、小端模式的hashCode
     */
    public String hexHash()
    {
        //对象的原始 hashCode,Java默认为大端模式
        int hashCode = this.hashCode();

        //转成小端模式的字节数组
        byte[] hashCode_LE = ByteUtil.int2Bytes_LE(hashCode);

        //转成十六进制形式的字符串
        return ByteUtil.byteToHex(hashCode_LE);
    }

    /**
     * 输出二进制、小端模式的hashCode
     */
    public String binaryHash()
    {
        //对象的原始 hashCode,Java默认为大端模式
        int hashCode = this.hashCode();

        //转成小端模式的字节数组
        byte[] hashCode_LE = ByteUtil.int2Bytes_LE(hashCode);

        StringBuffer buffer=new StringBuffer();
        for (byte b:hashCode_LE)
        {
            //转成二进制形式的字符串
            buffer.append( ByteUtil.byte2BinaryString(b));
            buffer.append(" ");
        }
        return buffer.toString();
    }

    /**
     * 输出十六进制、小端模式的ThreadId
     */
    public String hexThreadId()
    {
        //当前线程的 threadID,Java默认为大端模式
        long threadID = Thread.currentThread().getId();

        //转成小端模式的字节数组
        byte[] threadID_LE = ByteUtil.long2bytes_LE(threadID);

        //转成十六进制形式的字符串
        return ByteUtil.byteToHex(threadID_LE);
    }

    /**
     * 输出二进制、小端模式的ThreadId
     */
    public String binaryThreadId()
    {
        //当前线程的 threadID,Java默认为大端模式
        long threadID = Thread.currentThread().getId();
        //转成小端模式的字节数组
        byte[] threadID_LE = ByteUtil.long2bytes_LE(threadID);

        StringBuffer buffer=new StringBuffer();
        for (byte b:threadID_LE)
        {
            //转成二进制形式的字符串
            buffer.append( ByteUtil.byte2BinaryString(b));
            buffer.append(" ");
        }
        return buffer.toString();
    }

    public void printSelf()
    {
        // 输出十六进制、小端模式的hashCode
        System.out.println("lock hexHash= " + hexHash());

        // 输出二进制、小端模式的hashCode
        System.out.println("lock binaryHash= " + binaryHash());

        //通过JOL工具获取this的对象布局
        String printable = ClassLayout.parseInstance(this).toPrintable();

        //输出对象布局
        System.out.println("lock = " + printable);
    }
    // 省略其他
}
  • 测试
package com.shu;

import org.openjdk.jol.vm.VM;

/**
 * @description:
 * @author: shu
 * @createDate: 2022/11/10 9:43
 * @version: 1.0
 */
public class InnerLockTest {
    public static void main(String[] args) {
        System.out.println(VM.current().details());
        ObjectLock objectLock=new ObjectLock();
        System.out.println("object status");
        objectLock.printSelf();
    }
}

image.png
image.png

  • 当前JVM的运行环境为64位虚拟机。
  • 运行结果中输出了ObjectLock的对象布局,所输出的ObjectLock对象为16字节,其中对象头(Object Header)占12字节,剩下的4字节由amount属性(字段)占用。
  • 由于16字节为8字节的倍数,因此没有对齐填充字节(JVM规定对象头部分必须是8字节的倍数,否则需要对齐填充)。
  • 对象一旦生成了哈希码,它就无法进入偏向锁状态。也就是说,只要一个对象已经计算过哈希码,它就无法进入偏向锁状态。
  • 当一个对象当前正处于偏向锁状态,并且需要计算其哈希码的话,它的偏向锁会被撤销,并且锁会膨胀为重量级锁。

补充

大端序:大端模式是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。大端存放模式有点类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放。
小端序:小端模式是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,此模式和日常的数字计算在方向上是一致的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浏览器自动化利器Selenium IDE使用指南

文章目录前言一、安装及界面1.1 安装1.2 界面二、常用命令2.1 通用2.2 表单2.3 流程控制三、常用操作3.1 命令操作3.2 js脚本3.3 录制3.4 导出四、实际操作例子4.1 红黑树插入可视化4.2 github下载参考前言 Selenium 是一个浏览器自动化框架&#xff0c;专门为 W3C WebDriver …

创龙AD+全志T3 TL7606I模块测试

上一篇&#xff1a;创龙AD全志T3 ad_display 开发案例(2) 前 言 本文主要介绍基于全志科技T3(ARM Cortex-A7)处理器的8/16通道AD采集开发案例&#xff0c;使用核芯互联CL1606/CL1616AD芯片&#xff0c;亦适用于ADI AD7606/AD7616。CL1606/CL1616与AD7606/AD7616软硬件兼容。 …

力扣(LeetCode)775. 全局倒置与局部倒置(C++)

模拟 理解题&#xff0c;全局倒置就是不相邻的逆序对&#xff0c;局部倒置就是相邻的逆序对。提示中给出&#xff0c;0<nums[i]<n0 < nums[i] < n0<nums[i]<n &#xff0c;其中 nnums.lengthn nums.lengthnnums.length , numsnumsnums 中的所有整数 互不相…

什么是 rektguy NFT系列?

rektguy 系列是一组闪烁着霓虹灯颜色的饮酒骷髅 该项目背后的团队没有提出路线图或分支项目 艺术家 OSF 已经在 NFT 生态系统中拥有一个成熟的粉丝群体 rektguy NFT系列由 8,814 个 NFT 组成&#xff0c;展示了穿着连帽衫、用瓶子喝水的骷髅。这些人物由深黑色背景上闪烁的霓…

[论文阅读] 颜色迁移-EM概率分割的局部颜色迁移

[论文阅读] 颜色迁移-EM概率分割的局部颜色迁移 文章: Local Color Transfer via Probabilistic Segmentation by Expectation-Maximization,[paper][code(未公开)] 本文目的为: 图像局部颜色迁移. 1-符号说明 在进行本文算法原理说明之前, 先对一些英文缩写进行简单说明: …

一次nacos 导致的 CPU 飙高问题

序 今天下午突然 出现 测试环境 cpu飙高&#xff0c;干到了 60%&#xff0c;其他项目 响应时间明显变长。。。有点吓人&#xff0c;不想背锅 项目背景 出问题的项目是 需要连接各个不同nacos 和不同的 namespace 进行对应操作的 一个项目&#xff0c;对nacos的操作都是httpC…

JavaScript break 、 continue和return 语句介绍

目录 一、Break 1、介绍 2、代码&#xff1a; 3、示例&#xff1a; 二、Continue 1、介绍 2、代码 3、示例 三、break 、continue总结&#xff1a; 代码 示例 四、Return 1、简介 2、语法&#xff1a; 3、代码 4、示例 五、return、break、continue总结 1、re…

02【MyBatis框架的CRUD】

二、MyBatis框架的CRUD 重新搭建一个新的MyBatis环境&#xff0c;进行MyBatis的CRUD测试&#xff1b; 参考&#xff1a;01【MyBatis-快速入门】 2.1 新增 2.1.1 dao接口 package com.dfbz.dao;/*** author lscl* version 1.0* intro:*/ import com.dfbz.entity.Emp;public i…

秒杀系统设计(微服分布式)

流程图(分布式) 例子 现场要卖1000件下面这个婴儿纸尿裤&#xff0c;根据以往这样秒杀活动的数据经验来看&#xff0c;目测来抢这100件纸尿裤的人足足有10万人 问题 高并发 秒杀的特点就是这样时间极短、 瞬间用户量大。 正常的店铺营销都是用极低的价格配合上短信、APP的…

自制操作系统日记(6):静态桌面初步

代码仓库地址&#xff1a;https://github.com/freedom-xiao007/operating-system 简介 在上篇中我们成功的加载跳转执行了C语言的代码&#xff0c;本篇中将跟随书籍&#xff0c;初步展示了一个系统页面的初步界面&#xff0c;看到桌面那刻还是有点意思的 最终结果展示 不多…

贵的键盘就一定好吗?程序员该怎样选择一款适合自己的键盘呢,来这里参考下吧

&#x1f3ac; 博客主页&#xff1a;https://xiaoy.blog.csdn.net &#x1f3a5; 本文由 呆呆敲代码的小Y 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;Unity系统学习专栏 &#x1f332; 游戏制作专栏推荐&#xff1a;游戏制作 &…

唯亚威VIAVIFiberChek Sidewinder光纤手持式检测仪

唯亚威FiberChek Sidewinder 是业界“全功能”手持式检测和分析解决方案&#xff0c;适用于诸如 MPO 等多光纤连接器。唯亚威VIAVI 屡获殊荣的 FiberChek 系列中的这款最新产品提供了一个完全自动化的解决方案&#xff0c;可对 MPO 或其他多光纤连接器中的每条光纤进行检测和分…

C. Peaceful Rooks(并查集找环)

Problem - 1411C - Codeforces 题意: 你会得到一个nn的棋盘。棋盘的行和列从1到n编号。单元格(x,y)位于列号x和行号y的交点上。 车是一个棋子&#xff0c;它可以在一个回合内垂直或水平地移动任何数量的单元。棋盘上有m个车(m<n)&#xff0c;其放置方式是没有一对车互相攻…

空间金字塔池化Spatial Pyramid Pooling

1. 概述 通常在卷积神经网络CNN中主要是由卷积层&#xff08;包括Convolution和Pooling两部分组成&#xff09;和全连接层组成&#xff0c;对于任意一张大小的图片&#xff0c;通常需要通过裁剪或者拉伸变形的方式将其转换成固定大小的图片&#xff0c;这样会影响到对图片的识…

从源码角度分析Mybatis级联映射的实现原理

Mybatis是一个半自动化ORM框架&#xff0c;可以将数据库中的记录转换为java实体对象&#xff0c;但是java实体属性通常采用驼峰命名法&#xff0c;而数据库字段习惯采用下划线分隔命名法&#xff0c;因此需要用户指定java实体属性与数据库表字段之间的映射关系。 mybatis的Mapp…

智慧网格解决方案-最新全套文件

智慧网格解决方案-最新全套文件一、建设背景二、思路架构三、建设方案1、民生管理2、网格化管理3、智慧党建4、网上政务5、综治管理四、获取 - 智慧网格全套最新解决方案合集一、建设背景 在我国现代化转型中&#xff0c;社会环境的复杂性和不确定性增强&#xff0c;传统的基层…

智能合约开发 基于Hardhat(实操)

Hardhat是一个编译、部署、测试和调试以太坊应用的开发环境。 ​ Hardhat内置了Hardhat网络&#xff0c;这是一个专为开发设计的本地以太坊网络。主要功能有Solidity调试&#xff0c;跟踪调用堆栈、 console.log() 和交易失败时的明确错误信息提示等 ​安装 # 创建项目目录 …

TSRFormer:复杂场景的表格结构识别新利器

编者按&#xff1a;近年来&#xff0c;各大企业和组织机构都在经历数字化转型。将文档转换成计算机所能识别的样态&#xff0c;是数字化转型的关键步骤&#xff0c;如何识别出图片中表格具体的结构与内容&#xff0c;并直接提取其中的数据和信息是学术界和工业界共同瞩目的焦点…

C语言操作符大全(建议收藏)

前言 &#x1f496;作者&#xff1a;龟龟不断向前 ✨简介&#xff1a;宁愿做一只不停跑的慢乌龟&#xff0c;也不想当一只三分钟热度的兔子。 &#x1f47b;专栏&#xff1a;C初阶知识点 &#x1f47b;工具分享&#xff1a; 刷题&#xff1a; 牛客网 leetcode笔记软件&#xff…

操作系统4小时速成:进程管理占考试40%,进程状态,组织,通信,线程拥有调度,进程拥有资源,进程和线程的区别

操作系统4小时速成&#xff1a;进程管理占考试40%&#xff0c;进程状态&#xff0c;组织&#xff0c;通信&#xff0c;线程拥有调度&#xff0c;进程拥有资源&#xff0c;进程和线程的区别 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招…