专业技能(挖坑填坑)——Java核心基础知识,对集合、线程

news2024/12/28 18:31:08

熟悉Java核心基础知识,对集合、线程等都有了解,能运用模块化、面向对象的方式编程。

1.Java八种基本数据类型

在这里插入图片描述
Java的数据类型分为两大类:①基本数据类型 ②引用数据类型
在这里插入图片描述

2.面向对象三大特性

封装、继承、多态。

简要介绍一下/谈一下你的理解:

封装:把对象的状态信息(属性)隐藏在内部,不允许外部对象直接访问对象的内部信息

封装的主要目的是隔离复杂度,保护对象的内部状态不被外部随意修改,只通过对象提供的接口进行访问。
在Java中,我们通常通过以下步骤实现封装:

  • 使用访问修饰符:使用访问修饰符来定义类的属性和方法。通常,private以防止外部直接访问。通过public来提供对属性的访问和修改。
  • 使用类:我们将相关的属性和方法定义在一个类中,形成一个独立的对象。这个对象可以包含数据和对数据的操作。
  • 提供接口:我们提供公共的接口(方法)供外部访问和修改对象的内部状态。这些接口通常被设计为符合对象的行为和职责。

继承

继承是一种重要的面向对象编程特性,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,从而提高了代码的重用性和可维护性。
继承是通过关键字extends来实现的。子类可以继承父类的非私有属性和方法,并可以添加新的属性和方法或覆盖父类中的方法
继承的作用:

  1. 代码重用:继承允许子类重用父类的代码,从而避免了大量重复代码的编写。通过继承,子类可以自动获得父类中的属性和方法,无需重新编写。这大大减少了代码的冗余,提高了代码的可维护性。
  2. 扩展性:继承允许子类在继承父类功能的基础上添加新的功能。子类可以通过添加新的属性和方法或覆盖父类中的方法来实现自己的功能扩展。这使得子类能够更加灵活地满足特定的需求。
  3. 层次结构:继承可以形成类的层次结构,使得类之间的关系更加清晰。通过继承,我们可以将具有相似属性和方法的类组织在一起,形成一个类族。这有助于我们更好地理解和使用这些类。

多态:多种形态,就是去完成某个行为,当不同的对象去完成时会产生不同的状态。

多态主要体现在方法重载和方法重写两个方面。

  • 方法重载是 通过在同一个类中定义多个同名但参数列表不同的方法来实现多态。
  • 方法重写则是子类可以定义一个与父类同名同参数的方法来实现多态。
  • 接口实现: 通过实现接口并在实现类中定义相应的方法来体现多态。接口是一种完全抽象的类,而实现类则需要提供具体的方法实现。

1.重写和重载区别

重写:是子类对父类方法重写,要求方法名、参数列表、返回值类型与父类相同,修饰符访问权限要大于等于父类,抛出的异常小于等于父类。如果父类方法修饰符为private,那么子类就不能重写该方法。
重载:是同一类中,不同的函数使用相同 的函数名,要求方法名相同,参数类型个数顺序、返回值类型有不同。

2.==和 equals 的区别

==:对基本数据类型比较值是否相等;对引用数据类型比较地址是否相同。
equals:默认比较地址是否相同,对String、Integer、Date等对equals进行重写,可以比较内容是否相同。

3.接口和抽象类的区别

实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
类可以实现很多个接口;但是只能继承一个抽象类。
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写),
接口中不能包含普通方法, 子类必须重写所有的抽象方法.

构造函数:抽象类可以有构造函数;接口不能有。

main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方 法。

访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符

4.String、StringBuffer、StringBuilder区别

可变性:

String类中他使用的是字符数组保存字符串,所以String对象他是不可变的
StringBuffer、StringBuilder这两种是可变的

线程安全性:
String的对象他是不可变的,可就可以理解为是常量,线程是安全的。
StringBuffer对方法中加了同步锁或者对调用方法加了同步锁,所以线程是安全的。
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的

性能:
每次对String类型进行改变的时候,都会生成一个String对象,这时候空指针会指向新的String对象。
操作少量的字符串 用String; 单线程下需要操作大量数据 用StringBuilder;多线程下需要操作大量数据 用StringBuffer

5.java几种修饰符的区别

访问修饰符:public、protected、private、default

1.public:公共;使用对象:类、接口、变量、方法。使用场景:当你希望某个类的功能被广泛使用时,应将其设置为 public。

2.default:默认;使用对象:类、接口、变量、方法。使用场景:该类的实现细节仅在包内部可见时

3.private:私有;使用对象:成员变量,成员函数。使用场景:方法、变量或构造函数仅作为类的内部实现的一部分

注释:只在本类中有效;不能修饰类;

4.protected:保护;使用对象:变量、方法。使用场景:某个类的成员可以被子类继承,但又不希望被其他不相关的类访问时。

注意:不能修饰类(外部类)。

注释:保护权限,不同包之间只有继承了的子类才能访问;
    在这里插入图片描述
总结:public 提供了最大的灵活性,protected 允许一定程度的继承访问,默认访问级别限制了包内访问,而 private 则提供了最强的封装。

非访问修饰符:final、static、abstract、synchronized、volatile

abstract:表示该类或方法是抽象的,不能被实例化或调用,只能被继承或实现。

final:表示该类、方法或变量不能被继承、重写或重新赋值。

static:表示该方法或变量是类级别的,可以通过类名直接访问,不需要创建实例。

synchronized:表示该方法是同步的,多个线程不能同时访问该方法

volatile:表示该变量是易变的,每次读取变量时都会从主存中获取最新值,每次修改变量时都会立即写入主存。

6.final、static修饰符的区别

final和static的区别:
很多时候会容易把static和final关键字混淆,static作用于成员变量用来表示只保存一份副本,也就是在内存中只有一个,静态变量被所有的对象所共享,在内存中只有一个副本,而final的作用是用来保证变量不可变。

final

(1)final修饰的成员变量,一旦有了初始值就不能被重新赋值。由于成员变量具有默认值,用final关键字修饰后 不会再给默认值,必须手动赋值,否则会报错。
(2)修饰类和方法,不能被继承和重写。

static

(1)static是静态的意思,可用来修饰 成员方法、成员变量。static修饰后的变量、方法,可以被类的所有对象共享
(2)static不能修饰类,被static修饰的方法可以直接使用类名.方法名

(3)静态方法只能访问静态成员。不能直接访问实例成员
(4)静态方法中是不可以出现this关键字的。this指当前对象,静态方法中不用声明实例对象

静态方法和实例方法有什么不同

1.调用方式:
外部调用示例方法,使用对象.方法名;外部调用静态方法既可以对象.方法名也可以类名.方法名。也就是说,调用静态方法无需创建对象 。
2.访问类成员是否存在限制:
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员。
因为:静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象只有在对象实例化之后才存在,需要通过类的实例对象去访问。在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。

5.String 对象的创建

JDK7之后:
在这里插入图片描述

(1)有两种方式创建String对象:字面量赋值、new关键字

  • 字面值创建:JVM首先检查字符串常量池中是否已经存在该字符串,如果存在 则直接返回字符串对象的引用,否则就创建一个新的字符串对象并放入字符串常量池中,最终将该对象的引用赋值给变量str。【字符串常量池中不会存储相同内容的字符串】
  • 通过new关键字创建字符串对象:会先检查字符串常量池中是否有相同的字符串,如果有则拷贝一份放到堆中,然后返回堆中地址;如果没有 就先在字符串常量池中创建"abc"这个字符串,而后再复制一份放到堆中 并把堆地址返回给str。【字符串常量池和堆内存都会有这个对象】
  • 方式一效率比方式二高。

(2) String str1="abc"和String str2=new String(“abc”)区别

  • String str=“abc"创建了几个对象? 0个 或 1个。如果字符串常量池中没有"abc”,则在常量池中创建"abc" 并让str引用指向该对象(1个);如果字符串常量池中有"abc",则一个都不创建 直接返回地址值给str(0个)
  • String str=new String(“abc”)创建了几个对象? 1个 或 2个。如果字符串常量池中没有"abc",则在字符串常量池堆内存中各创建一个对象,返回堆地址(2个);如果常量池中有"abc",则只在堆中创建对象并返回地址值给str(1个)。【new相当于在堆中新建了value值,每new一个对象就会在堆中新建,地址值也因此不同,堆中的value存储着指向常量池的引用地址】

(3)字符串拼接操作

  • 常量 与 常量 的拼接结果在 常量池,原理是 编译期 优化;常量池 中不会存在相同内容的常量
  • 只要其中一个是变量,结果就在堆中
  • 变量拼接的原理 是StringBuilder
  • 如果拼接的结果调用 intern() 方法,则主动将常量池中 还没有的字符串对象放入池中,并返回地址

(4)String类型变量+常量

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true——常量折叠,字符串常量池中同一个
System.out.println(str4 == str5);//false——两个引用在程序编译期是无法确定的所以无法常量折叠

(1)对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池
对于 String str3 = “str” + “ing”; 编译器会给你优化成 String str3 = “string”; —— 这就是常量折叠。

只有编译器在程序编译期就可以确定值的常量才可以:

  • 基本数据类型( byte、boolean、short、char、int、float、long、double)以及字符串常量。
  • final 修饰的基本数据类型和
  • 字符串变量字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )
    (2)引用的值在程序编译期是无法确定的,编译器无法对其进行优化。
    String str4 = str1 + str2实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
    不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
String[] arr = {"he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
    s += arr[i];
}
System.out.println(s);

上述代码:StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。

String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
    s.append(value);
}
System.out.println(s);

上述代码:如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

6.内部类

将一个类的定义放在里另一个类的内部,这就是内部类。
广义上我们将内部类分为四种:成员内部类、静态内部类、局部(方法)内部类、匿名内部类。

内部类基本概念:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象

class Outer {
        int num = 4;
        class Inner {
            void show() {
                System.out.println("inner show run " + num);
            }
        }
        public void method() {
            Inner in = new Inner();// 创建内部类的对象。
            in.show();// 调用内部类的方法。
        }
    }

可以使用内部类继承某个具体的或抽象的类,间接解决类无法多继承引起的一系列问题。

匿名内部类

匿名内部类:是没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。
原本创建子类或者实现类去继承父类和实现接口,才能重写其中的方法。但是有时候子类和实现类只使用了一次。这个时候可以使用匿名内部类,不用去写子类和实现类,起到简化代码的作用。
【也就是说可以直接实现接口方法,和重写父类方法】

匿名内部类的格式:父类/接口  对象 = new 父类/接口(){   重写父类/接口中的方法   }

这样做就把子类继承父类,重写父类中的方法,创建子类对象,合成了一步完成,减少了其中创建子类的过程。
或者将实现类实现接口,重写接口中的方法,创建实现类对象,合成了一步完成,减少了其中创建实现类的过程

Collections类

Collections类是java提供的一个操作List,Set和Map等集合的工具类。Collections类中提供了一系列操作集合的静态方法,使用这些方法可以实现对集合元素的排序、查询、修改等操作。

在这个包下java.util.Collections
直接使用:

Collections.reverse(list);//反转
Collections.shuffle(list);//随机打乱
Collections.sort(list);//升序
Collections.sort(list, new Comparator<Object>() {//可以根据指定的Comparator指定的顺序对指定的List集合进行升序排序。
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Integer && o2 instanceof Integer)
                    return (Integer)o2 - (Integer)o1;
                return -1;
            }
        });
Collections.swap(list, 0, list.size() - 1);//指定0与最后一个元素交换
int max = (int) Collections.max(list);//集合中的最大值
int min = (int) Collections.min(list);//集合中的最小值
int times_0 = Collections.frequency(list, 11);//指定元素在指定集合中一共出现的次数
Collections.copy(list2, list);//将指定的旧集合中的元素拷贝到指定的新集合中。当新集合的长度小于旧集合时,抛出下标越界异常
Collections.replaceAll(list, "Cyan", "Ice")//将集合中指定的旧值全部替换为指定的新值

7.Java集合类有哪些

主要集合类包括:

  • List:有序集合,允许元素重复。主要实现类有ArrayList、LinkedList和Vector等。
    • ArrayList:基于动态数组实现,适合频繁的随机访问
    • LinkedList:基于双向链表实现,适合频繁插入和删除
    • Vector:与ArrayList类似同样维护一个数组, 但通过加synchronized来保证线程安全
  • Set:无序集合,不允许元素重复。主要实现类有HashSet、LinkedHashSet、TreeSet。
    • HashSet:基于哈希表实现,不保证集合的迭代顺序
    • LinkedHashSet:继承自HashSet,通过链表维护元素插入顺序
    • TreeSet:基于红黑树实现,可以对集合中的元素进行排序
  • Map:键值对集合,一个键最多只能映射到一个值。主要实现类有HashMap、LinkHashMap、TreeMap。
    • HashMap:基于哈希表实现,不保证映射顺序
    • LinkHashMap:继承自HashMap,通过链表维护键值对的插入顺序
    • TreeMap:基于红黑树实现,可以对键进行排序
  • Queue:元素有序,可重复,按特定的排队规则来确定先后顺序。

Java 集合框架概览:
在这里插入图片描述

8.HashMap底层数据结构

HashMap核心知识,扰动函数、负载因子、扩容链表拆分
HashMap 的底层数据结构是哈希表(散列表),具体实现为数组+链表(JDK1.8前)或数组+链表+红黑树(JDK1.8及以后)
主要特点:

  • 数组:用于存储桶(Bucket)的数组,每个桶要么存储一个元素(键值对),要么存储一个指向链表头结点的引用(哈希冲突时)
  • 链表:用于解决哈希冲突,的那个多个键值对银蛇到数组的同一位置时,会形成一个链表。
  • 红黑树:联保长度超过某个阈值(默认为8),链表会转换为红黑树以提高查找效率。红黑树是自平衡的二叉查找树,能保持较低的高度,从而减少查找时间。

工作流程:
在这里插入图片描述

9.List和Set的区别,以及底层数据结构实现

List中的元素是有序、可重复的。
List接口有三个实现类:LinkedList基于链表实现,链表内存是散列的,增删快,查找慢;ArrayList基于数组实现,非线程安全,效率高,增删慢,查找快;Vector也是基于数组实现,但使用synchronized保证线程安全,效率低,增删慢,查找慢;

Set中的元素是无序的不可重复的。
Set接口有三个实现类:HashSet底层是基于HashMap实现,使用该方式时需要重写 equals()和 hash Code()方法;LinkedHashSet继承于 HashSet,通过来链表来维护键值对的插入顺序;TreeMap底层基于红黑树实现,可以对键进行排序。

10.为什么arraylist检索快增删慢

1.检索快:

  • 直接通过索引访问,因为数组内存地址联系,通过首地址+(元素长度*下标)就能计算出内存地址
  • 时间复杂度低,查找的时间复杂度为O(1)

2.增删慢

  • 需要移动元素,尤其是操作发生在列表开头或中间位置时,后续元素需要前移或后移,导致时间复杂度高。
  • 当ArrayList容量不足时,需要扩容。扩容涉及创建新数组,然后间原数组复制到新数组中,时间复杂度O(n)且会导致内存碎片化。

3.删除的额外开销
删除元素使,还需要处理数组末尾的null元素???,以及可能的数组缩容

11.红黑树和链表的查询效率对比

BST(二叉搜索树),AVL(平衡二叉树)、RBT(红黑树)的区别

红黑树是自平衡二叉搜索树,通过一些列旋转和重新着色操作维护树的平衡性。
平衡性有一组规则来保证高度近似O(logn),每个节点要么是红要么是黑,根节点是黑色,叶子结点是黑色(NIL节点、空节点)。
因此红黑树的查询时间复杂度为O(logn)
/
链表是线性数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表中每个元素通过指针链接,不支持索引访问元素,每次查询需要从头结点开始循序遍历,查询的时间复杂度O(logn)。

12.多线程下想用hashmap怎么办

多线程下使用HashMap可能会导致数据不一致问题,因为HashMap不是线程安全的。
(1)使用Java集合提供的Collection.synchronizedMap方法,使其成为一个线程安全的Map。
每次只能有一个线程访问Map,可能个会限制并发性。
(2)ConcurrentHashMap提供更高级的并发级别。内部使用分段锁(分段加锁,减少锁的竞争),使得多个线程可以同时读写不同的数据段,从而提高性能。
(3)使用读写锁来控制对Map的访问。这种锁允许多个读操作同时进行但写操作互斥

常见多线程的使用方式(创建线程的方式)

1.继承Thread类

继承java.lang.Thread类并重写run()方法,可以创建一个新线程。
创建该类的实例并调用start()方法来启动线程。
优点:简单直观
缺点:Java不支持多重继承,如果类已经继承了另一个类,则无法再继承Thread类

class Grape extends Thread {    /**当某个类继承了Thread类后,就可以当作线程使用*/
    @Override
    public void run() {
    }
}
public class Thread_Demo1 {
    public static void main(String[] args) {
        //创建线程对象
        Grape grape = new Grape();
        //启动线程!
        grape.start();
    }
}
 

2.实现Runnable接口

实现java.lang.Runnable接口并重写run()方法。
将任务实例传递给Thread类的构造器来创建线程对象,调用start()方法启动线程。
优点:避免java单继承的限制;线程与任务Runnable实例分离,便于任务的传递和共享。
缺点:相比继承Thread类 稍显复杂

class Fruit {}
class Apple extends Fruit implements Runnable { 
    @Override
    public void run() {
        System.out.println("线程是:" + Thread.currentThread().getName());
    }
}

public class Thread_Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Apple apple = new Apple();
        //实例传递给Thread类的构造器来创建线程对象
        Thread thread = new Thread(apple);
        thread.start();

    }
}
 

3.使用Callable和Future

Callable接口类似于Runable,但它可以返回一个结果并可以抛出异常。Future接口用于表示异步计算的结果。
FutureTask是Future和Runnable之间的桥梁,它实现了Runnable并封装了Callable。
优点:可以返回执行结果和抛出异常。
缺点:需要处理Future的结果,可能会阻塞调用线程。

4.使用线程池ExecutorService

线程池是一种基于池化技术的多线程管理工具,可以重用线程,减少线程创建和销毁的开销,提高系统的相应速度和吞吐量。
优点:提高了资源利用率和系统相应速度;便于管理和控制线程的生命周期。
缺点:需要合理配置线程池的参数,比如线程池大小、队列类型等以避免资源耗尽或性能瓶颈。

例1:
首先是MyCallable类,继承Callbale接口,重写call()函数
之后是主函数,使用了线程池,以及利用Future接口的get()方法来获取返回值Future接口概念:异步接收,ExecutorService .submit()的返回结果,其提供的get()方法异步等待call()的返回值),这个案例可以清楚地看到,get()的阻塞线程的效果。

package Threads;
// Commons IO是针对开发IO流功能的工具类库
// FileUtils文件工具,复制url到文件
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
// 线程创建方式三:实现callable接口
/*
callable的好处
1.可以定义返回值
2.可以抛出异常
*/
public class MyCallbale implements Callable<Integer> {
    
    @Override
    public Integer call() throws Exception {

        System.out.println("开始执行");
        int sum = 0;
        for(int i = 0; i <= 100; i++){
            //这里加这个等待的时间是为了,更明显的看出get的阻塞效果,除此之外没有别的含义。
            Thread.sleep(50);
            sum += i;
        }
        System.out.println("执行结束了");
        return sum;
    }
}
 public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义任务
        MyCallbale task = new MyCallbale();

        //定义线程池
        ExecutorService es = Executors.newSingleThreadExecutor();

        //提交任务,这里future会异步接收es正在执行的task的返回值
        Future<Integer> future = es.submit(task);

        //关闭线程池
        es.shutdown();

        //获取返回值,这里的get是阻塞式的等待future的返回值的
        Integer sum = future.get();

        System.out.println(sum);
    }


例2:

  1. 将两个任务分别创建出来,一个计算1-50的和,另一个计算51-100的和
  2. 创建线程池,因为只有两个任务,所以创建固定线程数量的线程池
  3. 将两个任务提交到线程池,并利用Future接口的get方法接收返回的结果
import java.util.concurrent.*;

public class TestFuture {

    public static void main(String[] args) throws Exception{

        //匿名内部类
        Callable<Integer> thread1 = new Callable<Integer>(){
            @Override
            public Integer call() throws Exception{

                System.out.println(Thread.currentThread().getName() + "正在执行1-50的和");
                int sum = 0;
                for(int i = 1; i < 50; i++ ){
                    sum += i;
                }
                System.out.println(Thread.currentThread().getName() + "执行完成");
                return sum;
            }
        };

        //匿名内部类
        Callable<Integer> thread2 = new Callable<Integer>(){
            @Override
            public Integer call() throws Exception{

                System.out.println(Thread.currentThread().getName() + "正在执行50-100的和");
                int sum = 0;
                for(int i = 50; i < 101; i++ ){
                    sum += i;
                }
                System.out.println(Thread.currentThread().getName() + "执行完成");
                return sum;
            }
        };

        ExecutorService es =  Executors.newFixedThreadPool(2);
        Future<Integer> sum1 = es.submit(thread1);
        Future<Integer> sum2 = es.submit(thread2);

        Integer total = sum1.get() + sum2.get();

        System.out.println(total);
    }
}


JDK 1.8运行时数据区域:

在这里插入图片描述
线程私有的:【它们的生命周期和线程相同,随着线程的创建而创建,随着线程的死亡而死亡。】

  • 程序计数器
  • 虚拟机栈( VM Stack)
  • 本地方法栈

线程共享的:

  • 堆(内含字符串常量池)
  • 方法区
  • 直接内存 (非运行时数据区的一部分)

Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。Java 世界中“几乎”所有的对象都在堆中分配,但是,随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

方法区

方法区属于是 JVM 运行时数据区域的一块逻辑区域,是各个线程共享的内存区域。在不同的虚拟机实现上,方法区的实现是不同的。
当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。
方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

字符串常量池

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

直接内存

直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。

虚拟机对象创建

1.类加载检查

  • 虚拟机遇到一条 new 指令时,首先去检查指令的参数是否能在常量池中定位到这符号引用,并且检查引用代表的类是否被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

2.分配内存
类加载检查通过后,接下来虚拟机将为新生对象分配内存。
对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。
分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定
内存分配的两种方式 (补充内容,需要掌握):

  • 指针碰撞:
    • 适用场合:堆内存规整(即没有内存碎片)的情况下。
    • 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
    • 使用该分配方式的 GC 收集器:Serial, ParNew
  • 空闲列表:
    • 适用场合:堆内存不规整的情况下。
    • 原理:虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
    • 使用该分配方式的 GC 收集器:CMS
内存分配并发问题(补充内容,需要掌握)
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
. CAS+失败重试: CAS 是如果冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
. TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配

3.初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

4.设置对象头
将这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

5.执行 init 方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的访问定位

建立对象就是为了使用对象,我们的 Java 程序通过栈上的 引用数据来操作堆上的具体对象。
对象的访问方式由虚拟机实现而定,目前主流的访问方式有:使用句柄、直接指针

直接指针:
reference中存储的直接就是对象地址
在这里插入图片描述

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

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

相关文章

为什么 FPGA 的效率低于 ASIC?

FPGA是“可重构逻辑”器件。先制造的芯片&#xff0c;再次设计时“重新配置”。 ASIC 不需要“重新配置”。你先设计&#xff0c;把它交给代工厂&#xff0c;然后制造芯片。 现在让我们看看这些芯片的结构是什么样的&#xff0c;以及它们的不同之处。 ● 逻辑单元&#xff1a;F…

(leetcode学习)21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[]示例…

Ethernet/IP转ModbusTCP协议转化网关(经典通讯案例)

怎么样把EtherNet/IP和ModbusTCP网络通讯连接起来呢?最近有很多朋友咨询这个问题&#xff0c;在这里统一为大家详细说明一下。其实有一个设备可以很轻松地解决这个问题&#xff0c;名为YC-EIP-TCP&#xff0c;下面是详细信息。 一&#xff0c;设备主要功能 1YC-EIP-TCP工业级…

数据结构(5.3_1)——二叉树的先中后序遍历

先序遍历——根左右——前缀表达式 中序遍历——左根右——中缀表达式 后序遍历——左右根——后缀表达式 二叉树的遍历(手算) 先序遍历代码 struct ElemType {int value; }; //二叉树的结点(链式存储) typedef struct BiTNode {ElemType data;//数据域struct BiTNode *lchil…

数据采集产品 搭建智能水房实现远程控制的案例分享

一、系统简介 随着科技的逐步发达&#xff0c;在各个领域都迫切的希望有一种控制系统能代替传统的操作方式&#xff0c;智能水房控制系统则是一种符合人们要求的系统&#xff0c;他可代替传统的人工机械操控&#xff0c;真正实现控制智能化。通过水房的各种数据采集可以实现24小…

学习记录——day15 数据结构 链表

链表的引入 顺序表的优缺点 1、优点:能够直接通过下标进行定位元素&#xff0c;访问效率高&#xff0c;对元素进行查找和修改比较快 2、不足:插入和删除元素需要移动大量的元素&#xff0c;效率较低 3、缺点:存储数据元素有上限&#xff0c;当达到MAX后&#xff0c;就不能再…

VScode tab不能正常使用

现象不能够在文本编辑器中按下tab键&#xff0c;如果按下了&#xff0c;就焦点会跑到终端或者是其他地方&#xff0c;猜测是因为装了某些插件导致的。 解决方案比较简单&#xff1a; 删除掉其他的按键 比如这样的&#xff0c;保存就可以了

mysql1055报错解决方法

目录 一、mysql版本 二、 问题描述 三、解决方法 1.方法一&#xff08;临时&#xff09; 2.方法二&#xff08;永久&#xff09; 一、mysql版本 mysql版本&#xff1a;5.7.23 二、 问题描述 在查询时使用group by语句&#xff0c;出现错误代码&#xff1a;1055&#xf…

Facebook的创新之路:科技驱动的社交革命

Facebook自2004年创立以来&#xff0c;已经从一个大学校园内的社交网站发展成为全球最大的社交媒体平台。其成功的背后&#xff0c;不仅仅是广泛的用户基础和高效的运营模式&#xff0c;更在于其不断推进的技术创新。本文将探讨Facebook在技术创新方面的诸多努力&#xff0c;如…

MYSQL 六、mysql锁 1

一、概述 在数据库中&#xff0c;除传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的 资源。为保证数据的一致性&#xff0c;需要对 并发操作进行控制 &#xff0c;因此产生了 锁 。同时 锁机制 也为实现MySQL 的各…

GitHub Actions是什么

目录 GitHub Actions是什么 GitHub Actions的使用方法 示例 注意事项 GitHub Actions配置文件中-工作流的 :Workflow 一、自动化任务执行 二、规范团队协作 三、灵活配置和定制 四、提高开发效率 五、集成GitHub生态 六、可复用性和共享性 仓库中的“Actions”部分…

新生上大学提前去西藏旅游有什么要注意的,语言上该怎么办?

新生前往西藏旅游并提前适应大学生活是一次充满挑战与发现的旅程。在准备过程中&#xff0c;重要的是要对高原反应有所准备&#xff0c;了解其症状并采取预防措施&#xff0c;同时携带必要的防晒和保暖衣物以应对极端的气候条件。在交通和饮食方面&#xff0c;选择安全可靠的选…

奔跑利润如何释放?来看看怎么分析现货黄金的跟踪止损位

跟踪止损位是现货黄金交易和资金管理中一个重要的概念。我们做现货黄金的时候&#xff0c;将仓位分成两部分&#xff08;或以上&#xff09;。第一部分&#xff0c;我们在首个目标位获利离场&#xff0c;剩下那部分就可以让它奔跑&#xff0c;看看市场会不会走出大行情&#xf…

springboot系列十一:Thymeleaf

文章目录 官方文档基本介绍Thymeleaf机制说明Thymeleaf语法表达式运算符th属性迭代条件运算使用Thymeleaf th属性需要注意点 Thymeleaf综合案例需求说明思路分析代码实现 作业布置 官方文档 在线文档: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html 离线…

自动导入unplugin-auto-import+unplugin-vue-components

文章介绍 接下来将会以Vite Vue3 TS的项目来举例实现 在我们进行项目开发时&#xff0c;无论是声明响应式数据使用的ref、reactive&#xff0c;或是各种生命周期&#xff0c;又或是computed、watch、watchEffect、provide-inject。这些都需要前置引入才能使用&#xff1a; …

Mem0:个性化的AI记忆层,一款开源的大语言记忆增强工具

在人工智能的浪潮中&#xff0c;个性化体验已成为创新的关键。而随着各种各样的模型迭代更新&#xff0c;如何为AI应用提供持久、智能的记忆系统逐渐成为了一个关键挑战。 最近开源的Mem0项目为我们提供了一个强大的解决方案。它为大型语言模型(LLM)提供了一个智能、自我优化的…

CTF ssrf 基础入门

0x01 引言 我发现我其实并不是很明白这个东西&#xff0c;有些微妙&#xff0c;而且记忆中也就记得Gopherus这个工具了&#xff0c;所以重新学习了一下&#xff0c;顺便记录一下吧 0x02 辨别 我们拿到一个题目&#xff0c;他的名字可能就是题目类型&#xff0c;但是也有可能…

昇思25天学习打卡营第九天|本地安装mindspore之一|Linux的系统在vmware上的安装以及mindspore的安装

课程已经学完了&#xff0c;打算再深入一些。初步的想法是&#xff0c;在本地安装&#xff0c;本地执行。 根据老师的指引&#xff0c;MindSpore官网&#xff0c;“https ://www.mindspore.cn/install/”&#xff0c;注意&#xff0c;因为csdn博客编辑器的原因&#xff0c;当我…

C#与C++交互开发系列(三):深入探讨P/Invoke基础知识

欢迎来到C#与C交互开发系列的第三篇。在这篇博客中&#xff0c;我们将深入探讨P/Invoke&#xff08;Platform Invocation Services&#xff09;的基础知识。P/Invoke是C#调用非托管代码的一种机制&#xff0c;能够让C#直接调用C编写的动态链接库&#xff08;DLL&#xff09;中的…

轻松翻译,你值得拥有的PDF翻译工具分享

大家好&#xff0c;作为一名初入职场的小菜鸟&#xff0c;我发现了一个让我头疼不已的问题——那就是PDF文件的翻译。在全球化日益加剧的今天&#xff0c;我们经常会遇到需要阅读或者翻译外文PDF文件的情况。但PDF文件不同于Word或Excel&#xff0c;它通常不易直接编辑&#xf…