JVM详解--内存结构

news2024/11/15 18:50:17

文章目录

  • 什么是JVM
  • 内存结构
    • 程序计数器(Program Counter Register)
    • 虚拟机栈(Java Virtual Machine Stacks)
      • 概述
      • 栈内存溢出
    • 本地方法栈
    • 堆(Heap)
      • 堆内存溢出
      • 堆内存诊断
    • 方法区
      • 方法区内存溢出
    • 常量池
      • 运行时常量池
      • StringTable_常量池与串池的关系
      • StringTable_字符串变量拼接
      • StringTable_编译器优化
      • StringTable_字符串延迟加载
      • StringTable_intern
      • StringTable特性
      • StringTable位置
      • StringTable_垃圾回收
      • StringTable性能调优
    • 直接内存(Direct Memory)
      • 直接内存分配和释放原理
      • 禁止显示回收对直接内存的影响

什么是JVM


Java 虚拟机(JVM)是运行 Java 二进制字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。

JVM 并不是只有一种!只要满足 JVM 规范,每个公司、组织或者个人都可以开发自己的专属 JVM。 也就是说我们平时接触到的 HotSpot VM 仅仅是是 JVM 规范的一种实现而已。

好处:

  • 一次编写,到处运行
  • 自动内存管理,垃圾回收功能
  • 数组下标越界检查
  • 多态

jvm、jre、jdk比较
我们先看这张图:

在这里插入图片描述

JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。

JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。

这里大概介绍了三者的区别以及关系,下面具体来看看jvm的原理

内存结构


程序计数器(Program Counter Register)

定义
Program Counter Register 程序计数器(寄存器)

  • 作用,是记住下一条jvm指令的执行地址
  • 特点
    • 是线程私有的
      每个线程都有自己的程序计数器,随着线程创建而创建,随着线程销毁而销毁
    • 不会存在内存溢出

作用
在这里插入图片描述

java虚拟机跨平台的基础就是这套jvm指令,对所有平台都是一致的,这套指令不能直接交给CPU来执行,需要交给解释器,经过解释器解释称为机器码,然后才能把机器码交给CPU来执行

拿到第一条0地址指令时交给解释器,解释器把它变成机器码,然后交给CPU执行,与此同时,会把下一条指令的地址3放入程序计数器,第一天指令执行完以后,解释器到程序计数器中找到下一条指令的地址

虚拟机栈(Java Virtual Machine Stacks)


概述

Java Virtual Machine Stacks (Java 虚拟机栈)

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

在这里插入图片描述

垃圾回收是否涉及栈内存

答案是否,因为栈内存就是一次次的方法调用产生的栈帧内存,栈帧内存在每次方法结束后都会弹出栈,会被自动回收掉

栈内存分配越大越好吗

栈内存变大会让线程数减少,因为物理内存大小一定

方法内的局部变量是否线程安全

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

怎么理解呢,接下来看两个例子:

static void m1(){
int x = 0;
    for (int i = 0; i < 5000; i++) {
        x++;
    }
    System.out.println(x);
}

以这段代码为例,一个线程对应一个栈帧,对应一个栈,线程内每一次方法调用都会产生新的栈帧,每个线程都有自己私有的局部变量x,互不干扰,因此不会产生线程安全问题

但是如果该变量是static时,就会产生线程安全问题


//是线程安全,和上面那个例子相似
public static void m1(){
    StringBuilder sb = new StringBuilder();
    sb.append(1);
    sb.append(2);
    sb.append(3);
    System.out.println(sb.toString());
}

//不安全,sb对象是作为方法的参数传进来,有可能有其他的线程能访问到它,对多个线程是共享的
public static void m2(StringBuilder sb){
    sb.append(1);
    sb.append(2);
    sb.append(3);
    System.out.println(sb.toString());
}

//不安全,把对象返回了,意味着其他线程可以拿到这个对象操作
public static StringBuilder m3(){
    StringBuilder sb = new StringBuilder();
    sb.append(1);
    sb.append(2);
    sb.append(3);
    return sb;
}

栈内存溢出

  • 栈帧过多导致栈内存溢出
  • 栈帧过大导致栈内存溢出
/**
 * 演示栈内存溢出  java.lang.StackOverflowError
 */
public class Demo4 {
    private static int count;

    public static void main(String[] args){
        try {
            method1();
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(count);
        }
    }

    private static void method1(){
        count++;
        method1();
    }
}

在这里插入图片描述

/**
 * json 数据转换
 */
public class Demo5 {

    public static void main(String[] args) throws JsonProcessingException {
        Dept d = new Dept();
        d.setName("Market");

        Emp e1 = new Emp();
        e1.setName("zhang");
        e1.setDept(d);

        Emp e2 = new Emp();
        e2.setName("li");
        e2.setDept(d);

        d.setEmps(Arrays.asList(e1, e2));

     	/**
          * { 
          * 	name: 'Market', 
          *	 	emps: [
          *				{ name:'zhang', 
          *				  dept:{ name:'', 
          *						 emps: [ {}]
          *					   } 
          *				},
          *			  ] 
          *	}
          * 会无限循环
          */
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(d));
    }
}

class Emp {
    private String name;
    //@JsonIgnore
    private Dept dept;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }
}
class Dept {
    private String name;
    private List<Emp> emps;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Emp> getEmps() {
        return emps;
    }

    public void setEmps(List<Emp> emps) {
        this.emps = emps;
    }
}

此时运行会出现错误
在这里插入图片描述
这是由于两个类之间的循环引用,导致json解析是一直循环,加上@JsonIgnore注解后进行中断

线程运行诊断案例

案例一:cpu占用过多

定位

  • 用top定位哪个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack 进程id
    • 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

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

还是用jstack命令

本地方法栈


在这里插入图片描述
本地方法:指不是由java代码编写的方法,java代码有时候不能直接和操作系统底层打交道,需要一些用c/c++编写的本地方法,java代码可以间接通过本地方法来调用底层功能,这些本地方法运行的时候使用的就是本地方法栈

在这里插入图片描述

堆(Heap)


Heap 堆

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

特点

  • 它是线程共享的,堆中对象都需要考虑线程安全的问题
  • 有垃圾回收机制

堆内存溢出

/**
 * 演示堆内存溢出  java.lang.OutOfMemoryError: Java heap space
 */
public class Demo6 {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "hello";
            while (true){
                list.add(a);
                a = a + a;
                i++;
            }
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

在这里插入图片描述

堆内存诊断

  1. jps 工具
    查看当前系统中有哪些 java 进程
  2. jmap 工具
    查看堆内存占用情况 jmap - heap 进程id
  3. jconsole 工具
    图形界面的,多功能的监测工具,可以连续监测

测试代码

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1..."); //创建对象之前
        Thread.sleep(30000); 
        byte[] array = new byte[1024 * 1024 * 10]; //创建对象  10MB
        System.out.println("2..."); //创建对象后
        Thread.sleep(30000); 
        array = null;
        System.gc();
        System.out.println("3..."); //垃圾回收后
        Thread.sleep(30000);
    }
}

查看进程id

在这里插入图片描述

程序打印出1… 时执行,查看创建对象之前使用的堆内存

在这里插入图片描述

程序打印出2… 时执行,查看创建对象之后使用的堆内存
在这里插入图片描述
可见多了10MB左右

程序打印出3… 时执行,查看垃圾回收之后使用的堆内存

在这里插入图片描述

jconsole 工具

运行程序后,在控制台输入jconsole会弹出一个可视化窗口,里面可检测到变化

jvisualvm 工具

运行程序后,在控制台输入jconsole会弹出一个可视化窗口,里面可检测到变化

案例:垃圾回收后,内存占用仍然很高
在这里插入图片描述
在这里插入图片描述
这样就可以查看占用内存的原因

方法区


方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。

《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,方法区到底要如何实现那就是虚拟机自己要考虑的事情了。也就是说,在不同的虚拟机实现上,方法区的实现是不同的。

当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

方法区和永久代以及元空间是什么关系呢? 方法区和永久代以及元空间的关系很像 Java 中接口和类的关系,类实现了接口,这里的类就可以看作是永久代和元空间,接口可以看作是方法区,也就是说永久代以及元空间是 HotSpot 虚拟机对虚拟机规范中方法区的两种实现方式。并且,永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。

JDK1.8以前组成

在这里插入图片描述
JDK1.8组成

在这里插入图片描述

方法区内存溢出

测试代码

/**
 * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
 * 设置虚拟机参数:VM -XX:MaxMetaspaceSize=8m
 */
public class Demo9 extends ClassLoader { // 可以用来加载类的二进制字节码
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo9 test = new Demo9();
            for (int i = 0; i < 10000; i++, j++) {
                // ClassWriter 作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                // 版本号, public, 类名, 包名, 父类, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 返回 byte[]
                byte[] code = cw.toByteArray();
                // 执行了类的加载
                test.defineClass("Class" + i, code, 0, code.length); // Class 对象
            }
        } finally {
            System.out.println(j);
        }
    }
}

在这里插入图片描述

场景:

在Spring和MyBatis等等的一些框架中都用到了字节码技术,用到cglib技术,Spring用它生成代理类,MyBatis用它产生Mapper接口的是实现类

所以实际情况中,我们在用到这些框架时,在运行期间生成的类还是很容易导致永久代的内存溢出的,在1.8以后,元空间使用的是系统内存,相对的充裕了很多

常量池


我们先来通过一个测试大概了解常量池的作用

//二进制字节码(类基本信息、常量池、类方法定义(包含了虚拟机指令))
public class Demo10 {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

运行代码会生成一个字节码文件,我们通过反编译这个字节码文件来看看里面的内容

进入字节码文件的目录中运行此命令
在这里插入图片描述
第一部分就是类的基本信息
在这里插入图片描述

然后基本信息下面是类的常量池
目前可能看不明白,下面介绍完组成后会具体介绍执行流程中常量池发挥的作用
在这里插入图片描述
然后常量池下面就是类方法定义
在这里插入图片描述

接下来我们来看具体的执行流程
在这里插入图片描述

至此,我们就已经大概了解到了常量池的作用了

运行时常量池

  • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量
    等信息
  • 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量
    池,并把里面的符号地址变为真实地址

当运行时就要放在内存,放在内存中的位置就是运行时常量池

StringTable_常量池与串池的关系

//串池StringTable[  ] (哈希表,长度固定并不能扩容)
public class Demo11 {
    //常量池中的信息,都会被加载到运行时常量池中,这时,a b ab 都是常量池中的符号
    //还没有变为 java 中的字符串对象

    //ldc #2 会把 a 符号变为 "a" 字符串对象,会把它作为 key 到 StringTable中找看有没有取值相同的 key
    //没有,会把该对象放入串池
    //StringTable[ "a" ]
    //ldc #3 会把 b 符号变为 "b" 字符串对象,会把它作为 key 到 StringTable中找看有没有取值相同的 key
    //没有,会把该对象放入串池
    //StringTable[ "a","b" ]
    //ldc #4 会把 ab 符号变为 "ab" 字符串对象,会把它作为 key 到 StringTable中找看有没有取值相同的 key
    //没有,会把该对象放入串池
    //StringTable[ "a","b","ab" ]
    public static void main(String[] args) {
        String s1 = "a"; //用到的时候才会开始创建,懒惰的
        String s2 = "b";
        String s3 = "ab";
    }
}

在这里插入图片描述

StringTable_字符串变量拼接

 String s1 = "a"; 
 String s2 = "b";
 String s3 = "ab";
 String s4 = s1 + s2; //new StringBuilder()  
 System.out.println(s3 == s4); //false  s3在串池中,s4在堆中

在这里插入图片描述

StringTable_编译器优化

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; //new StringBuilder()
//System.out.println(s3 == s4);

String s5 = "a" + "b";
System.out.println(s3 == s5);  //true

在这里插入图片描述
这实际上是javac 在编译时的优化,“a” 和 “b” 都是常量,内容不会变了,所以拼接的结果是确定的,既然是确定的,在编译期间就能知道结果肯定就是 “ab” 了,不可能是别的值,与上不同的是,s1 和s2是变量

StringTable_字符串延迟加载

/**
 * 演示字符串字面量也是【延迟】成为对象的
 */
public class Demo12 {
    public static void main(String[] args) {
        int x = args.length;
        System.out.println(); // 字符串个数 2146

        System.out.print("1");
        System.out.print("2");
        System.out.print("3");
        System.out.print("4");
        System.out.print("5");
        System.out.print("6");
        System.out.print("7");
        System.out.print("8");
        System.out.print("9");
        System.out.print("0");
        System.out.print("1"); // 字符串个数 2146
        System.out.print("2");
        System.out.print("3");
        System.out.print("4");
        System.out.print("5");
        System.out.print("6");
        System.out.print("7");
        System.out.print("8");
        System.out.print("9");
        System.out.print("0");
        System.out.print(x); // 字符串个数 2146
    }
}

此时的字符串数量为2146

请添加图片描述

接下来每走一步,该数值就会加一

此时的字符串数量为2156
请添加图片描述

再往后走则不会有变化
请添加图片描述
因为这几个字符串对象在串池中就已经存在了

StringTable_intern

public static void main(String[] args) {
	 String s = new String("a") + new String("b"); //new String("ab")
    //堆 new String("a")  new String("b")  new String("ab")
    String s2 = s.intern(); //将这个字符串对象尝试放入串池,
    //如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回

    System.out.println(s2 == "ab"); //true
    System.out.println(s == "ab");  //true
}
public static void main(String[] args) {
	String x = "ab";
	String s = new String("a") + new String("b"); //new String("ab")
    //堆 new String("a")  new String("b")  new String("ab")
    String s2 = s.intern(); //将这个字符串对象尝试放入串池,
    //如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回

    System.out.println(s2 == x); //true
    System.out.println(s == x);  //false
    //此时s并没有放进去,因为串池中的已经存在 "ab",因此x 和 s并不是一个对象
}

StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串
      池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,
      放入串池, 会把串池中的对象返回

StringTable位置

在JVM1.6的时候StringTable是在常量池,随着常量池存储在永久代中,从1.7开始,把StringTable 转移到了堆中,因为永久代内存的回收效率很低,永久代需要Full GC才会触发垃圾回收,StringTable存着字符串常量,使用很频繁,会占用大量内存,需要回收效率高,因此转移到了堆

StringTable_垃圾回收

演示:

/**
 * 演示StringTable垃圾回收
 * 设置虚拟机堆内存最大值:-Xmx10m
 * 打印字符串表的统计信息:-XX:+PrintStringTableStatistics
 * 打印垃圾回收详细信息:-XX:+PrintGCDetails -verbose:gc
 */
public class Demo1 {
    public static void main(String[] args) {
        int i = 0;
        try {

        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }
    }
}

在这里插入图片描述
运行
在这里插入图片描述
此时什么都没做,里面已经有一千多个字符串对象,因为java程序运行时类名、方法名这些数据也是以字符串常量的形式表示的,存在串池当中

public static void main(String[] args) {
        int i = 0;
        try {
            for (int j = 0; j < 10000; j++) {
                String.valueOf(j).intern();
                i++;
            }
        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }
    }

执行这段代码时,可以看到下面的结果:

在这里插入图片描述
发生了垃圾回收,把一些无用的对象清除掉,StringTable中只是创建了新的字符串,但是没有人用,没有什么list集合去引用,所以无用的就被回收掉了

StringTable性能调优

  • 调整虚拟机参数,-XX:StringTableSize=桶个数
  • 考虑将字符串对象是否入池

首先演示第一个

在这里演示一下读取linux.words,是一个单词表,里面大约存储了48万个单词,通过改变串池大小查看读取时间变化

/**
 * 演示串池大小对性能的影响
 * -Xms500m -Xmx500m -XX:+PrintStringTableStatistics
 * 串表其实是哈希表,设置桶的个数:-XX:StringTableSize=20000
 */
public class Demo2 {

    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);
        }


    }
}

首先配置虚拟机参数

在这里插入图片描述

从这里也能看出来
在这里插入图片描述

在这里插入图片描述

当我们比改变桶大小,使用系统默认的时:

在这里插入图片描述

把大小改为2000时:

在这里插入图片描述

因此,如果系统中字符串常量个数非常多,可以适当把StringTable中桶的个数调的大一些,减少哈希冲突

接下来来演示第二个

public class Demo2 {

    public static void main(String[] args) throws IOException {
        List<String> list = new ArrayList<>();
        System.in.read();       //回车程序可继续运行
        for (int i = 0; i < 10; i++) {
            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;
                    }
                    list.add(line); //加入list,防止被回收
                }
                System.out.println("cost:" + (System.nanoTime() - start) / 1000000);
            }
        }
        System.in.read();

    }
}

在这里插入图片描述

在这里插入图片描述

然后让字符串入池再观察

 list.add(line.intern()); //加入list,防止被回收

在这里插入图片描述

在这里插入图片描述

进行了入池前后内存占用的比较可以,看出来入池后会减少内存占用,所以应用中有大量字符串而且字符串会存在重复的问题,我们可以让字符串入池来减少字符串对象个数,减少内存使用

直接内存(Direct Memory)


属于操作系统内存,不属于java虚拟机管理

  • 常见于 NIO 操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受 JVM 内存回收管理

没有使用直接内存的情况
在这里插入图片描述

使用了直接内存的情况

在这里插入图片描述

少了一次数据拷贝,读取速度得到提升

直接内存分配和释放原理

  • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
  • ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦
    ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调
    用 freeMemory

禁止显示回收对直接内存的影响

/**
 * 禁用显式回收对直接内存的影响
 * -XX:+DisableExplicitGC  加了这个参数后 System.gc()就是无用的
 */
public class Demo1_26 {
    static int _1Gb = 1024 * 1024 * 1024;

    /*
     * -XX:+DisableExplicitGC 显式的
     */
    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
        System.out.println("分配完毕...");
        System.in.read();
        System.out.println("开始释放...");
        byteBuffer = null;
        System.gc(); // 显式的垃圾回收,Full GC
        System.in.read();
    }
}

System.gc(); 触发的是一次Full GC,是一种比较影响性能的垃圾回收,不仅要回收新生代,还要回收老年代

这样ByteBuffer不被回收,会导致直接内存占用内存过大长时间得不到释放,也会影响性能

可以直接用unsafe对象直接调用freeMemory()

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

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

相关文章

PHP手册

NULL 未定义和unset()的变量都将解析为值null unset() unset( $var, ...$vars) 如果在函数中 unset() 一个全局变量&#xff0c;则只是局部变量被销毁&#xff0c;而在调用环境中的变量将保持调用 unset() 之前一样的值。 <?php function destroy_foo() {global $foo;un…

双系统下linux分区被误删的解决办法

前言在windows系统的磁盘管理中误删了ubuntu的磁盘分区&#xff0c;开机后一直卡在grub界面。Windows/Linux双启动的机器一般都使用grub作为引导程序。如果不小心在Windows中删除了linux分区&#xff0c;grub就会因为找不到配置文件而造成无法启动。 系统配置 系统类型&#x…

Qt新手入门指南 - 如何创建模型/视图(一)

每个UI开发人员都应该了解ModelView编程&#xff0c;本教程的目标是为大家提供一个简单易懂的介绍。Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff…

java排错定位

1、检查有没有报错信息 日志文件中登记的错误&#xff0c;这个算是最简单的&#xff0c;在定位错误时&#xff0c;也最希望问题在这一步得到确认。在打印异常时&#xff0c;通常会打印异常的调用栈信息&#xff0c;通过调用栈信息就可以很便捷的定位问题了。 例如&#xff1a; …

【JavaScript】原型与原型链以及判断数据类型方式

&#x1f4bb; 【JavaScript】原型与原型链以及判断数据类型方式 &#x1f3e0;专栏&#xff1a;JavaScript &#x1f440;个人主页&#xff1a;繁星学编程&#x1f341; &#x1f9d1;个人简介&#xff1a;一个不断提高自我的平凡人&#x1f680; &#x1f50a;分享方向&#…

Redis对不起是我肤浅了(基础和应用篇):位图(Bitmaps)的妙用和深入分析每个命令的用法

一、前言 在Redis 4.0 版本之前&#xff0c;Redis是单线程程序&#xff0c;主要是指Redis的网络I/O线程。Redis的持久化、集群同步等操作&#xff0c;则是由另外的线程来执行的。但在Redis 4.0 版本之后&#xff0c;Redis添加了多线程的支持&#xff0c;这时的多线程主要体现在…

【IT互联网行业内,什么岗位工作更有前景?】

前言互联网及IT行业作为集技术与高薪于一身的新技术行业&#xff0c;不仅成为时下众多年轻人的首选行业&#xff0c;其本身也承载了社会、企业数字化发展转型的重担&#xff0c;从国家到社会、市场都非常重视行业技术的发展和渗透&#xff0c;其重要性不言而喻。作为普通人的小…

AcWing 1073. 树的中心(详解树形DP和换根DP)

AcWing 1073. 树的中心&#xff08;树形DP 换根DP&#xff09;一、问题二、思路1、暴力做法2、树形DP换根DP&#xff08;1&#xff09;思路分析&#xff08;2&#xff09;普通树形DP与换根DP的区别三、代码一、问题 二、思路 1、暴力做法 这道题其实暴力的做法很简单&#x…

【金融学】Economics of Money and Banking {暂时搁置,中级宏观和微观经济学未学}

Economics of Money and BankingClass1 The Big PicturePrerequisitesSome MaterialsCourse Material: https://www.coursera.org/learn/money-banking/lecture/8WXSW/the-big-picture Class1 The Big Picture Prerequisites intermediate macroeconomics 中级宏观经济学 int…

Java on VS Code 2023年1月更新|Spring 插件包、代码补全更新以及性能改进

作者&#xff1a;Nick Zhu - Senior Program Manager, Developer Division at Microsoft 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎来到我们 2023 年的第一篇博客&#xff01;我们想与您分享几个与 Spring 插件、代码编辑和性能相关的激动人心的更新&#xff0c;让我们…

XML方式—解决mybatis实体类属性名和数据库字段名不一致问题

数据库字段与类属性名称不一致&#xff0c;导致查询数据时数据没有封装上。 [Brand{id1, brandNamenull, companyNamenull}, Brand{id2, brandNamenull, companyNamenull}]解决方式一&#xff08;为表字段取别名&#xff09; <select id"selectAll" resultType&…

【大唐杯备考】——5G网络组网部署(学习笔记)

&#x1f4d6; 前言&#xff1a;本期介绍5G网络组网部署。 目录&#x1f552; 1. SA组网和NSA组网&#x1f558; 1.1 SA组网&#x1f558; 1.2 NSA组网&#x1f564; 1.2.1 Option 3系列&#x1f564; 1.2.2 Option 7系列&#x1f564; 1.2.3 Option 4系列&#x1f558; 1.3 组…

【Linux】gcc编译器

【Linux】gcc编译器 文章目录【Linux】gcc编译器1、背景2、翻译过程2.1 预处理2.2 编译2.3 汇编2.4 链接2.5 总过程3、动、静态链接库3.1 查看库3.2 库分类3.3 链接使用4、gcc参数总结1、背景 在Linux中&#xff0c;C语言的编译肯定需要编译器来进行&#xff0c;这个编译器就是…

微课录制软件哪个好用?微课制作方法分享

很多小伙伴都不知道什么是微课&#xff1f;微课视频时长比较短&#xff0c;一般在5-10分钟。微课的教学内容也比较少&#xff0c;一个微课视频一般只对一个知识点进行针对性讲解。微课的这些特性可以让我们利用碎片时间进行学习。制作微课需要录制电脑屏幕&#xff0c;那微课录…

Python 部分位运算(包含负数反码手算求法)

首先要回忆负数的补码的求法&#xff1a;写出其相反数的原码&#xff0c;而后从右往左&#xff0c;碰到第一个1后上面的数全取反。 下面以4位二进制的-5举例&#xff08;首位为符号位&#xff09;&#xff1a; 5的原码 0101末位遇1后取反 1011即-5的补码为 1011下面是部分pytho…

C/C++ 高精度(加减乘除)算法压位优化

文章目录前言一、基本原理1、存储方式2、计算方式二、完整代码三、性能对比总结附录1、性能测试代码前言 由于上一章《C/C 高精度&#xff08;加减乘除&#xff09;算法简单实现》实现了基本的高精度计算&#xff0c;数组的每个元素存储一位10进制的数字。这样的存储方式并不是…

数学建模学习笔记(15)时间序列分析

时间序列分析时间序列分析概述和数据预处理时间序列分解模型指数平滑模型ARIMA模型时间序列分析概述和数据预处理 时间序列的概念&#xff1a;也称为动态序列&#xff0c;是指将某种现象的指标值按照时间顺序排列而成的数值序列。 时间序列的组成要素&#xff1a;时间要素、数…

表格控件Aspose.Cells for Java 授权须知

Aspose API支持流行文件格式处理&#xff0c;并允许将各类文档导出或转换为固定布局文件格式和最常用的图像/多媒体格式。 Aspose.Cells for Java 是一个屡获殊荣的Excel电子表格处理API&#xff0c;它允许Java开发人员在自己的Java应用程序中嵌入可读取、写入和操作Excel电子…

天云数据Hubble数据库被评为“IDC创新者:中国分布式数据库,2022”

近日&#xff0c;国际知名研究机构 IDC 发布《IDC Innovator&#xff1a;中国分布式关系型数据库》报告正式发布&#xff0c;天云融创数据科技&#xff08;北京&#xff09;有限公司&#xff08;以下简称“天云数据”&#xff09;被评为“IDC创新者&#xff1a;中国分布式数据库…

经过ASEMI整流桥MB10F后输出电压是多少

编辑-Z 型号&#xff1a;MB10F 封装&#xff1a;MBF-4 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;1000V 最大平均正向整流输出电流&#xff08;IF&#xff09;&#xff1a;1.0A 峰值正向浪涌电流&#xff08;IFSM&#xff09;&#xff1a;35A 每个元…