你写的 Java 代码,凭什么能让电脑干活?CPU、内存、硬盘这些硬件自己不会思考,全靠软件指挥。但硬件种类那么多,软件怎么统一协调?Java 的办法很简单:中间加个 "翻译官"JVM,让它去跟硬件打交道,你只管写代码就行。
平时我们写的 .java 文件,先编译成 .class 字节码,然后 JVM 把字节码翻译成当前电脑能跑的机器码。好处是你在 Windows 写的代码,拿到 Linux、Mac 上也能跑,因为每个平台都有自己的 JVM,它帮你搞定了底层差异。这就是 Java 常说的 "一次编写,到处运行"。简单说,你面向 JVM 写代码,JVM 面向硬件写代码。
public class JvmInfo {
public static void main(String[] args) {
// CPU核心数
int cores = Runtime.getRuntime().availableProcessors();
System.out.println("CPU核心数:" + cores);
// JVM最大内存(字节转MB)
long maxMem = Runtime.getRuntime().maxMemory() / 1024 / 1024;
System.out.println("JVM最大内存:" + maxMem + " MB");
// JVM已分配内存
long totalMem = Runtime.getRuntime().totalMemory() / 1024 / 1024;
System.out.println("已分配内存:" + totalMem + " MB");
// JVM空闲内存
long freeMem = Runtime.getRuntime().freeMemory() / 1024 / 1024;
System.out.println("空闲内存:" + freeMem + " MB");
}
}
JVM 跑字节码有两种方式,一种是解释执行,跑一句翻译一句,启动快但跑得慢;另一种是编译执行,先全翻译好再跑,启动慢但快。JVM 很聪明,它会盯着哪些方法被反复调用,也就是所谓的 "热点代码",调用次数多了就用 JIT 编译器把它翻译成机器码存起来,下次直接跑,不用再翻译。打个比方,第一次去陌生地方要开导航,去多了路熟了直接走。这就是对 CPU 资源的合理利用,冷门代码省着编译,热门代码直接拉满速度。
C 语言里你要手动申请内存、手动释放,忘了释放就内存泄漏,释放早了程序崩溃。Java 说别操心,对象不用了我自动回收。JVM 把堆内存分成两块,新对象都在新生代,大部分对象用完就丢,回收频率高;活过几次回收的 "老油条" 挪到老年代,回收频率低。常见的垃圾回收器比如 G1,也就是 JDK9 默认的,还有 ZGC 超低延迟,它们帮你自动打扫内存,你只管 new 对象就行。
现在 CPU 都是多核的,一个线程只能用一个核,太浪费。Java 用多线程让多个核同时干活。但多个线程同时改一个数据会出问题,所以 Java 给了几把 "锁",synchronized 加锁后同一时间只让一个线程进,volatile 保证一个线程改了别的线程马上能看到,还有线程池不用每次新建线程,复用已有线程省开销。线程池大小也有讲究,CPU 密集型任务线程数等于核数,IO 密集型可以开两倍核数,因为等 IO 的时候 CPU 闲着,可以让别的线程上。
传统 IO 是阻塞的,你读文件,线程就卡在那等磁盘读完,CPU 闲着没事干。NIO 搞了个 Selector,一个线程盯着好几个通道,哪个数据准备好了就去处理哪个,CPU 不用傻等,效率高多了。像 Tomcat、Netty 这些高性能框架底层都用的 NIO。
说到底,Java 协调硬件就五招:用 JVM 字节码搞定跨平台,用 JIT 让热点代码跑得快,用 GC 自动管内存防泄漏,用多线程吃满多核 CPU,用 NIO 不让 CPU 白等外设。一句话,Java 在 "写得爽" 和 "跑得快" 之间找了个不错的平衡,这也是它能在企业开发里火这么多年的原因。
