Java面试突击:46道高频基础题全解析(附30+手绘原理图)

Java后端面试突击:1-2月高效复习策略与高频考点深度解析 在软件工程领域,系统性能优化与高并发处理是后端开发的核心挑战,涉及JVM内存管理、并发编程、数据库索引与事务等关键技术。理解这些底层原理,如垃圾回收机制、锁的实现原理、SQL执行计划等,是构建稳定、高效应用系统的基石。其技术价值在于能显著提升系统吞吐量、降低延迟,并保障数据一致性,广泛应用于电商、金融、社交等需要处理海量请求与数据的场景。本文聚焦Java后端技术面试,针对JVM、并发、Spring、MySQL、Redis及分布式基础等核心知识域,提供一套以“问题链”思维模型为核心的突击复习框架,帮助开发者在 阅读详情

Java面试核心:从原理到实战的深度拆解与高频考点精讲

又到了一年一度的求职季,对于广大Java开发者而言,这既是机遇也是挑战。面对市场上层出不穷的“八股文”和浩如烟海的面试题,很多朋友感到无从下手,甚至陷入“背了忘,忘了背”的循环。这篇文章,我想和你聊聊如何真正理解而非机械记忆那些核心知识点。我会结合自己过去几年面试他人以及被面试的经验,用大量手绘原理图和实战代码片段,帮你构建一个清晰、立体的Java知识体系。我们的目标不是覆盖所有题目,而是让你掌握那些高频且关键的问题背后的“所以然”,从而在面试中游刃有余。

1. 理解Java的基石:JVM、内存与执行引擎

要真正掌握Java,绕不开对Java虚拟机(JVM)的深入理解。这不仅是面试高频区,更是你写出高性能、稳定代码的基础。

1.1 JVM内存区域的深度剖析

很多人对JVM内存区域的认知停留在“堆、栈、方法区”这几个名词上。但面试官想听的,是你对它们为何如此设计以及如何协同工作的理解。

JVM运行时数据区主要分为线程私有和线程共享两大类。为了让你有个直观印象,我们先看下面这张结构图:

+-------------------------------------------------------------------+
|                          JVM Runtime Data Area                    |
|                                                                   |
|  +---------------------+     +--------------------------------+  |
|  |   Thread-Private    |     |        Thread-Shared           |  |
|  |                     |     |                                |  |
|  |  +---------------+  |     |  +--------------------------+  |  |
|  |  |   Program     |  |     |  |         Heap            |  |  |
|  |  |   Counter     |  |     |  |  (对象实例、数组)        |  |  |
|  |  |   Register    |  |     |  +--------------------------+  |  |
|  |  +---------------+  |     |                                |  |
|  |                     |     |  +--------------------------+  |  |
|  |  +---------------+  |     |  |      Method Area        |  |  |
|  |  |   Java        |  |     |  | (类信息、常量、静态变量)|  |  |
|  |  |   Stack       |  |     |  +--------------------------+  |  |
|  |  |  (栈帧)       |  |     |                                |  |
|  |  +---------------+  |     |  +--------------------------+  |  |
|  |                     |     |  |   Runtime Constant      |  |  |
|  |  +---------------+  |     |  |       Pool              |  |  |
|  |  |   Native      |  |     |  |   (运行时常量池)         |  |  |
|  |  |   Method      |  |     |  +--------------------------+  |  |
|  |  |   Stack       |  |     |                                |  |
|  |  +---------------+  |     +--------------------------------+  |
|  |                     |                                          |
|  +---------------------+                                          |
+-------------------------------------------------------------------+

Java虚拟机栈(Java Stack) 是线程私有的,它的生命周期与线程相同。每个方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法从调用到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

注意:这里说的“栈”通常指的是虚拟机栈,或者说是虚拟机栈中局部变量表部分。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)。

本地方法栈(Native Method Stack) 与虚拟机栈作用相似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

堆(Heap) 是Java世界中最“大”的一块内存,也是垃圾收集器管理的主要区域。几乎所有的对象实例和数组都在这里分配内存。堆是线程共享的,因此在并发编程中需要特别注意线程安全问题。

方法区(Method Area) 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然Java虚拟机规范把它描述为堆的一个逻辑部分,但它有个别名叫做“非堆”(Non-Heap),目的是与Java堆区分开来。

运行时常量池(Runtime Constant Pool) 是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

1.2 对象的内存布局与访问定位

理解对象在堆中是如何存储的,能帮你更好地理解垃圾回收、内存对齐等概念。一个对象在堆内存中的存储布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

区域内容说明
对象头Mark Word存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID等。这部分在32位和64位虚拟机中长度分别为32bit和64bit。
类型指针即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据字段内容对象真正存储的有效信息,即程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录。
对齐填充无不是必然存在,仅起占位符作用。HotSpot VM要求对象起始地址必须是8字节的整数倍,因此当对象实例数据部分没有对齐时,需要通过对齐填充来补全。

对象创建后,如何访问它?主流访问方式有两种:使用句柄和直接指针。

  • 句柄访问:Java堆中划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。
  • 直接指针访问:reference中存储的直接就是对象地址,对象内存中需要放置访问类型数据的相关信息。

HotSpot主要使用直接指针方式,因为这种方式速度更快,节省了一次指针定位的时间开销。

1.3 垃圾回收机制:不只是“标记-清除”

谈到JVM,垃圾回收(GC)是必问话题。但别只停留在“标记-清除”、“复制”、“标记-整理”这几个名词上。面试官更想知道你如何根据应用特点选择GC策略,以及如何分析和解决实际中的GC问题。

首先,要理解“垃圾”的判断依据——可达性分析算法。这个算法的基本思路是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,则证明此对象不可能再被使用。

哪些对象可以作为GC Roots呢?

  • 在虚拟机栈(栈帧中的局部变量表)中引用的对象。
  • 在方法区中类静态属性引用的对象。
  • 在方法区中常量引用的对象。
  • 在本地方法栈中JNI引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象、常驻的异常对象等。
  • 所有被同步锁持有的对象。
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

现代的垃圾收集器基本都是分代收集的,所以我们需要了解不同分代的特点和收集策略:

// 一个简单的示例,展示不同生命周期的对象
public class GenerationExample {
    private static final List<byte[]> STATIC_LIST = new ArrayList<>(); // 常驻方法区,被静态变量引用
    
    public void method() {
        byte[] localArray = new byte[1024 * 1024]; // 在Eden区分配,方法结束后可能被回收
        STATIC_LIST.add(localArray); // 对象被静态变量引用,晋升到老年代
        // 方法结束,localArray引用消失,但对象仍在STATIC_LIST中
    }
    
    public static void main(String[] args) {
        GenerationExample example = new GenerationExample(); // 对象在堆中分配
        for (int i = 0; i < 100; i++) {
            example.method();
            System.gc(); // 建议GC,观察对象晋升
        }
    }
}

常见的垃圾收集器组合与适用场景:

收集器组合新生代算法老年代算法特点适用场景
Serial + Serial Old复制标记-整理单线程,STW时间长客户端模式,几百MB堆内存
ParNew + CMS复制标记-清除多线程并行,低停顿互联网B/S架构服务端
Parallel Scavenge + Parallel Old复制标记-整理吞吐量优先后台计算,不太关注响应时间
G1整体标记-整理,局部复制整体标记-整理,局部复制可预测停顿,区域化大堆内存,追求低延迟
ZGC染色指针,读屏障染色指针,读屏障亚毫秒级停顿超大堆内存,极致低延迟

在实际工作中,我遇到过一个典型的GC问题:一个后台服务在每天凌晨定时任务运行时,会出现长时间的Full GC,导致服务不可用。通过分析GC日志,发现是老年代空间被大量“朝生夕死”的大对象占满。根本原因是代码中在循环内不断创建大数组进行处理,且没有及时释放引用。解决方案是改用对象池复用大数组,并将处理逻辑改为流式处理,避免一次性加载全部数据。

2. 集合框架:不只是会用,更要懂其所以然

Java集合框架是日常开发中使用最频繁的API之一,但很多人只停留在“ArrayList查询快、LinkedList增删快”的表面认知。面试中,你需要展示的是对其底层数据结构和设计哲学的深刻理解。

2.1 HashMap:从源码看扩容与哈希冲突解决

HashMap几乎是面试必考题,但别只背“数组+链表+红黑树”。要能说清楚为什么是这种结构以及它是如何演进的。

HashMap在JDK 1.8中的核心结构如下:

// 简化版HashMap核心结构示意
public class HashMap<K,V> {
    // 哈希表数组
    transient Node<K,V>[] table;
    
    // 链表节点
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next; // 链表下一个节点
    }
    
    // 红黑树节点(当链表长度>=8且数组长度>=64时转换)
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // 父节点
        TreeNode<K,V> left;    // 左子节点
        TreeNode<K,V> right;   // 右子节点
        TreeNode<K,V> prev;    // 前驱节点(保持插入顺序)
        boolean red;           // 红色节点标识
    }
}

扩容机制是HashMap的核心之一。当元素数量超过阈值(容量×负载因子,默认0.75)时,HashMap会进行扩容,容量变为原来的2倍。扩容后,所有元素需要重新计算索引位置(rehash)。这个过程在JDK 1.8中做了优化:

// JDK 1.8中的resize方法关键逻辑(简化)
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 新容量 = 旧容量 * 2
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // 新阈值 = 旧阈值 * 2
    }
    
    // 创建新数组
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    
    if (oldTab != null) {
        // 遍历旧数组,重新哈希
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    // 只有一个节点,直接计算新位置
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    // 红黑树处理
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else {
                    // 链表处理:JDK 1.8优化,不需要重新计算hash
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        // 关键优化:利用(e.hash & oldCap)判断位置
                        if ((e.hash & oldCap) == 0) {
                            // 位置不变
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        } else {
                            // 位置变化:原索引 + oldCap
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

这个优化很精妙:通过(e.hash & oldCap) == 0判断,可以将链表中的节点分为两类,一类保持原索引位置,另一类移动到原索引+oldCap位置。这样不需要重新计算每个节点的hash值,大大提高了扩容效率。

哈希冲突解决方面,HashMap采用链地址法。当链表长度达到8且数组长度达到64时,链表会转换为红黑树;当树节点数减少到6时,红黑树会退化为链表。这个阈值的选择是基于统计学上的泊松分布,在负载因子0.75的情况下,链表长度达到8的概率极低(约0.00000006)。

2.2 ConcurrentHashMap的并发安全实现

在并发环境下,HashMap不是线程安全的,而Hashtable虽然线程安全但性能低下(所有方法都用synchronized修饰)。ConcurrentHashMap在JDK 1.7和1.8中有不同的实现方式:

JDK 1.7中的Segment分段锁:

  • 将整个哈希表分成多个Segment(默认为16个)
  • 每个Segment独立加锁,不同Segment的读写操作可以并发进行
  • 相当于降低了锁的粒度,提高了并发度

JDK 1.8中的优化:

  • 取消了Segment分段锁,采用Node + synchronized + CAS的方式
  • 锁的粒度更细,只锁住链表或红黑树的头节点
  • 使用volatile保证可见性,使用CAS实现无锁化操作
// JDK 1.8 ConcurrentHashMap的putVal方法关键逻辑(简化)
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable(); // 初始化表
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 使用CAS尝试插入新节点
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f); // 协助扩容
        else {
            V oldVal = null;
            synchronized (f) { // 只锁住链表或树的头节点
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) { // 链表
                        // ... 链表插入逻辑
                    }
                    else if (f instanceof TreeBin) { // 红黑树
                        // ... 树插入逻辑
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i); // 转换为红黑树
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

这种设计使得ConcurrentHashMap在保证线程安全的同时,获得了接近HashMap的性能。在实际高并发场景中,我推荐使用ConcurrentHashMap而不是Collections.synchronizedMap()包装的HashMap,因为前者有更好的并发性能。

2.3 ArrayList与LinkedList的真实性能对比

很多人背下了“ArrayList查询快、增删慢,LinkedList增删快、查询慢”的口诀,但实际情况要复杂得多。让我们通过实际测试来验证:

public class ListPerformanceTest {
    private static final int SIZE = 100000;
    
    public static void main(String[] args) {
        // 测试随机访问
        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();
        
        // 填充数据
        for (int i = 0; i < SIZE; i++) {
            arrayList.add(i);
            linkedList.add(i);
        }
        
        // 随机访问测试
        long start = System.nanoTime();
        for (int i = 0; i < 10000; i++) {
            int index = (int) (Math.random() * SIZE);
            arrayList.get(index);
        }
        long arrayTime = System.nanoTime() - start;
        
        start = System.nanoTime();
        for (int i = 0; i < 10000; i++) {
            int index = (int) (Math.random() * SIZE);
            linkedList.get(index);
        }
        long linkedTime = System.nanoTime() - start;
        
        System.out.println("随机访问测试:");
        System.out.println("ArrayList: " + arrayTime / 1000000.0 + " ms");
        System.out.println("LinkedList: " + linkedTime / 1000000.0 + " ms");
        
        // 头部插入测试
        arrayList = new ArrayList<>();
        linkedList = new LinkedList<>();
        
        start = System.nanoTime();
        for (int i = 0; i < 10000; i++) {
            arrayList.add(0, i); // 在头部插入
        }
        arrayTime = System.nanoTime() - start;
        
        start = System.nanoTime();
        for (int i = 0; i < 10000; i++) {
            linkedList.addFirst(i); // 在头部插入
        }
        linkedTime = System.nanoTime() - start;
        
        System.out.println("\n头部插入测试:");
        System.out.println("ArrayList: " + arrayTime / 1000000.0 + " ms");
        System.out.println("LinkedList: " + linkedTime / 1000000.0 + " ms");
    }
}

运行结果通常会显示:

  • 随机访问:ArrayList比LinkedList快几个数量级
  • 头部插入:LinkedList比ArrayList快,但差距没有想象中那么大(特别是对于小规模数据)

这是因为:

  1. ArrayList基于数组,支持O(1)的随机访问,但插入删除需要移动元素
  2. LinkedList基于双向链表,插入删除只需要修改指针,但随机访问需要遍历

但要注意:LinkedList的每个元素都需要额外的空间存储前后指针(每个指针在64位JVM中占8字节),内存开销更大。在实际项目中,除非有大量的头部插入删除操作,否则通常优先使用ArrayList。

3. 并发编程:从synchronized到AQS的深度探索

Java并发是面试中的难点和重点,也是区分初级和高级开发者的关键。这里我们不只讲用法,更要讲原理和最佳实践。

3.1 synchronized的锁升级过程

很多人知道synchronized是重量级锁,但不知道它有一个锁升级的过程。从JDK 1.6开始,synchronized引入了偏向锁、轻量级锁等优化,大大提升了性能。

synchronized的锁状态存储在对象头的Mark Word中,升级过程如下:

无锁状态 (001)
      |
      | 线程第一次访问
      ↓
偏向锁状态 (101) -- 线程ID记录在Mark Word
      |
      | 其他线程尝试获取锁
      ↓
轻量级锁状态 (00) -- 线程栈中创建Lock Record,CAS竞争
      |
      | 自旋超过一定次数或等待线程数>1
      ↓
重量级锁状态 (10) -- 指向Monitor对象的指针

这个升级过程是不可逆的。理解这个过程有助于我们写出更高效的并发代码。比如,对于竞争不激烈的短时同步,使用synchronized的性能可能比ReentrantLock更好,因为JVM会自动进行锁优化。

3.2 AQS:并发工具类的基石

AbstractQueuedSynchronizer(AQS)是Java并发包中很多同步器的基础,如ReentrantLock、CountDownLatch、Semaphore等。理解AQS,就理解了Java并发工具类的核心。

AQS的核心是一个FIFO队列和一个状态变量state。它通过CAS操作来保证状态变更的原子性。我们以ReentrantLock为例,看看AQS是如何工作的:

// 简化版ReentrantLock.NonfairSync实现
static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1)) // 尝试直接获取锁
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1); // 进入AQS队列
    }
    
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

// AQS的acquire方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 尝试获取锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并等待
        selfInterrupt();
}

// 节点加入等待队列
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) { // CAS设置尾节点
            pred.next = node;
            return node;
        }
    }
    enq(node); // 队列为空或CAS失败,进入enq方法
    return node;
}

AQS的等待队列是一个CLH队列的变体,每个节点代表一个等待线程。当线程获取锁失败时,会被包装成Node加入队列尾部,然后通过LockSupport.park()挂起。当锁释放时,会唤醒队列中的下一个节点。

公平锁与非公平锁的区别就在tryAcquire的实现上:

  • 公平锁:检查队列中是否有等待时间更长的线程
  • 非公平锁:直接尝试获取锁,不管队列中是否有等待线程

在实际开发中,除非有严格的公平性要求,否则通常使用非公平锁,因为它的吞吐量更高。

3.3 线程池的核心参数与工作流程

线程池是并发编程中最常用的工具之一,但很多人只是机械地使用Executors工厂方法,对其核心参数和工作原理一知半解。

ThreadPoolExecutor的7个核心参数:

  1. corePoolSize:核心线程数,即使空闲也会保留的线程数量
  2. maximumPoolSize:最大线程数,允许创建的最大线程数
  3. keepAliveTime:空闲线程存活时间(当线程数>corePoolSize时生效)
  4. unit:keepAliveTime的时间单位
  5. workQueue:工作队列,用于存放等待执行的任务
  6. threadFactory:线程工厂,用于创建新线程
  7. handler:拒绝策略,当队列和线程池都满时的处理策略

线程池的工作流程可以用下面的流程图表示:

新任务提交
    ↓
当前线程数 < corePoolSize?
    ├── 是 → 创建新线程执行任务
    ↓
    └── 否 → 工作队列未满?
        ├── 是 → 将任务加入队列等待
        ↓
        └── 否 → 当前线程数 < maximumPoolSize?
            ├── 是 → 创建新线程执行任务
            ↓
            └── 否 → 执行拒绝策略

拒绝策略有四种:

  • AbortPolicy(默认):直接抛出RejectedExecutionException
  • CallerRunsPolicy:用调用者所在线程来执行任务
  • DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新尝试执行
  • DiscardPolicy:直接丢弃任务,不抛异常

在实际项目中,我推荐自定义线程池而不是使用Executors的工厂方法,因为后者可能隐藏一些问题:

  • newFixedThreadPool和newSingleThreadExecutor:使用无界队列,可能堆积大量请求导致OOM
  • newCachedThreadPool和newScheduledThreadPool:最大线程数为Integer.MAX_VALUE,可能创建大量线程导致OOM
// 推荐的自定义线程池配置
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, // corePoolSize
    10, // maximumPoolSize
    60L, TimeUnit.SECONDS, // keepAliveTime
    new ArrayBlockingQueue<>(100), // 有界队列
    new ThreadFactoryBuilder().setNameFormat("custom-pool-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

3.4 volatile与happens-before原则

volatile关键字保证可见性和禁止指令重排序,但不保证原子性。这是很多人的误区。理解volatile需要从JMM(Java内存模型)的角度出发。

JMM定义了8个happens-before规则,这些规则是判断数据是否存在竞争、线程是否安全的主要依据:

  1. 程序次序规则:一个线程内,按照代码顺序,前面的操作先行发生于后面的操作
  2. 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  3. volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
  4. 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
  5. 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  7. 对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始
  8. 传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C

volatile的实现原理是在写操作后加入一条store屏障指令,在读操作前加入一条load屏障指令。这保证了:

  • 写volatile变量时,会将工作内存中的共享变量值刷新到主内存
  • 读volatile变量时,会从主内存中读取共享变量

但volatile不能保证复合操作的原子性,比如count++。这时需要使用synchronized或Atomic类。

4. IO与NIO:从BIO到Netty的演进之路

Java的IO模型经历了从BIO到NIO再到AIO的演进,理解这个演进过程对于构建高性能网络应用至关重要。

4.1 传统BIO模型的瓶颈

传统的BIO(Blocking IO)模型使用“一个连接一个线程”的方式,伪代码如下:

// 传统BIO服务器示例
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
    Socket socket = serverSocket.accept(); // 阻塞等待连接
    new Thread(() -> {
        try {
            InputStream input = socket.getInputStream();
            OutputStream output = socket.getOutputStream();
            // 处理请求
            byte[] buffer = new byte[1024];
            int len;
            while ((len = input.read(buffer)) != -1) { // 阻塞读取
                // 处理数据
                output.write("Response".getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();
}

这种模型的缺点很明显:

  • 每个连接都需要一个线程,线程创建和切换开销大
  • 线程大部分时间在等待IO,CPU利用率低
  • 连接数受限于线程数,无法支持高并发

4.2 NIO的非阻塞与多路复用

NIO(New IO)引入了Channel、Buffer、Selector三个核心概念,实现了非阻塞IO和多路复用:

// NIO服务器示例
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 非阻塞模式
serverChannel.bind(new InetSocketAddress(8080));

Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 注册接受事件

while (true) {
    selector.select(); // 阻塞直到有事件发生
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectedKeys.iterator();
    
    while (iter.hasNext()) {
        SelectionKey key = iter.next();
        iter.remove();
        
        if (key.isAcceptable()) {
            // 接受连接
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            // 读取数据
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = client.read(buffer);
            if (len > 0) {
                buffer.flip();
                // 处理数据
                client.write(ByteBuffer.wrap("Response".getBytes()));
            } else if (len == -1) {
                client.close();
            }
        }
    }
}

NIO的核心优势在于:

  • 非阻塞:线程不会在IO操作上阻塞
  • 多路复用:一个线程可以处理多个连接
  • 缓冲区:直接内存操作,减少数据拷贝

但NIO编程模型复杂,需要处理各种边界条件和异常情况,这也是Netty等框架流行的原因。

4.3 Netty的线程模型与核心组件

Netty是基于NIO的客户端-服务器框架,它简化了网络编程,并提供了高性能、高可靠性的网络通信能力。Netty的核心组件包括:

  1. Channel:网络操作的抽象,代表一个到实体的开放连接
  2. EventLoop:处理所有IO操作的核心抽象,一个EventLoop可以处理多个Channel
  3. ChannelPipeline:包含一系列ChannelHandler的拦截过滤器
  4. ChannelHandler:处理入站和出站事件

Netty的线程模型采用了Reactor模式的变体,通常使用主从多线程模型:

主Reactor线程 (1个)
    ↓ (接受连接)
从Reactor线程池 (N个)
    ↓ (处理IO)
Worker线程池 (M个,处理业务逻辑)

这种设计的好处是:

  • 接受连接与处理IO分离,提高吞吐量
  • IO操作与业务处理分离,避免IO阻塞业务
  • 灵活的线程池配置,根据业务特点调整

在实际项目中,使用Netty可以大大简化网络编程。比如构建一个简单的HTTP服务器:

public class HttpServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接受连接
        EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理IO
        
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new HttpServerCodec()); // HTTP编解码
                     p.addLast(new HttpObjectAggregator(65536)); // 聚合HTTP消息
                     p.addLast(new HttpServerHandler()); // 业务处理器
                 }
             });
            
            ChannelFuture f = b.bind(8080).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

// 业务处理器
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) {
        // 处理HTTP请求
        FullHttpResponse response = new DefaultFullHttpResponse(
            HttpVersion.HTTP_1_1, 
            HttpResponseStatus.OK,
            Unpooled.copiedBuffer("Hello, World!", CharsetUtil.UTF_8)
        );
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        
        ctx.writeAndFlush(response);
    }
}

Netty的强大之处在于其高度可定制的Pipeline和丰富的编解码器,可以轻松支持各种协议(HTTP、WebSocket、自定义协议等)。

5. JVM性能调优实战:从理论到实践

掌握了JVM理论知识后,如何应用到实际项目中?这里分享几个我在实际工作中遇到的性能问题及解决方案。

5.1 内存泄漏排查实战

内存泄漏是Java应用中常见的问题。有一次,我们的一个后台服务在运行几天后就会发生OOM。通过以下步骤,我们定位并解决了问题:

第一步:确认问题

# 查看GC日志,发现Full GC频繁且每次回收后老年代占用率仍在上升
java -Xmx2g -Xms2g -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar

第二步:生成堆转储文件

# 在OOM时自动生成堆转储
java -Xmx2g -Xms2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -jar app.jar

第三步:使用MAT分析堆转储

  1. 下载并打开MAT(Memory Analyzer Tool)
  2. 加载heapdump.hprof文件
  3. 查看Dominator Tree,发现某个自定义缓存类占用了80%的内存
  4. 查看GC Roots到该对象的引用链,发现是静态Map没有及时清理

第四步:修复代码

// 问题代码:使用静态Map做缓存,但没有清理机制
public class ProblemCache {
    private static final Map<String, Object> CACHE = new HashMap<>();
    
    public static void put(String key, Object value) {
        CACHE.put(key, value);
    }
    
    public static Object get(String key) {
        return CACHE.get(key);
    }
    // 没有remove方法,对象永远不会被回收
}

// 修复方案1:使用WeakHashMap(弱引用)
public class FixedCache1 {
    private static final Map<String, Object> CACHE = new WeakHashMap<>();
    // 当key不再被强引用时,Entry会被自动回收
}

// 修复方案2:使用LRU缓存(推荐)
public class FixedCache2 {
    private static final int MAX_SIZE = 1000;
    private static final Map<String, Object> CACHE = new LinkedHashMap<String, Object>(
        MAX_SIZE, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
            return size() > MAX_SIZE; // 超过容量时移除最旧的条目
        }
    };
    
    public static synchronized void put(String key, Object value) {
        CACHE.put(key, value);
    }
    
    public static synchronized Object get(String key) {
        return CACHE.get(key);
    }
}

第五步:验证修复效果 修复后,服务可以稳定运行,老年代内存使用率保持平稳,不再有OOM发生。

5.2 GC调优实战

另一个常见问题是GC停顿时间过长。我们的一个实时数据处理应用,要求每次GC停顿不超过100ms,但实际运行中偶尔会出现200ms以上的停顿。

分析过程:

  1. 使用jstat -gcutil <pid> 1000观察GC情况,发现Young GC频繁但时间短,Full GC偶尔发生但停顿时间长
  2. 分析GC日志,发现老年代使用率在Full GC前达到98%
  3. 检查代码,发现有大对象直接进入老年代(通过-XX:PretenureSizeThreshold设置)

解决方案:

# 调整JVM参数
java -Xmx4g -Xms4g \
     -Xmn2g \  # 增大新生代,减少对象晋升到老年代
     -XX:SurvivorRatio=8 \  # Eden:Survivor=8:1:1
     -XX:MaxTenuringThreshold=5 \  # 对象在新生代存活次数
     -XX:+UseG1GC \  # 改用G1收集器,追求低停顿
     -XX:MaxGCPauseMillis=100 \  # 目标最大停顿时间
     -XX:+PrintGCDetails \
     -XX:+PrintGCDateStamps \
     -Xloggc:gc.log \
     -jar app.jar

代码层面优化:

// 优化前:创建大数组
public byte[] processData(List<Data> dataList) {
    byte[] result = new byte[dataList.size() * 1024]; // 可能很大
    // ... 处理逻辑
    return result;
}

// 优化后:使用流式处理或分块处理
public void processDataStream(List<Data> dataList, OutputStream output) {
    for (Data data : dataList) {
        byte[] chunk = processChunk(data); // 每次处理一小块
        output.write(chunk);
    }
}

// 或者使用对象池复用大对象
private static final ObjectPool<byte[]> BUFFER_POOL = new ObjectPool<>(
    () -> new byte[1024 * 1024], // 创建1MB缓冲区
    10 // 池大小
);

public byte[] processWithPool(List<Data> dataList) {
    byte[] buffer = BUFFER_POOL.borrowObject();
    try {
        // 使用buffer处理数据
        return processWithBuffer(dataList, buffer);
    } finally {
        BUFFER_POOL.returnObject(buffer); // 归还到池中
    }
}

5.3 线程池调优实战

线程池配置不当也会导致性能问题。我们有一个批处理任务,使用固定大小的线程池,但任务执行时间差异很大,导致某些线程空闲而某些任务等待。

问题现象:

  • CPU利用率不均衡
  • 任务执行时间差异大(从几毫秒到几分钟)
  • 长任务阻塞短任务

解决方案:使用合适的线程池和工作队列

// 根据任务特点选择合适的线程池
public class TaskExecutorFactory {
    
    // CPU密集型任务:线程数 ≈ CPU核心数
    public static ExecutorService createCpuIntensiveExecutor() {
        int corePoolSize = Runtime.getRuntime().availableProcessors();
        return new ThreadPoolExecutor(
            corePoolSize,
            corePoolSize * 2,
            60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            new ThreadFactoryBuilder().setNameFormat("cpu-pool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
    
    // IO密集型任务:线程数可以更多
    public static ExecutorService createIoIntensiveExecutor() {
        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
        return new ThreadPoolExecutor(
            corePoolSize,
            corePoolSize * 4,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<>(), // 不缓存任务,直接创建新线程
            new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
    
    // 混合型任务:使用优先级队列
    public static ExecutorService createMixedTaskExecutor() {
        return new ThreadPoolExecutor(
            5, 10, 60L, TimeUnit.SECONDS,
            new PriorityBlockingQueue<>(1000, Comparator.comparing(Task::getPriority)),
            new ThreadFactoryBuilder().setNameFormat("mixed-pool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
}

// 任务类,支持优先级
class Task implements Runnable, Comparable<Task> {
    private final int priority; // 优先级,值越小优先级越高
    private final Runnable runnable;
    
    public Task(int priority, Runnable runnable) {
        this.priority = priority;
        this.runnable = runnable;
    }
    
    public int getPriority() {
        return priority;
    }
    
    @Override
    public void run() {
        runnable.run();
    }
    
    @Override
    public int compareTo(Task other) {
        return Integer.compare(this.priority, other.priority);
    }
}

监控线程池状态:

public class ThreadPoolMonitor {
    private final ThreadPoolExecutor executor;
    private final ScheduledExecutorService monitor;
    
    public ThreadPoolMonitor(ThreadPoolExecutor executor) {
        this.executor = executor;
        this.monitor = Executors.newSingleThreadScheduledExecutor();
        
        // 每5秒监控一次
        monitor.scheduleAtFixedRate(this::report, 0, 5, TimeUnit.SECONDS);
    }
    
    private void report() {
        System.out.println("=== ThreadPool Status ===");
        System.out.println("Core Pool Size: " + executor.getCorePoolSize());
        System.out.println("Current Pool Size: " + executor.getPoolSize());
        System.out.println("Active Threads: " + executor.getActiveCount());
        System.out.println("Task Count: " + executor.getTaskCount());
        System.out.println("Completed Task Count: " + executor.getCompletedTaskCount());
        System.out.println("Queue Size: " + executor.getQueue().size());
        System.out.println("========================\n");
        
        // 动态调整(根据负载)
        if (executor.getQueue().size() > 1000 && 
            executor.getPoolSize() < executor.getMaximumPoolSize()) {
            // 队列积压严重,增加核心线程数
            executor.setCorePoolSize(executor.getCorePoolSize() + 1);
        }
    }
}

通过这些实战案例,我们可以看到,JVM调优不是简单的参数调整,而是需要结合代码逻辑、业务特点和监控数据,进行系统性的分析和优化。每个应用都有其独特性,没有一套放之四海而皆准的最优配置,关键在于理解原理,然后根据实际情况进行调整。

java面试题 Java 软件工程师面试资料大整合 1 Java 面霸 1 1. int 和 Integer 有什么区别? 8 2. String 和StringBuffer的区别 8 3. 运行时异常与一般异常有何异同? 8 4. 说出ArrayList,Vector,LinkedList的存储性能和特性 8 5. EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。 9 6. Collection 和 Collections的区别。 9 7. &和&&的区别。 9 8. HashMap和Hashtable的区别。 10 9. final, finally, finalize的区别。 10 10. sleep() 和 wait() 有什么区别? 10 11. Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型? 10 12. error和exception有什么区别? 11 13. 同步和异步有何异同,在什么情况下分别使用他们?举例说明。 11 14. 简述synchronized和java.util.concurrent.locks.Lock的异同 ? 11 15. 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 11 16. abstract class和interface有什么区别? 12 17. abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized? 12 18. 接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? 12 19. heap和stack有什么区别。 13 20. forward 和redirect的区别 13 21. EJB与JAVA BEAN的区别? 13 22. Static Nested Class 和 Inner Class的不同。 13 23. JSP中动态INCLUDE与静态INCLUDE的区别? 14 24. List, Set, Map区别 14 25. 集合类都有哪些?主要方法? 14 26. 简述逻辑操作(&,|,^)与条件操作(&&,||)的区别。 14 27. XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式? 14 28. JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么? 15 29. Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)? 15 30. 构造器Constructor是否可被override 15 31. try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后? 16 32. 应用服务器与WEB SERVER的区别? 16 33. BS与CS的联系与区别。 16 34. 启动一个线程是用run()还是start()? 17 35. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 18 36. swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上? 18 37. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 18 38. 比较truncate和delete 命令 18 39. 解释$ORACLE_HOME 和$ORACLE_BASE的区别? 19 40. session与cookie的区别和联系? 19 41. Statement和PrepareStatement的区别 19 42. JSP的内置对象及方法。 19 43. JSP的常用指令 20 44. 四种会话跟踪技术 20 45. Request对象的主要方法: 21 46. jsp有哪些动作?作用分别是什么? 21 47. 两种跳转方式分别是什么?有什么区别? 22 48. get和post的区别? 22 49. JDK,JRE,JVM的区别? 22 50. Java中常见类,方法,接口 23 51. 多线程 23 51.1. 线程的基本概念 23 51.2. Java中的线程有四种状态 23 51.3. 多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 24 51.4. 线程同步的方法。 24 51.5. java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用? 25 52. 数据连接池 25 52.1. 连接池的基本原理: 25 52.2. 连接池的工作机制 25 52.3. 建立连接池 26 52.4. 连接池内连接的使用与释放 26 52.5. 配置连接池 26 52.6. 配置tomcat 6.0.10连接池 26 52.7. Hibernate实现数据库的连接不同方式: 28 52.8. 有几种开源的数据库连接池: 29 53. 描述一下JVM加载class文件的原理机制? 30 54. socket编程 30 54.1. 什么是TCP/IP、UDP? 30 54.2. Socket在哪里呢? 31 54.3. Socket是什么呢? 32 54.4. socket的实现步骤 37 55. Servlet 38 55.1. Servlet工作流程 38 55.2. servlet的生命周期 38 55.3. Servlet执行时一般实现哪几个方法? 38 56. 会话跟踪 39 56.1. Cookie 39 56.2. session 39 56.2.1. Session 生命周期 39 57. EJB的几种类型 39 58. 排序都有哪几种方法?请列举。用JAVA实现一个快速排序。 40 59. 请对以下在J2EE中常用的名词进行解释(或简单描述) 40 59.1. web 容器 40 59.2. EJB容器 40 59.3. JNDI 40 59.4. JMS 41 59.5. JTA 41 59.6. JAF 41 59.7. RMI/IIOP 41 60. JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗? 41 61. MVC的各个部分都有那些技术来实现?如何实现? 42 62. java中实现多态的机制是什么? 42 63. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收? 42 63.1. 判断该对象是否是时候可以收集方法 43 63.1.1. 引用计数 43 63.1.2. 对象引用遍历 43 63.2. 几种垃圾回收机制 43 63.2.1. 标记-清除收集器 43 63.2.2. 标记-压缩收集器 43 63.2.3. 复制收集器 44 63.2.4. 增量收集器 44 63.2.5. 分代收集器 44 63.2.6. 并发收集器 44 63.2.7. 并行收集器 44 63.3. Sun HotSpot 1.4.1 JVM堆大小的调整 44 63.4. BEA JRockit JVM的使用 45 63.4.1. Bea JRockit JVM支持4种垃圾收集器: 46 63.5. 如何从JVM中获取信息来进行调整 46 63.6. Pdm系统JVM调整 47 63.6.1. 服务器:前提内存1G 单CPU 47 63.6.2. 客户机:通过在JNLP文件中设置参数来调整客户端JVM 47 64. 什么时候用assert。 47 65. 什么是java序列化,如何实现java序列化? 48 65.1. java序列化、反序列化 48 65.2. 对象的序列化主要有两种用途: 48 65.3. 对象序列化包括如下步骤: 49 65.4. 对象反序列化的步骤如下: 49 66. 反射机制 49 66.1.1. 传统的类型转换。 49 66.1.2. 通过Class对象来获取对象的类型。 49 66.1.3. 通过关键字instanceof或Class.isInstance()方法 49 67. 说出一些常用的类,包,接口,请各举5个 50 68. XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式? 51 69. jdbc 51 69.1. 简述 51 69.2. JDBC调用数据库的基本步骤 52 69.3. JDBC URL 52 70. MVC设计模式 53 71. Hibernate 54 71.1. Hibernate 介绍 54 71.2. Hibernate 实现原理 55 71.3. Hibernate 优点 56 71.4. Hibernate 的缓存体系 56 71.4.1. 一级缓存: 56 71.4.2. 二级缓存: 56 71.4.3. 缓存管理 56 71.5. Hibernate 中Java对象的状态 58 71.5.1. 临时状态 (transient) 58 71.5.2. 持久化状态(persisted) 58 71.5.3. 游离状态(detached) 58 71.5.4. hibernate的三种状态之间如何转换 59 71.6. Hibernate并发机制,并发问题的处理。 59 71.6.1. Hibernate并发机制 59 71.6.2. 并发问题解决方案 59 71.7. Hibernate是如何延迟加载? 60 71.8. Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 60 71.9. 说下Hibernate的缓存机制 60 71.10. Hibernate的查询方式 60 71.11. 如何优化Hibernate? 61 71.12. Hibernate和spring中常出现的几个异常 61 71.13. Hibernate与jdbc的联系 62 71.14. Hibernate与Spring的联系 62 71.15. Hibernate自带的分页机制是什么?如果不使用Hibernate自带的分页,则采用什么方式分页? 62 71.16. hibernate中一对多配置文件返回的是什么? 63 71.17. hibernate拒绝连接、服务器崩溃的原因?最少写5个 63 71.18. Hibernate主键介绍 63 71.18.1. Assigned 63 71.18.2. Hilo 63 71.18.3. Increment 64 71.18.4. Identity 64 71.18.5. Sequence 64 71.18.6. Native 64 71.18.7. UUID 64 71.18.8. Foreign GUID 65 71.19. Hibernate源码中几个包的作用简要介绍 65 72. struts 66 72.1. struts 简介 66 72.2. STRUTS的应用(如STRUTS架构) 66 72.3. 请写出Struts的工作原理、工作机制 67 72.4. struts的处理流程。 67 72.5. Struts 2框架的大致处理流程如下: 68 72.6. Struts体系结构中的组件 69 72.7. struts如何实现国际化 70 72.8. struts2.0的常用标签 71 72.9. action是单实例还是多实例,为什么? 73 72.10. Struts的validate框架是如何验证的? 74 72.11. dispatchAction是用什么技术实现的? 74 72.12. struts2.0的mvc模式?与struts1.0的区别? 74 72.13. struts1.2和struts2.0的区别?如何控制两种框架中的单例模式? 74 73. Spring 75 73.1. Spring 简介 75 73.2. 为什么要用Spring? 76 73.3. spring工作机制或工作原理 76 73.4. Spring是什么?根据你的理解详细谈谈你的见解。 76 73.5. 项目中如何体现Spring中的切面编程,具体说明。 77 73.6. 项目中用到的Spring中的切面编程最多的地方:声明式事务管理。 77 73.7. spring的事务如何配置 77 73.8. transaction有那几种实现(事务处理)(Spring) 79 73.9. Spring IoC 79 73.10. Spring AOP面向方面编程 82 74. 项目中为什么使用SSH 85 75. Spring在SSH中的作用 86 76. weblogic 86 76.1. 如何给weblogic指定大小的内存? 86 76.2. 如何设定的weblogic的热启动模式(开发模式)与产品发布模式? 86 76.3. 如何启动时不需输入用户名与密码? 86 76.4. 在weblogic管理制台中对一个应用域(或者说是一个网站,Domain)进行jms及ejb或连接池等相关信息进行配置后,实际保存在什么文件中? 86 76.5. 在weblogic中发布ejb需涉及到哪些配置文件 87 76.6. 如何在weblogic中进行ssl配置与客户端的认证配置或说说j2ee(标准)进行ssl的配置 87 76.7. 如何查看在weblogic中已经发布的EJB? 87 76.8. 说说在weblogic中开发消息Bean时的persistent与non-persisten的差别 87 77. tomcat 87 77.1. 解决端口冲突导致tomcat无法启动的问题 87 77.2. 修改java虚拟机内存 88 77.3. 修改tomcat连接数 88 77.4. 禁止列出目录下的文件 88 77.5. 设置session失效的时间 89 77.6. 设置MIME响应类型 89 77.7. 设置tomcat的默认访问页面 89 77.8. 设置tomcat管理用户 89 77.9. 附录 90 78. websphere 90 79. 常见异常 90 79.1. nullpointerexception 90 79.2. classnotfoundexception 90 79.3. arithmeticexception 90 79.4. arrayindexoutofboundsexception 91 79.5. illegalargumentexception 91 79.6. illegalaccessexception 91 80. 异常机制 97 81. 异常的分类 97 82. 异常的使用方法 98 83. JAVA代码查错 101 83.1. 判断 101 83.2. 判断 102 83.3. 判断 102 83.4. 判断 102 83.5. 判断 102 83.6. 判断 103 83.7. 判断 103 83.8. 判断 103 83.9. 判断 104 83.10. 判断 104 83.11. 判断 105 84. 编码 106 84.1. 写出一个单例模式 106 84.2. 我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串? 106 84.3. 设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。 107 84.4. 现在输入n个数字,以逗号”,”分开;然后可选择升或者降序排序;按提交键就在另一页面显示按什么排序,结果为,提供reset 108 84.5. 金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 109 84.6. 内部类的实现方式? 112 84.7. 编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。 113 84.8. 将一个键盘输入的数字转化成中文输出(例如:输入1234567,输出:一百二拾三万四千五百六拾七),请用java语言编一段程序实现! 114 84.9. 题目1:用1、2、2、3、4、5这六个数字,用java写一个main函数,打印出所有不同的排列,如:512234、412345等,要求:"4"不能在第三位,"3"与"5"不能相连. 117 84.10. 写一个方法,实现字符串的反转,如:输入abc,输出cba 119 84.11. 请用java写二叉树算法,实现添加数据形成二叉树功能,并以先序的方式打印出来. 119 84.12. 请写一个java程序实现线程连接池功能? 122 84.13. 编一段代码,实现在控制台输入一组数字后,排序后在控制台输出; 122 84.14. 列出某文件夹下的所有文件; 123 84.15. java调用系统命令实现删除文件的操作; 123 84.16. java实现从文件中一次读出一个字符的操作; 124 84.17. 列出一些控制流程的方法; 124 84.18. 编写了一个服务器端的程序实现在客户端输入字符然后在控制台上显示,直到输入"END"为止,让你写出客户端的程序; 124 84.19. 用jdom解析xml文件时如何解决中文问题?如何解析? 127 84.20. Jquery ajax 实现异步 129 立即下载

相关推荐

Java面试笔试题大汇总(最全+详细答案)

Java面试笔试题大汇总(最全+详细答案) Java面试笔试题大汇总(最全+详细答案)

Java最新面试题(全网最全、最细、附答案)

Java 语言具有多种显著特点,使其成为广泛应用的编程语言。首先,Java 是面向对象的,支持封装、继承和多态三大特性,代码以类和对象为组织单位。其次,Java 具有平台无关性,通过 Java 虚拟机(JVM)实现跨平台运行,编译后的字节码可在不同操作系统上执行。Java 是强类型语言,所有变量必须先声明类型,并在编译时进行严格类型检查。此外,Java 提供自动内存管理,通过垃圾回收(GC)机制减少内存泄漏风险,并内置多线程支持,通过 Thread 类或 Runnable 接口创建线程。

javaeEEse的博客 24万+

单细胞实战之亚细胞分群从T/NK至CD4+T细胞——从入门到进阶(中级篇1)

亚细胞分群从T/NK至CD4+T细胞

zfyyzhys的博客 6391

JAVA面试题大全(200+道题目)

面试题 一、Java 基础 1.JDK 和 JRE 有什么区别? 2.== 和 equals 的区别是什么? 3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗? 4.final在 java 中有什么作用? 5.java中的 Math.round(-1.5) 等于多少? 6.String 属于基础的数据类型吗? 7.java中操作字符串都有...

qq_38078190的博客 13万+

EPSON RX8010SJ RTC实战:I2C寄存器配置避坑指南(附完整初始化代码)

本文详细解析了EPSON RX8010SJ RTC芯片在I2C寄存器配置中的常见问题与解决方案,包括TEST位、STOP位和中断控制等关键配置要点。通过实战经验分享和完整的初始化代码模板,帮助开发者避免常见陷阱,确保RTC模块的稳定运行。

weixin_28678831的博客 407

脆弱的冗余:链路聚合(LACP)中单条链路故障引发的网络震荡

show etherchannel summary# 查看聚合组中所有端口的标志位,健康状态应为 ‘P’ (Port-channel) 或 ‘SU’(Stand-up)。本次故障的核心教训是:网络系统对“部分故障”的容忍度和自愈能力,远比应对“完全故障”要重要和复杂得多。3.加强监控:部署网管系统,监控聚合端口的包错误计数、LACP状态变化频率,并设置阈值告警。标签: #链路聚合 #LACP故障 #网络冗余 #排障案例 #网络高可用性。修复链路:更换光模块、清洁光纤接口、检查物理线路。

danyth的博客 1983

Java程序员必备的一些流程图(拿走不谢)

前言: 整理了一些Java基础流程图/架构图,做一下笔记,大家一起学习。 (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!) 1.spring的生命周期 Spring作为当前Java最流行、最强大的轻量级容器框架,了解熟悉spring的生命周期非常有必要; 首先容器启动后,对bean进行初始化 按照bean的定义,注入属性 检测该对象是否实现了xxx...

wanghao112956的博客 2万+

COLMAP性能优化实战:如何用GPU加速和并行计算让重建速度提升300%

本文详细介绍了如何通过GPU加速、并行计算和参数调优显著提升COLMAP的三维重建效率。从硬件资源分配到计算流程优化,再到大型数据集的分块处理策略,全面解析了将重建速度提升300%以上的实用技巧,特别适合处理无人机航拍和室内扫描等大规模三维重建项目。

weixin_29098391的博客 202

31道Java核心面试题,一次性打包送给你

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

2401_84023482的博客 1166

光伏电站发电功率预测实战:从基础物理模型到LSTM时间序列分析

本文系统介绍了光伏电站发电功率预测的实战方法,从基础的物理晴空模型到先进的LSTM时间序列分析。通过结合电工杯等竞赛中常用的代码与模型,详细阐述了如何利用历史数据和数值天气预报构建高精度预测体系,并针对阴雨天气等预测瓶颈提出了分场景建模与模型融合等优化策略,为电站运维和电网调度提供可靠的数据支持。

pp12345的博客 216

COMSOL求解器实战:如何避免非线性问题求解失败(附牛顿法调试技巧)

本文深入探讨了COMSOL求解器在处理非线性问题时常见的失败原因及解决方案。通过分析物理模型非线性特性、初始条件敏感性和求解器参数配置,提供了牛顿法调试的黄金四步法,包括模型检查、分阶段求解、参数调优和高级诊断工具应用。特别针对材料非线性、几何非线性和多物理场耦合等典型工程场景,给出了实战技巧和预防性建模策略,帮助工程师有效避免求解失败。

weixin_29284657的博客 420

Java虚拟机的组成

Java虚拟机主要分为以下五个区: 一、方法区: 1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载 2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。 3. 该区域是被线程共享的。 4. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池

俺叫赵小邪的博客 5908

2023年金蝶云星辰初级顾问认证参考试题(1)

最新积累的考试真题,答案只供参考(49分T~T),欢迎大佬们更正,私聊我修改,有新的试题也可以私聊我,日后会完善题库!大家一起努力,预祝大家顺利通过!1.【简单】生产环节中,客户老板希望掌握材料的超发或漏发情况,,以便分析用料情况,对材料浪费进行管控,可以查看什么报表?⒉[中等】客户主营电气柜的生产销售,商品为非标品需定制,又因交期效短,需要边生产边补料,以下哪个功能最适合该客户场景?15.【简单】在给商品设置科目关联时,分别按照单个商品,商品类别,所有商品设置了对应客户,()优先级最高。

MLX00的博客 7975

Java面试突击:46道高频基础题全解析(附30+手绘图解)

本文全面解析Java面试中的46道高频基础题,涵盖Java核心特性、JVM机制、集合框架、并发编程等关键知识点,并附有30+手绘图解帮助理解。内容深入浅出,适合准备Java面试的开发者系统复习,快速掌握面试必备的八股文知识。

ss78901的博客 916

STM32CubeIDE开发环境搭建与GPIO控制实战

嵌入式开发中,MCU开发环境构建是软硬件协同的起点,其核心在于工具链选型、外设抽象层级与调试可靠性。HAL库作为ST官方标准硬件抽象层,通过统一接口屏蔽芯片差异,显著提升工程可移植性;而STM32CubeIDE则以图形化配置+自动生成代码的方式,实现‘配置即代码’的确定性开发范式。该组合在教学验证、原型迭代和中小规模量产中具备高性价比技术价值,广泛应用于智能传感、工业HMI及IoT终端等场景。本文聚焦STM32CubeIDE 1.15.0环境部署、MyST账户与固件包管理、ST-LINK驱动校验,以及基于H

weixin_33531560的博客 81

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Java面试突击方案:高频考点驱动与场景题破局

在软件工程领域,面试准备是开发者职业发展中的关键环节,尤其对于Java技术栈而言。其核心原理在于通过结构化复习与针对性训练,将分散的知识点串联成体系化的解决方案。从技术价值看,这不仅关乎个人面试成功率,更是对知识掌握深度与应用能力的综合检验。典型的应用场景包括时间紧迫的求职冲刺、知识体系查漏补缺以及技术表达能力的强化训练。本文聚焦于Java面试的高效准备策略,通过高频考点驱动和场景题拆解框架,帮助开发者在有限时间内构建应对面试的核心能力,自然收敛到以实战输出为导向的复习方法论。

weixin_30888413的博客 343

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Java面试7天高效突击:核心考点与场景题应对策略

在Java开发领域,掌握核心知识体系是技术面试成功的关键。Java基础、并发编程、JVM、MySQL和Spring框架构成了企业级开发的基石,其原理与优化直接影响系统性能与稳定性。理解这些技术不仅有助于构建健壮的应用,更是应对高并发、大数据量场景的必备能力。例如,深入理解JVM内存模型与垃圾回收机制,能有效诊断线上OOM问题;精通MySQL索引与事务隔离级别,可大幅提升数据库查询效率与数据一致性。在实际面试中,面试官常通过场景题考察候选人将分散知识点串联解决实际问题的能力,如系统性能调优、高并发设计等。本文

weixin_33768481的博客 331

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Java面试突击:从背题到构建知识网络与实战表达

在技术面试中,理解核心概念的原理并关联实际应用是脱颖而出的关键。以Java并发编程为例,其核心在于解决多线程环境下的可见性、原子性与有序性问题,为此Java提供了synchronized、ReentrantLock、volatile及并发容器等机制。理解这些机制的原理,例如CAS(Compare-And-Swap)的乐观锁思想及其ABA问题,是基础。其技术价值在于能在高并发场景下,在安全性与性能之间取得平衡,从而支撑起秒杀、实时计算等高流量业务。线程池作为其中重要的工程实践组件,其参数配置与拒绝策略直接关系

culuo8053的博客 359

Java面试突击:2026下半年高频考点与场景题实战指南

并发编程是Java开发中的核心技术,通过线程池、锁机制和原子操作实现多任务高效协同。其原理基于JVM内存模型和CPU指令重排优化,确保数据可见性与有序性。在分布式系统中,并发控制能显著提升吞吐量,避免资源竞争导致的性能瓶颈。JVM作为Java程序的运行环境,负责内存分配与垃圾回收,合理的GC策略可减少STW时间,保障应用稳定性。结合MySQL索引优化与Spring框架的IOC容器,这些技术共同构建了高并发场景下的企业级解决方案。本文以面试高频考点为切入点,深入解析Java基础、JVM调优及秒杀系统设计等实战

weixin_34198881的博客 348

Java面试高效突击:5大核心模块高频考点与场景化实战攻略

在Java后端开发领域,掌握核心知识体系是应对技术面试的基础。其原理在于构建一个从语言特性、并发模型、虚拟机机制到数据库与主流框架的完整认知框架。这套体系的技术价值在于,它能帮助开发者系统性地理解应用运行的本质,从而高效定位和解决性能、稳定性等工程问题。典型的应用场景包括高并发系统设计、JVM性能调优、数据库优化以及微服务架构下的问题排查。本文聚焦于Java面试准备,针对时间有限的开发者,提供了一套以Java基础、并发编程、JVM、MySQL和Spring为核心的高频考点突击方案,并强调通过场景化记忆和实战

weixin_34095889的博客 327

Java面试突击指南:高效备战核心考点与实战验证

在软件开发领域,Java作为主流编程语言,其技术栈的深度和广度是面试考察的重点。理解Java虚拟机(JVM)的内存管理、垃圾回收机制以及并发编程的核心原理,是构建高性能、高可用系统的基石。这些底层知识不仅决定了程序的运行效率,也直接影响着系统的稳定性和扩展性。从技术价值看,掌握JVM调优和并发控制能有效解决线上OOM、死锁等常见生产问题,提升应用性能。在应用场景上,无论是互联网高并发业务,还是企业级复杂系统,扎实的Java基础、对Spring框架的深入理解以及对MySQL索引与事务的优化能力,都是工程师的核

weixin_34232744的博客 415

阿里P7面试实录:靠这份“收割机指南”,他当场拿下60k+ offer!

基础题不再直问“HashMap原理”,而是结合场景:“十万订单分页查询慢,如何优化?:当被问及Redis集群,主动延伸:“我们用Redisson分布式锁解决超卖,但需注意看门狗机制的线程泄漏风险”:那些觉得“再准备一周”的人,永远不知道昨天已有1000人靠这份指南上岸大厂。:基于MySQL Binlog+Kafka的数据同步,延迟要求<500ms。:Hystrix线程隔离的误用场景、JVM FullGC频繁的7个隐藏原因。:2025年最新美团、字节、蚂蚁金服真题,含解题技巧和陷阱提示。

2401_83384536的博客 952

Java面试突击:从知识点串联到场景化实战的10天高效攻略

在软件开发领域,数据结构与算法、系统设计、性能优化是构建高效稳定应用的核心基础。理解这些基础概念及其实现原理,例如HashMap的底层数据结构与扩容机制、ConcurrentHashMap的线程安全实现,对于提升代码质量和系统性能具有关键价值。这些技术广泛应用于高并发、大数据处理等场景,是后端工程师解决实际问题的必备工具箱。本文聚焦Java核心技术栈,通过构建“回答逻辑链”和“场景化记忆”的方法,将散落的知识点如JVM内存模型、MySQL索引与事务、Spring框架原理等进行有效串联,旨在帮助学习者在短时间

cihongmo6452的博客 365

【面试突击】深挖网络 IO 面试实战

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送! 在我后台回复 「资料」 可领取编程高频电子书! 在我后台回复「面试」可领取硬核面试笔记! 文章导读地址:点击查看文章导读! 感谢你的关注!

qq_45260619的博客 1264

几万页“Java 面试合集”号称大厂面试零门槛

小编就把这“六份 Java 面试大礼包”展示到这了,所有完整版文档加起来要四千页左右!这里只能以知识点+截图的方式供大家阅览!需要这份大礼包的朋友们,

gp0002222的博客 175

Java 后端面试复习没头绪?JavaGuide 一站式 Java 学习指南带你快速对齐考点

准备 Java 后端面试,最折磨人的往往不是"不会",而是知识点太散:Java 基础、并发、JVM、MySQL、Redis、分布式……每一块都能单独被问深,自己却摸不准哪些是高频考点、复习到什么程度才算够。JavaGuide 就是一份开源的 **Java 学习指南**,把后端面试常见的问题按主题整理成体系化的文档,还配了面试通关计划和学习路线,适合从零开始把复习拉回正轨。 ## 一句话定位 J

gitblog_00423的博客 83
上一篇: SpringBoot连接Nacos报503错误?三步排查法帮你快速定位问题
下一篇: 地铁站能耗管理实战:如何用安科瑞系统实现年省百万电费(附配置清单)
sql99
博客等级 码龄19年 168粉丝 1905原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值