Java 世界历险记:从一行 System.out.println 到整个 Spring 宇宙

Java 世界历险记:从一行 System.out.println 到整个 Spring 宇宙

你敲下 System.out.println("Hello"),屏幕上弹出两个字。你觉得自己会 Java 了。直到有一天,你打开了 JVM 的文档,才发现自己连这句话是怎么跑到屏幕上的都不知道。

序章:一个让我重新认识 Java 的瞬间

某天我写了一段再普通不过的代码:

java

ini 复制代码
public class Main {
    public static void main(String[] args) {
        String a = "hello";
        String b = "hello";
        String c = new String("hello");
        System.out.println(a == b);      // true
        System.out.println(a == c);      // false
        System.out.println(a.equals(c)); // true
    }
}

前两个输出我勉强能解释,第三个输出让我彻底懵了。

a == c 是 false,但 a.equals(c) 是 true。一个说"不相等",一个说"相等"。

我盯着屏幕,第一次意识到:我用了这么久 Java,连"相等"是什么意思都没搞明白。

于是,我决定往下挖。这一挖,挖出了 JVM、字节码、内存模型、垃圾回收、Spring 宇宙......一个比我想象中庞大得多的世界。

第一章:Java 代码的一生------从 .java 到机器码

你写的 .java 文件,并不是直接跑的。它经历了一段"变身之旅":

text

css 复制代码
Main.java
   ↓ javac 编译
Main.class(字节码)
   ↓ JVM 解释/编译
机器码

第一步:编译成字节码

bash

css 复制代码
javac Main.java

这行命令把 .java 编译成 .class,里面是字节码。

字节码不是机器码,也不是源代码,而是一种"中间语言"。想看它长什么样?用 javap:

bash

css 复制代码
javap -c Main

输出:

text

yaml 复制代码
0: ldc           #2    // String hello
2: astore_1
3: getstatic     #3    // Field java/lang/System.out
6: ldc           #4    // String hello
8: invokevirtual #5    // Method java/io/PrintStream.println

原来 System.out.println("hello") 不是一行代码,而是一堆指令。

第二步:JVM 负责"翻译"

JVM(Java 虚拟机)就是那个"翻译官"。它有两种模式:

  • 解释执行:读一条字节码,执行一条。
  • 即时编译(JIT):把"热点代码"编译成机器码,直接跑。

Java 比 C 慢?那是以前。有了 JIT,热点代码跑起来和 C 差不多。

第三步:为什么叫"一次编写,到处运行"

因为字节码是"平台无关"的。同一份 .class,在 Windows 上由 Windows 的 JVM 翻译,在 Linux 上由 Linux 的 JVM 翻译。

JVM 屏蔽了操作系统的差异,这就是 Java 的最大卖点。

第二章:JVM 内存模型------程序运行时的"五脏六腑"

JVM 在运行时,会把自己的内存分成几块:

1. 堆(Heap)------最大的一块

所有对象都在这里。 你 new 出来的东西,全在堆里。

java

sql 复制代码
User user = new User(); // user 对象在堆里

堆是线程共享的,也是垃圾回收的主战场。

2. 栈(Stack)------每个线程一个

方法调用时的局部变量、方法参数、返回值,都在这。

java

arduino 复制代码
public void hello() {
    int x = 1;        // x 在栈里
    String s = "hi";  // s 的引用在栈里,对象在堆里
}

栈是线程私有的。每个线程有自己的栈,方法调用就压栈,方法结束就弹栈。

3. 方法区(Method Area)------类信息

类的元信息、常量、静态变量,都在这。

从 JDK 8 开始,方法区的实现叫元空间(Metaspace),它在本地内存里,不在 JVM 堆里。

4. 程序计数器(PC Register)

记录当前线程执行到哪条字节码。

每个线程一个,是 JVM 内存里唯一不会 OOM 的区域。

5. 本地方法栈

给 native 方法用的。

这五块合起来,就是 JVM 的"五脏六腑"。

第三章:垃圾回收------JVM 的"自动清洁工"

Java 最爽的一点:不用手动 free。

但 JVM 怎么知道哪些对象该回收?

引用计数?不行

如果 A 引用 B,B 引用 A,两个都回收不了。

所以 JVM 用的是"可达性分析"。

可达性分析:从 GC Roots 出发

GC Roots 包括:

  • 栈里的局部变量。
  • 方法区的静态变量。
  • 常量引用。
  • JNI 引用。

从这些根出发,能走到的对象,就是"活的";走不到的,就是"垃圾"。

分代回收:新对象查得勤,老对象查得少

堆被分成两块:

  • 新生代(Young):新对象在这,回收频繁,用复制算法。
  • 老年代(Old):活得久的对象,回收少,用标记-整理算法。

为什么这么分?因为大部分对象"朝生夕死"。

垃圾回收器:从 Serial 到 G1 到 ZGC

  • Serial:单线程,简单,适合小内存。
  • Parallel:多线程,吞吐优先。
  • CMS:并发收集,低延迟。
  • G1:分区收集,平衡吞吐和延迟,JDK 9 后的默认。
  • ZGC:超低延迟,适合大堆。

选哪个?看你的场景:要吞吐选 Parallel,要低延迟选 ZGC。

第四章:Java 语言的"武功秘籍"

Java 语言本身,也有不少"招式"。

1. 面向对象:封装、继承、多态

java

scala 复制代码
class Animal {
    void speak() { System.out.println("..."); }
}

class Dog extends Animal {
    @Override
    void speak() { System.out.println("汪汪"); }
}

Animal a = new Dog();
a.speak(); // 汪汪

多态的本质是:编译时看类型,运行时看对象。

2. 泛型:类型安全的"模板"

java

ini 复制代码
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(1); // 编译报错

泛型只在编译期有效,运行时会被"擦除"。

java

ini 复制代码
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass()); // true

这就是"类型擦除",也是 Java 泛型最容易被误解的地方。

3. 反射:运行时的"透视眼"

java

ini 复制代码
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod("setName", String.class);
method.invoke(obj, "张三");

反射让你在运行时"看到"类的结构、调用方法、访问字段。

Spring 的依赖注入、MyBatis 的映射,全靠反射。

4. 注解:给代码贴"标签"

java

typescript 复制代码
@Override
public String toString() { return "hello"; }

@Deprecated
public void oldMethod() {}

@SuppressWarnings("unchecked")
public void unchecked() {}

注解本身不做任何事,是"处理注解的代码"让它有了意义。

比如 @Override 由编译器检查,@Autowired 由 Spring 处理。

5. Lambda 和 Stream:函数式编程

java

rust 复制代码
List<String> names = Arrays.asList("张三", "李四", "王五");

names.stream()
     .filter(n -> n.startsWith("张"))
     .map(String::toUpperCase)
     .forEach(System.out::println);

Lambda 让 Java 也能写出"函数式"的代码。

Stream 让集合操作像 SQL 一样流畅。

第五章:Spring 宇宙------Java 框架的"复仇者联盟"

如果说 Java 语言是"武功",那 Spring 就是"门派"。

Spring Core:IoC 和 DI

IoC(控制反转):对象的创建权,从你手里交给 Spring。

DI(依赖注入):Spring 把依赖"注入"到你的对象里。

java

kotlin 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
}

你不用 new UserRepository(),Spring 帮你 new。

Spring AOP:面向切面编程

把"日志、事务、权限"这些横切逻辑,从业务代码里抽出来。

java

less 复制代码
@Aspect
@Component
public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint jp) {
        System.out.println("调用方法:" + jp.getSignature());
    }
}

AOP 的底层是动态代理:JDK 动态代理或 CGLIB。

Spring MVC:Web 层

java

less 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

@RestController + @GetMapping,一个接口就出来了。

Spring Boot:把 Spring 变简单

java

typescript 复制代码
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

一行代码,启动整个应用。内嵌 Tomcat,自动配置,开箱即用。

Spring Boot 的"自动配置"是它最核心的魔法: 根据你引入的依赖,自动配置相应的 Bean。

Spring Cloud:微服务的"全家桶"

  • Eureka / Nacos:服务注册与发现。
  • OpenFeign:服务间调用。
  • Gateway:网关。
  • Hystrix / Sentinel:熔断限流。
  • Config / Nacos:配置中心。

Spring Cloud 让微服务不再是"架构师才懂的东西"。

MyBatis / MyBatis-Plus / JPA:持久层

java

kotlin 复制代码
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(Long id);
}

MyBatis 让你手写 SQL,灵活;JPA 让你面向对象,简洁。

MyBatis-Plus 在 MyBatis 上加了 CRUD 封装,开发更快。

第六章:Java 的"家族图谱"

Java 不只是语言,它是一个生态。

  • Java SE:标准版,基础语言 + 核心库。
  • Java EE / Jakarta EE:企业版,Servlet、JPA、EJB。
  • Java ME:微型版,嵌入式。
  • JavaFX:桌面 GUI。
  • Android:移动端(用 Java/Kotlin)。

JDK、JRE、JVM 的区别:

  • JVM:虚拟机,执行字节码。
  • JRE:JVM + 核心类库,能运行 Java 程序。
  • JDK:JRE + 编译器 + 工具,能开发 Java 程序。

一张图记住:JDK > JRE > JVM。

第七章:那些年 Java 被吐槽的"黑点"

1. 啰嗦

java

typescript 复制代码
// Java
public class Main {
    public static void main(String[] args) {
        System.out.println("hello");
    }
}

python
bash 复制代码
# Python
print("hello")

Java 的仪式感,确实重了点。

2. 泛型擦除

java

arduino 复制代码
List<String> list = new ArrayList<>();
list.add("hello");
// 运行时,你不知道 list 里是 String

擦除让泛型失去了运行时信息,也带来了一堆"类型转换警告"。

3. 空指针

java

scss 复制代码
user.getAddress().getCity().getName();
// 任何一环是 null,就 NPE

NPE 是 Java 程序员的"日常噩梦"。

JDK 14 引入了 Optional,但用起来还是不如 Kotlin 的 ?. 优雅。

4. 启动慢、内存大

JVM 启动要几秒,内存占用动辄几百 MB。

这也是为什么云原生时代,Go 和 Rust 开始抢 Java 的饭碗。

但 Java 也在进化:GraalVM 让 Java 能编译成原生镜像,启动快、内存小。

第八章:Java 的"武功心法"

学 Java 这么多年,我最大的感悟是:

Java 不是一门语言,而是一个"体系"。

  • 语言只是表层。
  • JVM 是地基。
  • 框架是楼房。
  • 生态是城市。

学 Java 的路径,大概是这样的:

  1. 先学语法:类、对象、继承、多态、集合、异常。
  2. 再学 JVM:内存模型、GC、类加载、字节码。
  3. 然后学框架:Spring、Spring Boot、MyBatis。
  4. 最后学架构:微服务、分布式、高并发。

但最重要的,不是学了多少框架,而是理解"为什么这么设计"。

为什么要有 GC?因为手动管理内存太容易出错。 为什么要有 IoC?因为对象依赖太复杂。 为什么要有 AOP?因为横切逻辑太分散。

每一个设计,都是为了解决一个具体问题。

终章:回到最初的问题

现在,我可以回答开头那个问题了。

java

ini 复制代码
String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println(a == b);      // true
System.out.println(a == c);      // false
System.out.println(a.equals(c)); // true
  • a == b 是 true:因为 "hello" 在字符串常量池里,a 和 b 指向同一个对象。
  • a == c 是 false:因为 new String() 在堆里创建了一个新对象,地址不同。
  • a.equals(c) 是 true:因为 equals 比较的是内容,不是地址。

一个"相等",背后是字符串常量池、堆、栈、引用和内容的复杂关系。

这就是 Java 的底层世界:你看到的每一行代码,背后都有一整套机制在运转。

P.S. 写完这篇文章后,我又写了一次 String a = "hello"; String b = "hello";,然后想:如果 JVM 哪天不优化了,这个世界会怎样?

P.P.S. 但 JVM 一直在优化,从解释到 JIT,从 Serial GC 到 ZGC。Java 没老,它一直在进化。

相关推荐
XIAEE1 小时前
微前端狂想曲:我试图让三个不同门派的前端住进同一栋楼
java·spring
XIAEE1 小时前
Linux 生存指南:从"终端恐惧症"到"命令行老司机"
java·spring
XIAEE1 小时前
实战教程:手机端死活调不出摄像头?聊聊安全上下文(Secure Context)
java·spring
程序员清风1 小时前
堆与优先队列:从 Top K 到任务调度
java·数据结构·算法
XIAEE1 小时前
ES6 与 Node.js 群侠传:那些年我们追过的语法,和那些年把我们追杀的 API
java·spring
XIAEE1 小时前
WebRTC 探险记:我是如何让两个浏览器"看对眼"的
java·spring
XIAEE1 小时前
UniApp 本地打包通关记:Android 是地狱难度,iOS 是隐藏 BOSS
java·spring
高晶1 小时前
Android 物联网组网实战:NFC 从零到落地
android·java
Dreamboat-L2 小时前
日志级别基础 + Spring Boot 日志实践指南
java·服务器·数据库·java-ee·springboot