• Java面试题


    一、SpringBoot核心注解

    1.1 @SpringBootApplication项目的启动注解,是一个组合注解

            包含@SpringbootConfiguration+@EnableAutoConfiguration+@ComponentScan 三个注解,一般用于扫描包的配置,如:

    @SpringBootApplication(scanBasePackages = "com.xxx")

    //扫描com.xxx包下的相关Configuration文件、自动映射文件、组件文件

    1.1.1 @SpringBootConfiguration

    声明为配置类(根配置类,首先扫描该类,本身是一个IOC容器的配置类),将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。

    SpringBoot使用Java Config技术进行配置,Java Config使用注解和Java代码的方式代替xml配置文件。

    任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。

    任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

    1.1.2 @ComponentScan

    自动扫描当前包及子包下被@Component,@Controller,@Service,@Repository注解标记的类,将其作为bean加载到IOC容器中。

    不指定范围则默认从注解所在类的包下进行扫描。

    1.1.3 @EnableAutoConfiguration

    核心注解,是自动配置的入口,SpringBoot根据添加的jar包来进行项目的默认配置。

    1.2 @RestController

    @RestController注解有两个目的。首先它是一个类似于@controller和@Service的构造型注解,能够让类被组件扫描功能发现。但是,与REST最相关在于@RestController会告诉Spring,控制器中所有的处理器方法的返回值都要直接写入响应体中,而不是将值放到模型中并传递给一个视图以便于渲染。
    作为替代方案就是@Controller加上@Response。

    @ResponseBody这个注解通常使用在控制层(controller)的方法上,其作用是将方法的返回值以特定的格式写入到response的body区域,进而将数据返回给客户端。

    @RequestBody 注解则是将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。

    作用:

    1) 该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定    到要返回的对象上; 
    2) 再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。

    1.3 SpringMVC常用的注解

    @Controller 标识是一个Controller,Spring包扫描创建实例

    @RequestMapping 请求后的映射路径

    @PathVariable 标识接收单个参数

    @ResponseBody 返回对象利用jackson工具类转换为json字符串

    @RequestParam 参数名和请求参数名称不同时使用,可以设置默认值

    二、如何获取自动生成的主键

    在使用insert标签的时候,usegeneratedKeys=true keyproperty="id"

    三、属性名和字段名不一致的情况

    3.1 使用别名处理

    3.2 通过resultMap来设置字段和属性的映射关系

    四、JVM的内存结构

    4.1 程私有区域的生命周期与线程相同,随线程启动而创建,随线程结束而销毁。在JVM内部,每个线程都与操作系统的本地线程直接映射,因此线程私有内存区域的存在与否,和本地线程的启动和销毁对应。

    4.2 线程共享区域随虚拟机启动而创建,随虚拟机的关闭而销毁。

    4.3 直接内存也叫堆外内存,它并不是JVM运行时数据区的一部分,但在并发编程中被频繁使用。JDK的NIO模块提供的基于Channel与Buffer的I/O操作方式就是基于堆外内存实现的,NIO模块通过调用Native函数库直接在操作系统上分配堆外内存,然后使用DirectByteBuffer对象作为这块内存的引用对内存进行操作,Java进程可以通过堆外内存技术避免Java堆和Native堆中来来回复制数据带来的资源浪费和性能消耗,因此堆外内存在高并发应用场景下被广泛使用(Netty、Flink、HBase、Hadoop 都有用到堆外内存)。

    每个区的作用:

    Java虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息。(栈里面存储的是的地址,实际指向的是堆里面的对象)

    本地方法栈:里面并没有我们写的代码逻辑,存储c++的native方法运行时候的栈区。

    程序计数器:(指向当前程序运行的位置)它是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、线程恢复等都依赖于计数器。

    堆:Java虚拟机中最大的内存空间,被所有的线程共享,几乎所有的对象实例都在这里分配实例

    方法区(Java8叫元空间):用于存放已被虚拟机加载的类信息、常量和静态变量等数据。

    五、JVM的运行时内存

    JVM的运行时内存也叫做JVM堆,从GC的角度可以将JVM分为新生代、老年代和永久代。

    其中新生代默认占1/3堆内存空间,老年代默认占2/3堆内存空间,永久代占非常少的对内存空间。

    新生代又分为Eden区、SurvivorFrom区和SurvivorTo区, Eden区默认占8/10新生代空间,SurvivorFrom区和SurvivorTo区默认分别占1/10新生代空间;Eden区最小占3/5新生代空间,SurvivorFrom区和SurvivorTo区分别占1/5新生代空间,如下图所示:

    永久代

            永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载Class文件过多时会抛出Out Of Memory异常,比如Tomcat引用Jar文件过多会导致JVM内存不足而无法启动。

            需要注意的是,在Java 8 中永久代已经被元数据区(也叫做元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,之和操作系统的内存有关。

            在Java 8 中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用的内存空间决定。

    原文链接:https://blog.csdn.net/qq_45886144/article/details/124083079

    六、强引用、软引用、弱引用、虚引用是什么,有什么区别?

    引用类型的作用:

    1. 可以 通过代码的方式 决定 某些对象的生命周期
    2. 有利于JVM进行垃圾回收

    6.1 强引用(StrongReference)

    强引用,就是普通的对象引用关系,如 String s = new String("ConstXiong")

    只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

    对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应引用赋值为null,一般认定就是可以被垃圾收集的了。

    6.2 软引用(SoftReference)

    软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

    因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

    6.3 弱引用(WeakReference)

    弱引用也是用来描述非必需对象的,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

    6.4 虚引用(PhantomReference)

    虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收

    参考:

    软引用和弱引用 - 风好大 - 博客园JVM面试(九)-强引用、软引用、弱引用、虚引用及应用场景_星光之子0317的博客-CSDN博客_softreference使用场景软引用和弱引用 - 风好大 - 博客园

    七、ThreadLocal为什么使用WeakReference弱引用

    threadlocalmap 的 key是ThreadLocal,value 是存储的值;

    threadlocalmap key 是弱引用,value 是强引用;

    ThreadLocal存入值时使用当前ThreadLocal实例作为key,存入当前线程对象中的Map中去。

    参考:为什么 ThreadLocalMap 的 key 是弱引用,而 value 是强引用? - 知乎

    八、JVM类加载机制

    加载(Loading),通过一个类的全限定名来获取定义此类的二进制字节流;将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

    验证(Verification),确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

    准备(Preparation),正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值。

    解析(Resolution),是 JVM 将常量池内的符号引用替换为直接引用的过程。

    初始化(Initialization),执行类构造器 方法的过程,执行所有类变量的赋值动作和静态语句块(static{}块)。

    其中验证、准备、解析统称为称为连接(Linking)。

    参考:类加载过程 - Vincent-yuan - 博客园

    JVM 面试题解答(40道全)_mb5fdcae3079e89的技术博客_51CTO博客

    九、threadLocal在父子线程中可继承吗

    不可以

    十、Java中截取字符串方法

    1、subString()方法来进行字符串截取,返回字符串中的子字符串

    2、split()+正则表达式来进行截取,将字符串按照分割符截取,以数组形式返回

    String str = "hello, name, 12345, 6789";

    String[] strs=str.split(",");

    十一、HashMap的遍历

    1.使用 For-each 循环遍历 HashMap的map.entrySet()

    1. public static void main(String[] args) {
    2. Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
    3. coursesMap.put(1, "C");
    4. coursesMap.put(2, "C++");
    5. coursesMap.put(3, "Java");
    6. coursesMap.put(4, "Spring Framework");
    7. coursesMap.put(5, "Hibernate ORM framework");
    8. // 1. 使用 For-each 循环遍历 HashMap
    9. for (Map.Entry < Integer, String > entry: coursesMap.entrySet()) {
    10. System.out.println(entry.getKey());
    11. System.out.println(entry.getValue());
    12. }
    13. }

    2、使用 Iterator 遍历 HashMap EntrySet

    1. public static void main(String[] args) {
    2. Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
    3. coursesMap.put(1, "C");
    4. coursesMap.put(2, "C++");
    5. coursesMap.put(3, "Java");
    6. coursesMap.put(4, "Spring Framework");
    7. coursesMap.put(5, "Hibernate ORM framework");
    8. // 2. 使用 Iterator 遍历 HashMap EntrySet
    9. Iterator < Entry < Integer, String >> iterator = coursesMap.entrySet().iterator();
    10. while (iterator.hasNext()) {
    11. Entry < Integer, String > entry = iterator.next();
    12. System.out.println(entry.getKey());
    13. System.out.println(entry.getValue());
    14. }
    15. }

    3、使用 Iterator 遍历 HashMap KeySet

    1. public static void main(String[] args) {
    2. Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
    3. coursesMap.put(1, "C");
    4. coursesMap.put(2, "C++");
    5. coursesMap.put(3, "Java");
    6. coursesMap.put(4, "Spring Framework");
    7. coursesMap.put(5, "Hibernate ORM framework");
    8. // 3. 使用 Iterator 遍历 HashMap KeySet
    9. Iterator < Integer > iterator = coursesMap.keySet().iterator();
    10. while (iterator.hasNext()) {
    11. Integer key = iterator.next();
    12. System.out.println(key);
    13. System.out.println(coursesMap.get(key));
    14. }
    15. }

    4、通过map.values()遍历所有的value

    1. public static void main(String[] args) {
    2. HashMap map = new HashMap<>();
    3. map.put("key1", "value1");
    4. map.put("key2", "value2");
    5. map.put("key3", "value3");
    6. map.put("key4", "value4");
    7. map.put("key5", "value5");
    8. // 4、通过map.values()遍历所有的value
    9. for (String value : map.values()) {
    10. System.out.println(value);
    11. }
    12. }

    5、使用 Lambda 表达式遍历 HashMap

    1. public static void main(String[] args) {
    2. Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
    3. coursesMap.put(1, "C");
    4. coursesMap.put(2, "C++");
    5. coursesMap.put(3, "Java");
    6. coursesMap.put(4, "Spring Framework");
    7. coursesMap.put(5, "Hibernate ORM framework");
    8. // 5. 使用 Lambda 表达式遍历 HashMap
    9. coursesMap.forEach((key, value) -> {
    10. System.out.println(key);
    11. System.out.println(value);
    12. });
    13. }

     6、使用 Stream API遍历 HashMap

    1. public static void main(String[] args) {
    2. Map < Integer, String > coursesMap = new HashMap < Integer, String > ();
    3. coursesMap.put(1, "C");
    4. coursesMap.put(2, "C++");
    5. coursesMap.put(3, "Java");
    6. coursesMap.put(4, "Spring Framework");
    7. coursesMap.put(5, "Hibernate ORM framework");
    8. // 6. 使用 Stream API 遍历 HashMap
    9. coursesMap.entrySet().stream().forEach((entry) - > {
    10. System.out.println(entry.getKey());
    11. System.out.println(entry.getValue());
    12. });
    13. }

  • 相关阅读:
    C/C++中的STL
    传统连接弊端分析、数据库连接池原理
    Markdown使用方法
    Clickhouse基准测试实践
    RabbitMQ原理和架构图解(附6大工作模式)
    【Redis】Redis 的基础数据结构 以及 各种数据结构常用命令使用示例
    巨子生物在香港上市:薇娅突击入股,范代娣、严建亚夫妇提前套现
    汽车行业DBC文件解析 | Python 解析dbc文件
    ECU Bootloader自学笔记
    实践分享:鸿蒙跨平台开发实例
  • 原文地址:https://blog.csdn.net/inexaustible/article/details/126498565