11. java创建对象有哪些方式?

1. 使用new关键字
2. 反序列化对象数据
3. 反射机制:Class类的forName方法的newInstance()
4. 反射机制: 对应类的getConstructor()的newInstance()
5. 通过clone()函数复制,必须实现Cloneable接口

4. 获取 Class 对象的四种方式

1. 直接该类名称的.class
2. 通过Class.forName传入类的全路径获取
3. 通过对象实例getClass函数获取
4. 通过类加载器ClassLoader.loadClass()传入类的路径获取

3. 动态代理

就是给已经开发好的实体类增加功能的手段,不改变原有的处理逻辑

jdk动态代理类使用步骤
1.定义一个接口及其实现类;
2.自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
3.通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。

GCLIB动态代理类使用步骤
1.定义一个类
2.自定义MethodIntercepter并重写intercept方法, 用于拦截增强被代理类的方法,和JDK动态代理的invoke方法类似
3. 通过Enhancer类的create创建代理类

写一个线程安全的懒加载单例

import java.util.Scanner;
class Main{
    private volatile static Main instance;
    public Main() {

    }

    public static Main getInstance() {
        if(instance == null) {
            synchronized (Main.class) {
                if(instance == null) {
                    instance = new Main();
                }
            }
        }
        return instance;
    }
}

2. I/O 流为什么要分为字节流和字符流呢?

防止转换成字节流进行信息交互,出现乱码问题

为什么ArrayList不是线程安全的,具体来说是哪里不安全?

不是

arraylist大体分三部分:

  • 需不需要扩容
  • size位置设置值
  • 将当前集合的大小加1

部分值为null

索引越界异常

size与add数量不一致

2. 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

线程不安全,都是实现set接口

Hashset是哈希表

LinkedHashset是哈希表+链表,保证了插入和取出顺序满足FIFO

TreeSet是红黑树

1. HashMap 和 Hashtable 的区别

线程安全:HashMap不安全,HashTable安全

效率:HashMap效率会高

对null key和nullvalue 支持:HashMap支持,HashTable不支持

初始容量和每次扩容容量大小不同:① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。

底层结构:JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间(后文中我会结合源码对这一过程进行分析)。Hashtable 没有这样的机制。

哈希函数的实现:HashMap 对哈希值进行了高位和低位的混合扰动处理以减少冲突,而 Hashtable 直接使用键的 hashCode() 值。

HashMap 的底层实现

HashMap 的长度为什么是 2 的幂次方

位运算效率更高:位运算(&)比取余运算(%)更高效。当长度为 2 的幂次方时,hash % length 等价于 hash & (length - 1)

可以更好地保证哈希值的均匀分布:扩容之后,在旧数组元素 hash 值比较均匀的情况下,新数组元素也会被分配的比较均匀,最好的情况是会有一半在新数组的前半部分,一半在新数组后半部分。

扩容机制变得简单和高效:扩容后只需检查哈希值高位的变化来决定元素的新位置,要么位置不变(高位为 0),要么就是移动到新位置(高位为 1,原索引位置+原容量)。

位运算效率更高,数据分布更均匀,扩容机制变得简单

列举HashMap在多线程下可能会出现的问题?

尾插法:会出现数据覆盖

头插法:会在扩容机制中出现死循环,扩容后需要重新分配位置

6. 列举HashMap在多线程下可能会出现的问题?

7. ConcurrentHashMap 和 Hashtable 的区别

底层数据结构:分段数组和链表。jdk8之后是数组和链表/红黑树;hashtable是数组和链表

实现线程安全的方式:ConcurrentHashMap直接对node加锁 cas; hashtable是整个一把锁,效率低下,使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

ConcurrentHashMap已经用了synchronized,为什么还要用CAS(乐观锁)呢

为什么HashMap要用红黑树而不是平衡二叉树?

平衡二叉树追求的是一种 “完全平衡” 状态:任何结点的左右子树的高度差不会超过 1,优势是树的结点是很平均分配的。这个要求实在是太严了,导致每次进行插入/删除节点的时候,几乎都会破坏平衡树的第二个规则,进而我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。

红黑树不追求这种完全平衡状态,而是追求一种 “弱平衡” 状态:整个树最长路径不会超过最短路径的 2 倍。优势是虽然牺牲了一部分查找的性能效率,但是能够换取一部分维持树平衡状态的成本。与平衡树不同的是,红黑树在插入、删除等操作,不会像平衡树那样,频繁着破坏红黑树的规则,所以不需要频繁着调整,这也是我们为什么大多数情况下使用红黑树的原因。

HashMap的扩容机制介绍一下

hashMap默认的负载因子是0.75,即如果hashmap中的元素个数超过了总容量75%,则会触发扩容,扩容分为两个步骤:

第1步是对哈希表长度的扩展(2倍)

第2步是将旧哈希表中的数据放到新的哈希表中。

只需要看原来的hash值新增的那个bit是0还是1,0是不动,1是移动原始容量距离

请简要描述线程与进程的关系,区别及优缺点?

  • 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。
  • 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
  • 线程执行开销小,但不利于资源的管理和保护;而进程正相反。

有了进程为什么还需要线程?

  • 进程切换是一个开销很大的操作,线程切换的成本较低。
  • 线程更轻量,一个进程可以创建多个线程。
  • 多个线程可以并发处理不同的任务,更有效地利用了多处理器和多核计算机。而进程只能在一个时间干一件事,如果在执行过程中遇到阻塞问题比如 IO 阻塞就会挂起直到结果返回。
  • 同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核

4. 如何创建线程?

继承Thread,实现Runnable接口、实现Callable接口,实现线程池

最终都是 new Thread.start()

说说线程的生命周期和状态?

初始

运行

阻塞

等待

超时等待

终止

7. Thread#sleep() 方法和 Object#wait() 方法对比

共同点:两者都可以暂停线程的执行。

区别

  • sleep() 方法没有释放锁,而 wait() 方法释放了锁

  • wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。

  • wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。

  • sleep()Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。

3. 并发编程三个重要特性

原子性:

可见性:修改是可见的

有序性:volatile关键字可以禁止指令进行重排序优化。

4. volatile 关键字

4. 如何实现乐观锁?

版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功

CAS:CAS 涉及到三个操作数:

  • V:要更新的变量值(Var)
  • E:预期值(Expected)
  • N:拟写入的新值(New)

4. synchronized 和 volatile 有什么区别?

互补

volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。

  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

synchronized 和 ReentrantLock 有什么区别?

都是可重入锁

synchronized是依赖于JVM,ReentrantLoack依赖于API

synchronized是非公平的,ReentrantLoack可以是公平或者非公平

ReentrantLoack可实现等待可中断

可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

支持超时ReentrantLock 提供了 tryLock(timeout) 的方法,可以指定等待获取锁的最长等待时间,如果超过了等待时间,就会获取锁失败,不会一直等待

ThreadLocal 内存泄露问题是怎么导致的?如何避免?

  1. ThreadLocal 实例不再被强引用;key为null
  2. 线程持续存活,导致 ThreadLocalMap 长期存在。

如何避免:

  • 在使用完 ThreadLocal 后,务必调用 remove() 方法。 这是最安全和最推荐的做法。 remove() 方法会从 ThreadLocalMap 中显式地移除对应的 entry,彻底解决内存泄漏的风险。 即使将 ThreadLocal 定义为 static final,也强烈建议在每次使用后调用 remove()

  • 在线程池等线程复用的场景下,使用 try-finally 块可以确保即使发生异常,remove() 方法也一定会被执行。

5. 如何跨线程传递 ThreadLocal 的值?

  • InheritableThreadLocalInheritableThreadLocal 是 JDK1.2 提供的工具,继承自 ThreadLocal 。使用 InheritableThreadLocal 时,会在创建子线程时,令子线程继承父线程中的 ThreadLocal 值,但是无法支持线程池场景下的 ThreadLocal 值传递。

  • TransmittableThreadLocalTransmittableThreadLocal (简称 TTL) 是阿里巴巴开源的工具类,继承并加强了InheritableThreadLocal类,可以在线程池的场景下支持 ThreadLocal 值传递

3. 如何创建线程池?常用方法

start启动线程,线程池:submit对于callable,execute对于runable。

7. 线程池的拒绝策略有哪些?

12. 线程池中线程异常后,销毁还是复用?

3. 一个任务需要依赖另外两个任务执行完之后再执行,怎么设计?

1. 对象的创建过程

内存分配和回收原则

死亡对象判断方法

4. 如何判断一个常量是废弃常量?

如何判断一个类是无用的类?

垃圾收集算法

垃圾收集器

类加载过程

JVM 中内置了三个重要的 ClassLoader

类加载器和双亲委派机制。

1. 说说自己对于 Spring MVC 了解?

MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

3. 将一个类声明为 Bean 的注解有哪些?

  • @Component:通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

11. Bean 的生命周期了解么?

  1. 创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。

  2. Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如@Autowired 等注解注入的对象、@Value 注入的值、setter方法或构造函数注入依赖和值、@Resource注入的各种资源。

  3. Bean 初始化

    • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
    • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
    • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
    • 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
    • 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
    • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法。
  4. 销毁 Bean

    :销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。

    • 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
    • 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy 注解标记 Bean 销毁之前执行的方法。
  1. 整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。

  2. 初始化这一步涉及到的步骤比较多,包含 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBeaninit-method 的初始化操作。

  3. 销毁这一步会注册相关销毁回调接口,最后通过DisposableBeandestory-method 进行销毁。

img

3. SpringMVC 工作原理了解吗?

  1. 客户端(浏览器)发送请求, DispatcherServlet拦截请求。
  2. DispatcherServlet 根据请求信息调用 HandlerMappingHandlerMapping 根据 URL 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
  3. DispatcherServlet 调用 HandlerAdapter适配器执行 Handler
  4. Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServletModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上的 View
  5. ViewResolver 会根据逻辑 View 查找实际的 View
  6. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  7. View 返回给请求者(浏览器)

img

上述流程是传统开发模式(JSP,Thymeleaf 等)的工作原理。

对于前后端分离时,后端通常不再返回具体的视图,而是返回纯数据(通常是 JSON 格式,Spring 会自动将其转换为 JSON 格式),由前端负责渲染和展示。

1. Spring 循环依赖了解吗,怎么解决?

两个或多个 Bean 之间相互持有对方的引用。

@Component
public class CircularDependencyA {
    @Autowired
    private CircularDependencyB circB;
}

@Component
public class CircularDependencyB {
    @Autowired
    private CircularDependencyA circA;
}

有三级缓存:

  1. 一级缓存(singletonObjects):存放最终形态的 Bean(已经实例化、属性填充、初始化),单例池,为“Spring 的单例属性”⽽⽣。一般情况我们获取 Bean 都是从这里获取的,但是并不是所有的 Bean 都在单例池里面,例如原型 Bean 就不在里面。
  2. 二级缓存(earlySingletonObjects):存放过渡 Bean(半成品,尚未属性填充,未进行依赖注入),也就是三级缓存中ObjectFactory产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用ObjectFactory#getObject()都是会产生新的代理对象的。
  3. 三级缓存(singletonFactories):存放ObjectFactoryObjectFactorygetObject()方法(最终调用的是getEarlyBeanReference()方法)可以生成原始 Bean 对象或者代理对象(如果 Bean 被 AOP 切面代理)。三级缓存只会对单例 Bean 生效。
  1. 先去 一级缓存 singletonObjects 中获取,存在就返回;
  2. 如果不存在或者对象正在创建中,于是去 二级缓存 earlySingletonObjects 中获取;
  3. 如果还没有获取到,就去 三级缓存 singletonFactories 中获取,通过执行 ObjectFacotrygetObject() 就可以获取该对象,获取成功之后,从三级缓存移除B,并将B对象加入到二级缓存中。

步骤 1:创建 Bean A

  1. Spring 容器开始创建 Bean A,首先将 Bean A 的创建状态标记为 “正在创建”。
  2. 实例化 Bean A,将 Bean A 的早期引用(一个 ObjectFactory 对象)放入三级缓存 singletonFactories 中。这个 ObjectFactory 对象可以在需要时返回 Bean A 的早期实例。
  3. 开始对 Bean A 进行属性注入,发现 Bean A 依赖于 Bean B。

步骤 2:创建 Bean B

  1. Spring 容器开始创建 Bean B,同样将 Bean B 的创建状态标记为 “正在创建”。
  2. 实例化 Bean B,将 Bean B 的早期引用放入三级缓存 singletonFactories 中。
  3. 开始对 Bean B 进行属性注入,发现 Bean B 依赖于 Bean A。

步骤 3:解决 Bean B 对 Bean A 的依赖

  1. 当 Bean B 需要注入 Bean A 时,Spring 容器首先从一级缓存 singletonObjects 中查找 Bean A,发现没有找到。
  2. 接着从二级缓存 earlySingletonObjects 中查找,也没有找到。
  3. 然后从三级缓存 singletonFactories 中查找,找到了 Bean A 的早期引用(ObjectFactory 对象)。
  4. 调用 ObjectFactorygetObject() 方法,获取 Bean A 的早期实例。如果 Bean A 需要进行 AOP 代理,会在这里创建代理对象,并将代理对象放入二级缓存 earlySingletonObjects 中,同时从三级缓存 singletonFactories 中移除该引用。
  5. 将获取到的 Bean A 的早期实例注入到 Bean B 中。

步骤 4:完成 Bean B 的创建

  1. Bean B 完成属性注入后,进行初始化操作。
  2. 将完全创建好的 Bean B 实例放入一级缓存 singletonObjects 中,并从二级缓存 earlySingletonObjects 和三级缓存 singletonFactories 中移除相关引用。

步骤 5:完成 Bean A 的创建

  1. 由于 Bean B 已经创建完成,将 Bean B 实例注入到 Bean A 中。
  2. Bean A 完成属性注入后,进行初始化操作。
  3. 将完全创建好的 Bean A 实例放入一级缓存 singletonObjects 中,并从二级缓存 earlySingletonObjects 和三级缓存 singletonFactories 中移除相关引用。

1. 什么是 Spring Boot Starters?

Spring Boot Starters 是一组便捷的依赖描述符,它们预先打包了常用的库和配置。当我们开发 Spring 应用时,只需添加一个 Starter 依赖项,即可自动引入所有必要的库和配置,快速引入相关功能。

在没有 Spring Boot Starters 之前,开发一个 RESTful 服务或 Web 应用程序通常需要手动添加多个依赖,比如 Spring MVC、Tomcat、Jackson 等。这不仅繁琐,还容易导致版本不兼容的问题。而有了 Spring Boot Starters,我们只需添加一个依赖,如 spring-boot-starter-web,即可包含所有开发 REST 服务所需的库和依赖。

这个 spring-boot-starter-web 依赖包含了 Spring MVC(用于处理 Web 请求)、Tomcat(默认嵌入式服务器)、Jackson(用于 JSON 处理)等依赖项。这种方式极大地简化了开发过程,让我们可以更加专注于业务逻辑的实现。

2. 介绍一下@SpringBootApplication 注解

  • @EnableAutoConfiguration: 启用 Spring Boot 的自动配置机制。它是自动配置的核心,允许 Spring Boot 根据项目的依赖和配置自动配置 Spring 应用的各个部分。
  • @ComponentScan: 启用组件扫描,扫描被 @Component(以及 @Service、@Controller 等)注解的类,并将这些类注册为 Spring 容器中的 Bean。默认情况下,它会扫描该类所在包及其子包下的所有类。
  • @Configuration: 允许在上下文中注册额外的 Bean 或导入其他配置类。它相当于一个具有 @Bean 方法的 Spring 配置类。

3. Spring Boot 的自动配置是如何实现的?

Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配.

HTTP和HTTPS二者区别

  • HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输
  • HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
  • 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
  • HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

对称+非对称、摘要算法+数字签名、身份证书

TCP、UDP区别、应用场景

MySQL 基础架构

1. 结构

  • 连接器: 身份认证和权限相关(登录 MySQL 的时候)。
  • 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
  • 分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
  • 优化器: 按照 MySQL 认为最优的方案去执行。
  • 执行器: 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。
  • 插件式存储引擎:主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。InnoDB 是 MySQL 的默认存储引擎,绝大部分场景使用 InnoDB 就是最好的选择。

img

2. SQL语句在MySQL中的执行过程

  • 查询语句的执行流程如下:权限校验(如果命中缓存)--->查询缓存--->分析器--->优化器--->权限校验--->执行器--->引擎
  • 更新语句执行流程如下:分析器---->权限校验---->执行器--->引擎---redo log(prepare 状态)--->binlog--->redo log(commit 状态)

Redis单线程怎么监视IO连接?

Redis 通过 IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。

I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗

Redis I/O复用的三种实现方式?复杂度

建议deepseek问一下。

select:轮寻,O(n)

poll:轮寻,O(n)

Epoll: 基于事件触发,O(1)

Redis ZSet的底层实现

重点关注跳表的原理,复杂度,为什么要采用两种实现。

缓存击穿、缓存穿透、缓存雪崩

如何解决?是结合黑马点评问的。

如何保障数据库和缓存的一致性。

旁路缓存模式:先修改数据库,在删除缓存。

说说hashmap,线程安全吗?哈希冲突怎么解决?说说扩容?升级红黑树的阈值,降为链表的阈值?为什么二者不一样。

看javaguide。

阈值是8和6,防止频繁的转换。

聊聊JVM的内存区域

很重要,建议javaguide。

线程的:栈、程序计数器。

进程的:堆。方法区

聊聊垃圾回收器G1的回收过程?优点是什么?

javaguide。

聊聊对象在JVM的移动(Eden->S1和S2->老年代),为什么老年区占空间大于新生区?

很重要,注意数组等大对象直接放在老年区。

面向对象三个特点?多态的实现方式?

见javaguide。

重载和重写的区别?

见javaguide。关注一下重载发生在编译器,重写发生在运行期。

Copyright © 版权信息 all right reserved,powered by aspire-zero and Gitbook该文件修订时间: 2025-02-23 15:02:48

results matching ""

    No results matching ""