11 java语言执行浅析1
Java 是基于线程模型的语言,程序的执行是通过线程来调度和运行的。
一、Java 的最小执行单元:线程(Thread)
- 每个 Java 程序至少有一个主线程(main thread),它从
main()
方法开始执行。 - 所有代码最终都在线程中运行,包括方法调用、对象创建等操作。
- 可以通过继承
Thread
类或实现Runnable
接口创建多线程程序。
public class MyThread extends Thread {public void run() {System.out.println("Thread is running");}public static void main(String[] args) {MyThread t = new MyThread();t.start(); // 启动线程}
}
结论:线程才是 Java 的最小执行单元;方法只是逻辑组织单位,并不能独立执行。
Java 与 Go 的面向对象/结构化设计差异
Java:一切必须封装在类中(OOP)
- Java 是一门典型的 面向对象编程语言(OOP)。
- 所有代码都必须写在类(
class
)内部,包括方法、变量、静态块等。 - 没有“函数”这个一级类型,只有类中的方法。即使是一个简单的
main()
方法也必须定义在一个类里。
特点:
- 强调封装性、模块性和可扩展性;
- 更适合大型企业级应用开发;
- 支持继承、多态、接口等 OOP 高级特性。
- (X)灵活性略差,语法繁琐;
- (X)对于简单脚本或小型项目不够轻便。
Go:函数是一等公民,支持自由函数(FP + OOP)
- Go 是一种 过程式 + 接口 + 并发模型 的语言,强调简洁和高效。
- 函数是“一等公民”,可以直接定义并作为参数传递。
- Go 中的函数可以独立存在,不依赖类结构。
- 类型系统简单,没有继承,而是通过组合实现复用。
特点:
- 更加灵活,适合并发、网络服务等场景;
- 语法简洁,学习成本低;
- 内置 goroutine 和 channel 支持 CSP 并发模型。
- (X)缺乏传统 OOP 的继承机制;
- (X)不适合构建复杂的类层次结构。
我们可以从 Java 的 动态代理(Dynamic Proxy) 和 Go 的 高阶函数(Higher-order Function) 来对比说明。
Java 动态代理基于 JVM 提供的 Proxy 类和 InvocationHandler 接口。
public interface Service {void doSomething();
}public class RealService implements Service {public void doSomething() {System.out.println("Doing something...");}
}public class LoggingInvocationHandler implements InvocationHandler {private Object target;public LoggingInvocationHandler(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method call");Object result = method.invoke(target, args);System.out.println("After method call");return result;}
}// 使用示例
Service realService = new RealService();
Service proxy = (Service) Proxy.newProxyInstance(realService.getClass().getClassLoader(),new Class<?>[]{Service.class},new LoggingInvocationHandler(realService)
);proxy.doSomething();
- 动态代理本质上是 对方法调用的拦截与增强;
- 它并不是直接“传入一个函数,返回一个增强函数”,而是 对对象的方法调用做拦截处理;
- 但其背后的思想确实是:传入一个行为(目标对象),返回一个包装后的行为(代理对象)。
Go 语言的“函数增强”方式:高阶函数
Go 没有动态代理的概念,但它可以通过 函数闭包 + 高阶函数 轻松实现类似功能。
package mainimport ("fmt""time"
)// 增强器函数:接收一个函数,返回一个新的增强函数
func timeLogger(fn func()) func() {return func() {start := time.Now()fn()fmt.Printf("Function took %v\n", time.Since(start))}
}func sayHello() {fmt.Println("Hello, World!")
}func main() {wrapped := timeLogger(sayHello)wrapped()
}
- 直接操作函数,不需要类、接口、代理;
- 更加轻量、直观;
- 更容易写出通用中间件(如 HTTP Middleware);
Java 代码执行流程详解
1. Java 内存由 JVM 管理,主要分为以下区域:
1. 堆(Heap)
- 所有对象实例和数组存储在此,是 JVM 管理的最大内存区域。
- 线程共享,垃圾回收(GC)的主要区域。
- 分代设计:
- 新生代(Young Generation):新创建的对象在此,分为 Eden 区和两个 Survivor 区。
- 老年代(Old Generation):长期存活的对象(多次 GC 后仍存活)进入老年代。
- 永久代 / 元空间(PermGen/Metaspace):JDK 8 之前存储类元数据(如类结构、方法字节码),JDK 8 后改为元空间(直接内存)。
2. 栈(Stack)
- 线程私有,每个线程有独立的栈。
- 存储局部变量、方法调用帧(包含方法参数、返回值、局部变量表、操作数栈等)。
- 栈帧(Stack Frame):每个方法调用创建一个栈帧,方法执行完毕后栈帧弹出。
3. 方法区(Method Area)
- 线程共享,存储类的结构信息(如类名、字段、方法、常量池)。
- JDK 8 之前是堆的永久代(PermGen),JDK 8 后改为元空间(Metaspace),使用直接内存。
4. 本地方法栈(Native Method Stack)
- 为 ** 本地方法(如
native
关键字修饰的方法)** 服务,与 Java 栈类似。
5. 程序计数器(Program Counter Register)
- 线程私有,记录当前线程执行的字节码行号,用于线程切换后恢复执行位置。
2. 程序启动:从 main()
方法开始
- JVM 调用
main()
方法作为程序入口。 main()
方法本身是在主线程中执行的。
public static void main(String[] args) {System.out.println("Start of program");someMethod();System.out.println("End of program");
}
3. 方法调用栈(Call Stack)
- Java 使用 调用栈(call stack) 来管理方法调用。
- 每次调用一个方法,JVM 都会在当前线程的栈中压入一个 栈帧(Stack Frame)。
- 栈帧包含局部变量表、操作数栈、动态链接、返回地址等信息。
void methodA() {methodB();
}void methodB() {int x = 10;
}
调用顺序如下:
main()
└─ methodA()└─ methodB()
methodB()
执行完后,弹出栈帧;- 返回到
methodA()
; - 最终回到
main()
,继续执行后续代码。
4. 异常处理与 finally 块
- 即使发生异常,JVM 也会确保栈帧被正确弹出。
finally
块保证无论是否抛出异常都会执行(除非虚拟机退出)。
5. 对象创建与内存分配示例
public class MemoryExample {public static void main(String[] args) {// 1. 基本类型变量存储在栈中int a = 10;// 2. 对象引用存储在栈中,对象实例存储在堆中Person p = new Person("Alice", 25);// 3. 调用方法时,方法帧入栈p.sayHello();}
}class Person {private String name; // 成员变量存储在堆中的对象实例中private int age; // 成员变量存储在堆中的对象实例中public Person(String name, int age) {this.name = name;this.age = age;}public void sayHello() {// 局部变量存储在栈帧中String message = "Hello, my name is " + name;System.out.println(message);}
}
内存分析:
- 基本类型变量
a
:存储在main
方法的栈帧中。 - 对象引用
p
:存储在main
方法的栈帧中,指向堆中的Person
对象实例。 Person
对象实例:存储在堆中,包含name
和age
字段。- 方法调用
p.sayHello()
:创建新的栈帧,存储局部变量message
,方法执行完毕后栈帧销毁。
Java 垃圾回收机制(GC)与内存释放
java -XX:+PrintCommandLineFlags -version
此命令会输出 JVM 的版本信息和启动参数,其中启动参数里就包含了选用的垃圾收集器等关键信息。
在Java 8中,默认的垃圾收集器配置是使用Parallel GC,即新生代采用Parallel Scavenge收集器,而老年代则使用Parallel Old收集器。这种组合旨在提高应用程序的整体吞吐量。
从Java 9开始,JVM默认使用的垃圾收集器变更为G1(Garbage First)收集器,它被设计为能够处理非常大的堆,并且可以在大多数情况下提供更短的停顿时间。G1收集器通过将堆划分为多个区域(Region),然后根据垃圾的数量优先回收那些包含最多可回收对象的区域,从而实现高效内存管理。
对于之后的版本,例如Java 11和Java 17等长期支持(LTS)版本,默认依旧是G1收集器。不过,值得注意的是,在较新的Java版本中(比如Java 17或更新版本),随着ZGC和Shenandoah等低延迟垃圾收集器的发展和完善,用户可以根据需要选择这些先进的垃圾收集器作为替代方案,但它们并不是默认选项。
1. Java 内存模型简述
区域 | 描述 |
---|---|
方法区 | 存储类信息、静态变量、常量池等 |
堆(Heap) | 对象实例主要存放地,GC 主要工作区域 |
栈 | 每个线程私有,存储局部变量、方法参数等 |
本地方法栈 | 用于 Native 方法调用 |
程序计数器 | 当前线程所执行字节码的行号指示器 |
2. 垃圾回收的基本原理
- Java 的垃圾回收(Garbage Collection, GC)自动管理堆内存。
- 可达性分析算法(GC Roots Tracing) 是主流判断对象是否为垃圾的方式:
- 从 GC Roots 开始遍历引用链;
- 无法到达的对象被视为不可达,可被回收。
常见的 GC Roots 包括:
- 虚拟机栈中的局部变量引用;
- 方法区中类的静态属性引用;
- 方法区中常量引用;
- 本地方法栈中 Native 引用。
3. 方法执行结束后是否会释放内存?
局部变量(基本类型):
- 存储在栈中,方法执行完毕自动弹出栈帧,无需 GC。
局部变量引用的对象(如 new Object()
):
- 实际对象在堆中;
- 方法执行结束后,该引用不再存在(即离开作用域);
- 如果没有其他引用指向该对象,则下一次 GC 会将其回收。
void method() {List<String> list = new ArrayList<>();list.add("Hello");
}
- 方法执行完后,
list
变量消失; - 如果没有其他引用指向这个
ArrayList
,GC 会回收它。
4. 如何加快对象回收?(建议做法)
- 将不再使用的对象设为
null
(帮助 GC 更早识别无用对象); - 避免内存泄漏(如长生命周期对象持有短生命周期对象的引用);
- 使用弱引用(WeakHashMap、SoftReference、PhantomReference)来辅助 GC 工作。
总结
项目 | 内容 |
---|---|
最小执行单元 | 线程(Thread) |
方法的作用 | 逻辑封装单位,必须在线程中执行 |
方法调用流程 | 通过调用栈进行压栈、弹栈操作 |
方法执行结束 | 栈帧自动弹出,局部变量释放,堆中对象等待 GC |
垃圾回收机制 | 自动管理堆内存,使用可达性分析算法 |
内存释放时机 | 对象不可达时,由 GC 在适当时间回收 |
扩展
垃圾回收的发展简要时间线:
- 1958年 John McCarthy 在 Lisp 中实现第一个自动垃圾回收器
- 1960年代~1970年代 多种函数式语言系统使用 GC 技术
- 1983年 Smalltalk 使用 GC 管理对象内存
- 1995年 Java 发布,GC 成为主流开发语言的重要组成部分
- 2000年后 .NET、Python、Ruby、JavaScript、Go 等语言广泛采用 GC
现在主流语言
c/C++
- 手工管理 malloc free/new delete
- 忘记释放-memory leak-out of memory
- 释放多次产生极其难易调试的bug,一个线程空间莫名其妙被另外一个释放了
- 开发效率很低
java python go js kotlin scala
-
方便内存管理的语言
-
GC-GarbageCollector-应用线程只管分配,垃圾回收器负责回收
-
大大减低程序员门槛
rust
-
运行效率超高(asmcc++)
-
不用手工管理内存(没有GC)
-
学习曲线巨高(ownership)
-
你只要程序语法通过,基本就不会有bug