【Java】Unsafe应用解析

news2024/12/26 20:42:09

目录

一.功能介绍

二.如何获取Unsafe对象

1.从getUnsafe静态方法获取

2.通过反射获取单例对象theUnsafe

三.Unsafe常用API操作

3.1.线程调度

3.1.1 多线程锁

3.1.2 多线程CAS操作

3.1.3 线程的挂起和恢复

3.2.内存屏障

 3.3.内存管理

3.4.对象操作

3.5.运行时动态创建类

3.6.Class相关

3.7.数组元素相关

四.总结


最初在看到Java AQS相关代码的时候发现Unsafe在加锁和释放锁时候使用,后来在Java并发编程java.util.concurrent包下经常看到,没有做过多深入研究,直到在Netty框架开发中涉及到了堆外内存(DirectoryByteBuffer)发现了Unsafe的奇妙之处,它避开了Java JVM 堆的内存管理,直接申请分配内存空间,不受JVM GC垃圾回收的限制。当然我们的应用程序运行在Java虚拟机中,JVM的垃圾回收机制释放堆内内存,释放堆内内存的对象所引用的堆外内存的数据结构要不要释放?答案是肯定的。为了巧妙解决这个问题Java提供了弱引用对象(Reference),目的是在JVM GC垃圾回收后触发一个操作:将要释放的堆外内存的对象的引用赋值,然后将引用Reference对象放入ReferenceQueue,ReferenceQueue的数据需要手动回收释放堆外内存(System.gc()),这个后续章节可以单独介绍。

Java Unsafe类大家都叫它魔法类,Unsafe类位于rt.jar包下sun.misc包下的一个类,Unsafe类提供了硬件级别的原子操作,类中的方法都是native方法,它们使用JNI的方式访问本地C++实现库。由此提供了一些绕开JVM的更底层功能,可以提高程序效率,主要提供一些用于执行低级别、不安全操作的方法,但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。

一.功能介绍

要了解Unsafe具备什么能力,首先从宏观上全面看一下(美团技术团队总结如下图)

我们发现Unsafe提供内存操作、CAS操作、Class相关、对象操作、线程调度、内存屏障等操作,接下来结合源码进行分析。

二.如何获取Unsafe对象

首先我们先找到Unsafe类源码,源码可以从Open JDK下载查看OpenJDK8源码下载

public final class Unsafe {

    private static native void registerNatives();
    static {
        registerNatives();
        sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
    }

    private Unsafe() {}

    //私有的对象实例
    private static final Unsafe theUnsafe = new Unsafe();

    //静态方法
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

从上面源码看出获取Unsafe对象可以有以下2个方法:

1.从getUnsafe静态方法获取

其实不然,这个方法在return之前做了一个校验,他会通过VM.isSystemDomainLoader方法校验调用者的ClassLoader,此方法的实现如下

    public static boolean isSystemDomainLoader(ClassLoader loader) {
        return loader == null;
    }

如果调用者的ClassLoader==null,在getUnsafe方法中才可以成功返回实例,否则会抛出java.lang.SecurityException: Unsafe 异常。

要使用getUnsafe方法需要通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。

java -Xbootclasspath/a: ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径

2.通过反射获取单例对象theUnsafe

在源码中可以发现,它是用theUnsafe字段来引用unsafe实例,那我们可以尝试通过反射获取theUnsafe字段,进而获取Unsafe实例,代码如下:

public class UnSafeTest {

    @Test
    public void TestUnsafeOffset() throws NoSuchFieldException,IllegalAccessException{
        //获取unsafe对象实例
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe)field.get(null);

        //获取count属性的Feild
        OrderCount orderCount = new OrderCount();
        Field countField = OrderCount.class.getDeclaredField("count");
        countField.setAccessible(true);

        //获取当前的count值
        int currentCount = countField.getInt(orderCount);
        //计算count内存偏移量countOffset
        int countOffset =  (int)unsafe.objectFieldOffset(countField);
        //原子性修改count的偏移量为10
        unsafe.compareAndSwapInt(orderCount,countOffset,currentCount,10);

        //获取指定偏移量的int值
        System.out.println(unsafe.getInt(orderCount,countOffset));
    }
}
class OrderCount{

    private int count=0;


}

三.Unsafe常用API操作

3.1.线程调度

Unsafe提供了线程调度能力,包括线程锁、CAS操作、线程挂起和恢复等。

3.1.1 多线程锁

主要包括监视器锁定、解锁以及CAS相关的方法。这部分包括了monitorEnter、tryMonitorEnter、monitorExit等方法。其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用,代码如下:

public class UnSafeTest {

    
    @Test
    public void testLock() throws InterruptedException {
        OrderCount orderCount = new OrderCount();
        //初始化unsafe
        orderCount.initUnsafe();
        //同步线程计数器
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(()->{
            orderCount.addCount(10);
            countDownLatch.countDown();
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                orderCount.addCount(20);
                countDownLatch.countDown();
            }
        }).start();
        countDownLatch.await();
        //输出修改后的值
        System.out.println(orderCount.getCount());
    }
}
class OrderCount{

    private int count=0;
    private  Object lock = new Object();

    private Unsafe unsafe = null;

    public void initUnsafe(){
        //获取unsafe对象实例
        Field field = null;
        try {
            field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe)field.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }

    //加锁操作
    public void addCount(int addInt){
        unsafe.monitorEnter(lock);
        count+=addInt;
        unsafe.monitorExit(lock);
    }

    public int getCount(){
        return count;
    }
}

3.1.2 多线程CAS操作

Unsafe类的CAS操作可能是用的最多的,常用的几个方法

public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,expected,x);

它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。


    //针对Object对象进行CAS操作。即是对应Java变量引用o,原子性地更新o中偏移地址为offset的属性的值为x,当且仅的偏移地址为offset的属性的当前值为expected才会更新成功返回true,否则返回false。所以是一种乐观锁,效率高。expected类似版本号,每次修改需要携带版本号。
    //o:目标Java变量引用,需要变更的目标对象。
    //offset:要修改的属性相对于object对象内存地址的偏移地址。
    //expected:目标Java变量中的目标属性的期望的当前值。x:目标Java变量中的目标属性的目标更新值。
    //类似的方法有compareAndSwapInt,compareAndSwapLong,compareAndSwapObject,在Jdk8中基于CAS扩展出来的方法有getAndAddInt、getAndAddLong、getAndSetInt、getAndSetLong、getAndSetObject,它们的作用都是:通过CAS设置新的值,返回旧的值。
    public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
    //对int类型的CAS操作
    public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
    //对long类型的CAS操作                                              
    public final native boolean compareAndSwapLong(Object o, long offset,
                                                   long expected,
                                                   long x);


    //获取对象obj 中偏移量为offset 的变量volatile语义的当前值,并设置变量volatile 语义的值为update
    long getAndSetLong(Object obj, long offset, long update)

    //获取对象obj同中偏移量为offset 的变量volatile语义的当前值,并设置变量值为原始值+addValue
    long getAndAddLong(Object obj, long offset, long addValue)

3.1.3 线程的挂起和恢复

Unsafe提供了park()、unpark()等方法。将一个线程进行挂起是通过park()方法实现的,调用 park()方法后,线程将一直阻塞直到超时或者中断等条件出现。相对应的unpark()方法可以终止一个挂起的线程,使其恢复正常。

整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

    //释放被park阻塞的线程,也可以被使用来终止一个先前调用park导致的阻塞,即这两个方法的调用顺序可以是先unpark再park。
    public native void unpark(Object thread);

    //阻塞当前线程直到一个unpark方法出现(被调用)、一个用于unpark方法已经出现过(在此park方法调用之前已经调用过)、线程被中断或者time时间到期(也就是阻塞超时)。
    //在time非零的情况下,如果isAbsolute为true,time是相对于新纪元之后的毫秒,否则time表示纳秒。
    public native void park(boolean isAbsolute, long time);

3.2.内存屏障

在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。Unsafe提供方法如下:

    //在该方法之前的所有读操作,一定在load屏障之前执行完成。
    public native void loadFence();

    //在该方法之前的所有写操作,一定在store屏障之前执行完成
    public native void storeFence();

    //在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个(load屏障和store屏障)的合体功能。
    public native void fullFence();

在Java 8中引入了一种锁的新机制——StampedLock,它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现,这种乐观读锁类似于无锁的操作,完全不会阻塞写线程获取写锁,从而缓解读多写少时写线程“饥饿”现象。由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题,所以当使用StampedLock的乐观读锁时,需要确保数据的一致性。

 3.3.内存管理

这部分主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。

    //获取本地指针的大小(单位是byte),通常值为4(32位系统)或者8(64位系统)。常量ADDRESS_SIZE就是调用此方法。
    public native int addressSize();

    //获取本地内存的页数,此值为2的幂次方。
    //java.nio下的工具类Bits中计算待申请内存所需内存页数量的静态方法,其依赖于Unsafe中pageSize方法获取系统内存页大小实现后续计算逻辑
    public native int pageSize();

    //分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址。可以通过freeMemory方法释放内存块,或者通过reallocateMemory方法调整内存块大小。
    //bytes值为负数或者过大会抛出IllegalArgumentException异常,如果系统拒绝分配内存会抛出OutOfMemoryError异常。
    public native long allocateMemory(long bytes);

    //通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)。可以通过freeMemory方法释放内存块,或者通过reallocateMemory方法调整内存块大小。
    //bytes值为负数或者过大会抛出IllegalArgumentException异常,如果系统拒绝分配内存会抛出OutOfMemoryError异常。
    public native long reallocateMemory(long address, long bytes);

    //在给定的内存块中设置值。内存块的地址由对象引用o和偏移地址共同决定,如果对象引用o为null,offset就是绝对地址。第三个参数就是内存块的大小,如果使用allocateMemory进行内存开辟的话,这里的值应该和allocateMemory的参数一致。value就是设置的固定值,一般为0(这里可以参考netty的DirectByteBuffer)。
    //一般而言,o为null,所以有个重载方法是public native void setMemory(long offset, long bytes, byte value);,等效于setMemory(null, long offset, long bytes, byte value);。
    public native void setMemory(Object o, long offset, long bytes, byte value);

    //释放内存
    public native void freeMemory(long address);
    //内存拷贝
    public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
    //获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等
    public native Object getObject(Object o, long offset);
    //为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
    public native void putObject(Object o, long offset, Object x);
    //获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)
    public native byte getByte(long address);
    //为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)
    public native void putByte(long address, byte x);

Unsafe分配的内存,不受Integer.MAX_VALUE的限制,并且分配在非堆内存,使用它时,需要非常谨慎,忘记手动回收时,会产生内存泄露,可以通过Unsafe#freeMemory方法手动回收;非法的地址访问时,会导致JVM崩溃。在需要分配大的连续区域、实时编程(不能容忍JVM延迟)时,可以使用它,因为直接内存的效率会更好,详细介绍可以去看看Java的NIO源码,NIO中使用了这一技术。

JDK nio包中通过ByteBuffer#allocateDirect方法分配直接内存时,DirectByteBuffer的构造函数中就使用到了Unsafe的allocateMemory和setMemory方法:通过Unsafe.allocateMemory分配内存、Unsafe.setMemory进行内存初始化,而后构建一个虚引用Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放(通过在Cleaner中调用Unsafe#freeMemory方法),虚引用Cleaner对象下一节结合Reference再做介绍。

3.4.对象操作

主要包括基于偏移地址offset获取或者设置变量的值、基于偏移地址获取或者设置数组元素的值、对象非常规的创建等

    /*1.获取对象字段的值*/

    //通过给定的Java变量获取引用值。这里实际上是获取一个Java对象o中,获取偏移地址为offset的属性的值,此方法可以突破修饰符的抑制,也就是无视private、protected和default修饰符。
    // 类似的方法有getInt、getDouble等等。
    public native Object getObject(Object o, long offset);

    //此方法和上面的getObject功能类似,不过附加了'volatile'加载语义,也就是强制从主存中获取属性值。类似的方法有getIntVolatile、getDoubleVolatile等等。
    // 这个方法要求被使用的属性被volatile修饰,否则功能和getObject方法相同。
    public native Object getObjectVolatile(Object o, long offset);

    /*2.修改对象字段的值*/

    //设置Java对象o中偏移地址为offset的属性的值为x,此方法可以突破修饰符的抑制,也就是无视private、protected和default修饰符。用于修改修改非基本数据类型的值。
    //类似的方法有putInt、putDouble等等,用于修改基本数据类型的值,再次不再赘述。
    public native void putObject(Object o, long offset, Object x);


    //此方法和上面的putObject功能类似,不过附加了'volatile'加载语义,也就是设置值的时候强制(JMM会保证获得锁到释放锁之间所有对象的状态更新都会在锁被释放之后)更新到主存,从而保证这些变更对其他线程是可见的。
    // 类似的方法有putIntVolatile、putDoubleVolatile等等。这个方法要求被使用的属性被volatile修饰,否则功能和putObject方法相同。
    public native void putObjectVolatile(Object o, long offset, Object x);

    //设置o对象中offset偏移地址offset对应的Object型field的值为指定值x。这是一个有序或者有延迟的putObjectVolatile方法,并且不保证值的改变被其他线程立即看到。
    // 只有在field被volatile修饰并且期望被修改的时候使用才会生效。类似的方法有putOrderedInt和putOrderedLong。
    // 最终会设置成x,但是可能导致其他线程在之后的一小段时间内还是可以读到旧的值。关于该方法的更多信息可以参考并发编程网翻译的一篇文章《AtomicLong.lazySet是如何工作的?》,文章地址是“http://ifeve.com/how-does-atomiclong-lazyset-work/”。
    public native void putOrderedObject(Object o, long offset, Object x);


    /*3.获取对象的字段相对该对象地址的偏移量*/

    //返回给定的静态属性在它的类的存储分配中的位置(偏移地址)。即相对于 className.class 的偏移量,通过这个偏移量可以快速定位字段.
    // 注意:这个方法仅仅针对静态属性,使用在非静态属性上会抛异常。
    public native long staticFieldOffset(Field f);

    //返回给定的非静态属性在它的类的存储分配中的位置(偏移地址)。即字段到对象头的偏移量,通过这个偏移量可以快速定位字段.
    // 注意:这个方法仅仅针对非静态属性,使用在静态属性上会抛异常。
    public native long objectFieldOffset(Field f);

    //返回给定的静态属性的位置,配合staticFieldOffset方法使用。实际上,这个方法返回值就是静态属性所在的Class对象的一个内存快照
    // 注释中说到,此方法返回的Object有可能为null,它只是一个'cookie'而不是真实的对象,不要直接使用的它的实例中的获取属性和设置属性的方法,它的作用只是方便调用上面提到的像getInt(Object,long)等等的任意方法。
    public native Object staticFieldBase(Field f);

    /*4.创建对象*/   
    //绕过构造方法、初始化代码来非常规的创建对象,注意创建对象只是分配对象地址,不负责对象属性初始化
    public native Object allocateInstance(Class<?> cls) throws InstantiationException;

 以上每一个方法都可以通过代码实例来测试。

3.5.运行时动态创建类

Unsafe提供了运行时动态创建类,标准的动态加载类的方法是Class.forName()(在编写jdbc程序时,记忆深刻),使用Unsafe也可以动态加载java 的class文件。操作方式就是将.class文件读取到字节数据组中,并将其传到defineClass方法中。

public class DynamicCreateClass {
    private static Unsafe unsafe;
    //初始化Unsafe对象
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //加载类字节码内容
    private static byte[] getClassContent() throws Exception {
        File f = new File("target/classes/com/thread/test/juc/unsafe/A.class");
        FileInputStream input = new FileInputStream(f);
        byte[] content = new byte[(int) f.length()];
        input.read(content);
        input.close();
        return content;
    }
    public static void main(String[] args) throws Exception {
        //Sample code to creat classes
        byte[] classContents = getClassContent();
        //通过unsafe的defineClass根据字节码内容生成class类类型对象
        Class c = unsafe.defineClass(null, classContents, 0, classContents.length, CreateClass.class.getClassLoader(), null);
        c.getMethod("a").invoke(c.newInstance());   //aaaa
    }
}
class A {
    public void a() {
        System.out.println("aaaa");
    }
}

3.6.Class相关

通过2.5测试了动态生成Class,Unsafe还提供了以下Class操作Api

    //检测给定的类是否需要初始化。通常需要使用在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)。
    //此方法当且仅当ensureClassInitialized方法不生效的时候才返回false。
    public native boolean shouldBeInitialized(Class<?> c);

    //检测给定的类是否已经初始化。通常需要使用在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)。
    public native void ensureClassInitialized(Class<?> c);

    //定义一个类,返回类实例,此方法会跳过JVM的所有安全检查。默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例应该来源于调用者。
    public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);


    ///定义一个匿名类,与Java8的lambda表达式相关,会用到该方法实现相应的函数式接口的匿名类,可以看结尾文章链接。
    public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

3.7.数组元素相关

    //返回数组类型的第一个元素的偏移地址(基础偏移地址)。如果arrayIndexScale方法返回的比例因子不为0,你可以通过结合基础偏移地址和比例因子访问数组的所有元素。
    // Unsafe中已经初始化了很多类似的常量如ARRAY_BOOLEAN_BASE_OFFSET等。
    public native int arrayBaseOffset(Class<?> arrayClass);

    //返回数组单个元素的大小,数组中的元素的地址是连续的。
    // Unsafe中已经初始化了很多类似的常量如ARRAY_BOOLEAN_INDEX_SCALE等。
    public native int arrayIndexScale(Class<?> arrayClass);

四.总结

官网温馨提示:

Although, Unsafe has a bunch of useful applications, never use it.

尽管Unsafe很有用,绝不能使用它

通过对Unsafe提供的API整理发现Unsafe在以下几个方面提供了优异的能力:

内存管理

  • 通过Unsafe实现堆外内存的分配,绕过Jvm垃圾回收的管理,避免了堆内内存扩大造成过多的垃圾回收影响性能,同时提供了堆外内存拷贝和释放等能力。需要注意的是使用Unsafe堆外内存需要手动释放,处理不好会导致堆外内存泄露。
  • Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。

线程调度:

  • 通过Unsafe实现多线程锁、多线程CAS操作、线程挂起和恢复,这些功能在AQS中以及Java多线程并发处理concurrent包下使用最为广泛,工作中直接使用即可没有必要使用Unsafe操作,避免使用不当造成风险。

其他注意事项

  • 其他更多的API操作如内存屏障、Class相关、对象操作日常开发有需要可以直接查阅源码进行调用即可。
  • Unsafe是“不安全”的,这里的不安全指的是不合理使用其API会产生无法预估的后果,并不是Unsafe不安全。使用它有一定的门槛,操作不当会让你的应用直接crash,所以不建议直接使用。
  • 未来Unsafe是否会从JDK中移除没有定论,为了避免未来移除Unsafe给程序带来的不可兼容性,所以谨慎使用。

参考文章

view src/share/classes/sun/misc/Unsafe.java @ 14626:7fcf35286d52

Java Magic. Part 4: sun.misc.Unsafe

JAVA中神奇的双刃剑--Unsafe

JVM源码分析之堆外内存完全解读

Java魔法类:Unsafe应用解析

Java中Unsafe类的原理详解与使用案例

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

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

相关文章

2023年中国金属涂胶板行业供需分析:销量同比增长2.8%[图]

金属涂胶板是一种将金属板材表面进行涂覆处理的产品。它通常由金属基材&#xff08;如钢板、铝板&#xff09;和胶粘剂组成&#xff0c;胶粘剂可以是有机胶粘剂、聚合物胶粘剂或其他特殊胶粘剂。 金属涂胶板行业分类 资料来源&#xff1a;共研产业咨询&#xff08;共研网&…

引领创新浪潮:“Polygon探寻新技术、新治理、新代币的未来之路!“

熊市是用来建设的&#xff0c;Polygon Labs一直在利用这漫长的几个月来做到这一点。 Polygon 是最常用的区块链之一&#xff0c;每周约有 150 万用户&#xff0c;每天超过 230 万笔交易&#xff0c;以及数千个 DApp&#xff0c;Polygon 最近面临着日益激烈的竞争。虽然从交易数…

助力精彩亚运,杭州第19届亚运会重保工作圆满完成

10月8日&#xff0c;随着杭州第19届亚运会闭幕会结束&#xff0c;大道云行为期16天的重保工作圆满结束。杭州亚运会向大家呈现了真正的“科技与狠活”&#xff0c;数字火炬手、开幕式AR、数字烟花&#xff0c;令人震撼的科技让线上线下的观众真切体会到数实融合。首轮直播全媒体…

与客户携手共进|博睿数据客户最佳实践精选

博睿数据将在秋季发布新一代一体化智能可观测平台 Bonree ONE&#xff0c;轻盈、有序、精准&#xff0c;更好地帮助企业应对新技术带来的运维压力&#xff0c;更好地实现数字化转型。 2023年10月20日的Bonree ONE秋季产品发布会上&#xff0c;我们将邀请先进客户分享最佳实践经…

ASL集睿致远 MIPI转LVDS芯片 CS5518规格书 PIN to PIN替代GM8775C 工业级标准

CS5518可以PIN to PIN替代GM8775C,不需要更改线路,直接替换,CS5518成本比GM8775C要低,整体方案性价比高.CS5518主要实现将 MIPI DSI 转单/双通道 LVDS功能,MIPI 支持1/2/3/4 通道可选,支持 4Gbps 速率。LVDS 时钟频率154MHz&#xff0c;支持常见的1920*1080分辨率的屏&#xff…

性能监控-linux操作系统计数器

top&#xff1a;实时显示系统中各个进程的资源占用状况 top [选项] 交互式命令 基本视图内容解释 top命令&#xff1a; 第一行队列任务信息 第二行 进程任务信息可以看到运行、休眠、停止、僵死状态的进程个数 第三行&#xff1a;CPU状态信息 %Cpu(s): 0.5 us, 0.4 sy, 0.0…

MOS管各种概念(三个极、沟道、衬底、电流方向、箭头方向、耗尽型和增强型、寄生二极管、封装引脚)

三个极、沟道、衬底 D(Drain)漏极&#xff1a;载流子&#xff08;NMOS为负电荷&#xff0c;PMOS为正电荷&#xff09;离开端。 S(Source)源极&#xff1a;载流子发射端。 G(Gate)栅极&#xff1a;控制MOS开关的管脚。 沟道&#xff1a;D和S之间形成的导电通道。 衬底&#xff…

如何实现chatGPT批量问答,不用token

3分钟&#xff0c;教你做个GPT批量问答还不用token | 有源码 源码链接 解压压缩包&#xff1b;在Pycharm打开这个文件夹 执行 pip install undetected_chromedriver 和 pip install selenium 执行第1到63行代码&#xff0c;后台会自动打开浏览器&#xff0c;需要手动登录账…

全场景流量验证系统 | 京东物流技术团队

本文介绍了一种基于线上流量实现对重构系统进行功能和性能验证的实践方案。针对线上流量如何拦截、如何录制、如何存储、如何回放以及如何发压均作了详细说明&#xff0c;为具有类似需求的读者提供了一种可供参考的思路。 1 业务背景 随着百川项目的启动&#xff0c;中台需要…

XSS原理

原理&#xff1a; 这是一种将任意 Javascript 代码插入到其他Web用户页面里执行以达到攻击目的的漏洞。攻击者利用浏览器的动态展示数据功能&#xff0c;在HTML页面里嵌入恶意代码。当用户浏览改页时&#xff0c;这些潜入在HTML中的恶意代码会被执行&#xff0c;用户浏览器被攻…

串联起深度学习的整体,以及其他领域

1、从模型拟合&#xff08;收敛&#xff09;数据关系出发&#xff1a; 2、f从简单的一层和两层连接开始&#xff0c;发展&#xff1b;被表示成 3、如何判断收敛&#xff1a;,即目标函数 4、如何界定任务&#xff1a;&#xff0c;表示什么&#xff1f;表示什么&#xff1f;&a…

解决ubuntu中没有网络连接的图标

现象&#xff1a;Ubuntu连接网络 在设置中没有显示网络图标 解决方案&#xff1a; 命令为 sudo nmcli networking off sudo nmcli networking on sudo service network-manager restart 重启ubuntu&#xff0c;网络连接完成

[MongoDB]-权限验证管理

[MongoDB]-权限验证管理 senge | 2023年9月 背景说明&#xff1a;现有两套MongoDB副本集群给开发人员使用时未开启认证。 产生影响&#xff1a;用户若输入账号以及密码则会进行校验&#xff0c;但用户可以在不输入用户名和密码的情况下也可直接登录。 倘若黑客借此进行攻击勒索…

nSoftware IPWorks IoT 2022 Java 22.0.8 Crack

物联网库&#xff0c;使用这个轻量级组件库&#xff0c;可以在任何平台上的应用程序中轻松实现物联网 (IoT) 通信协议。 nSoftware IPWorks IoT 最新的 IPWorks IoT 现已推出&#xff01;最新版本的 IPWorks IoT 具有现代化和简化的体验&#xff0c;包括 .NET 中的异步和跨平台…

LeetCode竞赛---第 366 场周赛

Problem: 100103. 分类求和并作差 &#x1f4da; 题目&#xff1a;给你两个正整数 n 和 m 。 现定义两个整数 num1 和 num2 &#xff0c;如下所示&#xff1a; num1&#xff1a;范围 [1, n] 内所有 无法被 m 整除 的整数之和。num1&#xff1a;范围 [1, n] 内所有 无法被 m 整…

OPPO realme 真我 一加 刷机工具下载 ColorOS Upgrade Tool

Download Realme Upgrade Tool for Windows Download ColorOS Upgrade Tool for Windows Realme升级工具是由Realme开发的Windows应用程序&#xff0c;可帮助用户升级其Realme设备上的固件。此工具支持在Realme Ul 3.0或更高版本上运行的Realme 设备。OPPO realme 真我 一加 …

下载安装JRebel插件

settings -> plugis -> JRebel 下载 然后重启idea 激活 现在呢&#xff0c;2023年啦~所以网址得是最先哒~ http://127.0.0.1:8888/{GUID} GUID从下面的网址生成&#xff0c;邮箱随意~ Create GUID online (guidgen.com) LS client not configured. 删除用户文件夹…

C++ | 仿函数

仿函数的用法 在C语言时期&#xff0c;如果想要实现回调函数或者是函数参数需要传入函数&#xff0c;通常是用的函数指针。而在C中&#xff0c;我们一般用仿函数来平替。 仿函数&#xff0c;又叫函数对象。虽然名字叫仿函数&#xff0c;但本质上就是一个重载了 operator() 的类…

金融信创黄金三年:小程序生态+跨端技术框架构建

小程序应用场景生态的发展&#xff0c;受益于开源技术的发展&#xff0c;以及响应快速开发的实际业务需求&#xff0c;一些跨端框架如&#xff1a;Electron、wxPython、FinClip、Tauri、Flutter等发展也非常迅速&#xff0c;小程序生态跨端技术框架&#xff0c;不仅能满足自有超…

Python3操作文件系列(三):OpenPyXl模块三大对象操作Excel文件

Python3操作文件系列(一):判断文件|目录是否存在三种方式 Python3操作文件系列(二):文件数据读写|二进制数据读写 Python3数据文件读取与写入 Python3操作文件系列(三):excel文件读写数据 Python操作Excel的三大对象认知升维&#xff1a; Excel对象模型:excel脚本编程的主要…