news 2026/10/21 6:46:00

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 世界历险记:从一行 System.out.println 到整个 Spring 宇宙

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

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

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

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

java

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

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

第一步:编译成字节码

bash

javac Main.java

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

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

bash

javap -c Main

输出:

text

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

User user = new User(); // user 对象在堆里

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

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

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

java

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

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

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

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

java

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

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

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

java

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

@Override public String toString() { return "hello"; } @Deprecated public void oldMethod() {} @SuppressWarnings("unchecked") public void unchecked() {}

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

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

5. Lambda 和 Stream:函数式编程

java

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

@Service public class UserService { @Autowired private UserRepository userRepository; }

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

Spring AOP:面向切面编程

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

java

@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

@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

@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

@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

// Java public class Main { public static void main(String[] args) { System.out.println("hello"); } } python
# Python print("hello")

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

2. 泛型擦除

java

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

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

3. 空指针

java

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

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© 是 true:因为 equals 比较的是内容,不是地址。

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

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

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 6:44:44

AI论文降重工具横向评测:从NLP原理到实际选型

拿到毕业季的红色查重报告那一刻&#xff0c;很多人的第一反应是搜“降重工具”。但市面上的AI论文降重、自然语言处理改写服务多到眼花&#xff0c;真正能实际用的其实没几个。我前后花了几周时间&#xff0c;把六个圈内比较常见的AI论文改写网站拉在一起做了一轮测&#xff0…

作者头像 李华
网站建设 2026/10/21 6:44:10

长期项目坚持不下去?第32天才是真正考验执行力的转折点

1. “day 32”到底是个什么信号1.1 第30天之后的两天&#xff0c;信息量最大很多长期项目都习惯把“30天”当成一个里程碑。训练营、打卡活动、习惯养成周期&#xff0c;几乎都默认30天是一个完整周期。但真正做过的人都知道&#xff0c;第30天往往是被关注、被庆祝、被截图的那…

作者头像 李华
网站建设 2026/10/21 6:43:46

车辆质量与道路坡度估计:从建模到实车标定的工程实战

车辆质量与道路坡度估计&#xff0c;做底盘控制的人对这俩词都不陌生&#xff0c;但真正吃透它们的人并不多。我指的是那种"拿出来就能用、装上车不跑偏"的层次。重载卡车和混合动力车辆对这两个参数的敏感程度尤其夸张——一个满载40吨&#xff0c;一个空载15吨&…

作者头像 李华
网站建设 2026/10/21 6:43:40

中压直流变压器松磁耦合结构设计与参数权衡分析

中压直流变压器这几年在直流配电网、新能源汇集、轨道交通这些场景里出镜率越来越高&#xff0c;核心拓扑大多落在双有源桥&#xff08;DAB&#xff09;这一族。做DAB直流变压器的人基本都绕不开一个问题&#xff1a;功率搬移必须靠电感&#xff0c;电感的体积和损耗往往是整机…

作者头像 李华
网站建设 2026/10/21 6:43:20

C语言递归函数入门:从“求5的阶乘”到调用栈与栈溢出

“求5”这个说法&#xff0c;我猜很多人第一次看到时都跟我一样有点懵&#xff1a;到底要求哪个5&#xff1f;是5的阶乘、5次方、还是1到5的累加&#xff1f;不过放在C语言递归函数这个上下文里&#xff0c;它的指向其实相当明确——绝大多数教材、习题和课程设计里&#xff0c…

作者头像 李华
网站建设 2026/10/21 6:43:16

一颗SoC管七屏十六摄:RK3588智能座舱方案详解

简介&#xff1a;面向汽车电子系统设计师与车载信息系统工程师的RK3588车载中控解决方案资料包&#xff0c;聚焦智能座舱多屏管理与先进人机交互&#xff0c;系统梳理了RK3588在多屏异显异触、多摄像头接入、快速倒车启动等场景下的设计思路&#xff0c;并展示其适配Android及Q…

作者头像 李华