1.硬核科普!Java语言的跨平台特性
2.Java中9种常见的CMS GC问题分析与解决(下)
3.不敢相信?System.currentTimeMillis()存在性能问题
4.JVM详解之:HotSpot VM中的Intrinsic methods
硬核科普!Java语言的跨平台特性
首先,我们需要明确“平台”的含义。在这里,“平台”指的绩效 源码是计算机操作系统,如Windows、Linux、mac系统。跨平台是软件开发的一个重要概念,即软件不依赖于特定的操作系统或硬件环境。在一个操作系统下开发的应用,放到另一个操作系统下依然可以运行。简单来说,开发出的软件可以同时在Windows、Linux、mac等系统中运行,这就是“跨平台”。
Java语言具有“跨平台”的特性,这是相对于其他编程语言来说的。Java语言编写出的应用程序,只需编写一次代码,就可以在不同的系统上运行。而其他一些高级语言,如C语言编写的代码,如果想在不同的平台上运行,需要编译成针对不同系统平台的目标代码。比如在Windows系统中编译的教材如何获取源码C语言程序,在Linux下是不能运行的,需要重新编译成Linux下的目标程序。反之,在Linux下编译的C语言程序,在Windows下也不能运行。
而Java在不同平台上运行时,不需要针对不同的平台多次重新编译,这样就实现了所谓的“Write Once,Run Anywhere”。为什么C、C++不能实现跨平台,而Java却可以实现跨平台?要想搞清楚这个问题,我们需要了解一下什么是JVM虚拟机。
JVM:Java Virtual Machine。这是一种抽象的计算机,在真实的计算机中仿真模拟出各种计算机的功能。简单地说,我们可以把JVM理解成是一个“软件”,该软件相当于一个小型的“计算机系统”。虚拟机有自己完善的硬件架构,如处理器、堆栈、寄存器等,还有各种指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种操作系统平台上不加修改地运行。源码共享论坛宗旨
JVM最核心的一个模块叫Java解释器,相当于是计算机的“CPU”,可以将.class字节码解释成适合不同操作系统的可执行代码。Java解释器实际上就是特定系统平台下的一个应用程序,只要实现了这个解释器程序,Java字节码就能通过该解释器在该平台下运行,这是Java之所以可以实现跨平台的根本原因所在!
但目前并不是所有的操作系统都有自己对应的Java解释器,所以Java程序并不能在所有的操作系统下运行。它只能在已实现了Java解释器的平台中运行,比如Windows、Linux、mac等主流平台。
目前商用的Java虚拟机有很多种,其中比较主流的有如下几个:1. 适用于JavaSE/JavaEE平台的主流JVM(按流行程度递减):HotSpot VM、J9 VM、Zing VM。2. 适用于JavaME平台的主流JVM:CLDC-HI、J9 VM;3. 适用于Android系统的主流JVM:Dalvik VM、ART VM;4. 其他
对于以上这些虚拟机种类,我们作为初学者简单了解即可,尤其是对HotSpot VM有印象就行,这是我们进行Java开发默认使用的标准JVM。
有了以上这些知识的铺垫,我们就可以尝试着理解Java之所以可以实现跨平台的原理了!
为了能让大家搞清楚Java的跨平台原理,我们还是挪车源码1.5.8先拿C语言的编译过程进行对比,如下图所示
从上图可知,在Windows系统下,我们编写的C语言源码文件后缀是.c,在Windows平台下进行编译会生成后缀名为.exe的可执行文件,经常安装软件的同学应该对这个.exe后缀不陌生。生成.exe文件后,就可以在Windows系统中运行了。
但是如果我们换一个Linux操作系统,即使是同样的C语言代码,在没有进行重新编译的前提下,是无法直接运行的。Windows里的.exe可执行文件,在Linux中是不能执行的!所以我们需要将之前的C语言程序,针对Linux平台重新编译,生成适合于Linux平台的可执行文件才行。
以上就是C语言针对不同平台的编译过程,换一个平台就得编译一次,所以C语言是无法实现跨平台的!但Java却可以实现跨平台,它是怎么做到的呢?我们往下看!
Java之所以可以实现跨平台,最根本的原因是由于JVM虚拟机的存在!我们先来看下图:
根据上图,并结合之前壹哥给大家讲过的Java编程步骤,我们知道Java编程要经历编写、编译、c util uuid源码执行这三个核心步骤。而在执行之前,我们会把.java源码文件编译成.class字节码文件,这里的.java文件、.class文件和具体的操作系统无关,无论是Windows/Linux/mac等任何系统,这些文件都是一样的。那Java到底是依靠什么,才能实现不同操作系统上跨平台的呢?从上图可以很明显的看出,关键在于JVM Java虚拟机!正是因为存在JVM虚拟机,所以Java才能实现跨平台!
那JVM又是如何实现跨平台的呢?
JVM之所以可以实现跨平台,原理其实很简单!其实JVM本身并没有实现跨平台,也就是说不同的操作系统中,需要不同的JVM虚拟机,如下图所示:
所以,如果我们想在Windows系统下开发Java项目,就需要去Oracle官网下载安装一个Windows版的JVM;如果我们想在Linux系统下开发Java项目,就需要去Oracle官网下载安装一个Linux版的JVM;其他系统以此类推。也就是说,.java文件、.class文件,无论在哪个系统上都是一样的,但JVM是不一样的,基于此实现了跨平台!
如下图所示:
从上图可知,Java源程序编译后生成的.class字节码,.class字节码文件运行在JVM虚拟机上,并不直接运行在具体的Windows系统上,而JVM虚拟机又分为Windows版、Linux版、Mac版。JVM充当了中间人的角色,把.class文件和操作系统分割并联系了起来,.class文件不和操作系统直接交互,而是由JVM和操作系统进行交互。
我们可以在Windows、Linux、Mac等任意系统中编写Java程序,并生成.class字节码文件,但要想实现跨平台,则需要安装Windows、Linux、或Mac等系统自己版本的JVM虚拟机。然后把字节码文件放到不同系统下的JVM虚拟机中运行,这样就实现了所谓的跨平台。这样我们程序员就只需要去编写和编译Java代码,至于跨平台的事情,是JVM要做的,我们不关心!
所以说,Java跨平台的根本原理,就是通过JVM虚拟机来实现的!但你要知道,Java语言是跨平台的,JVM却不是跨平台的,不同的操作系统有不同版本的JVM!
Java中9种常见的CMS GC问题分析与解决(下)
Java中9种常见的CMS GC问题分析与解决(下)
美团技术团队通过对内部GC问题的深入研究和总结,针对Hotspot VM中CMS + ParNew组合的复杂场景,提供了深入的分析和实用的解决方案。本文主要聚焦于以下几个关键问题:场景六:单次CMS Old GC耗时长
分析了长时间STW的原因,主要集中在Final Remark阶段,通过理解核心代码和步骤,提出了针对性的优化策略。
场景七:内存碎片与收集器退化
探讨了内存碎片导致的收集器退化,包括晋升失败和并发模式失败,提供了解决策略,如监控内存碎片率和避免大对象产生。
场景八:堆外内存 OOM
剖析了堆外内存泄漏的两种原因,通过NMT和JNI调用的排查方法,提供了解决方案。
场景九:JNI引发的GC问题
针对JNI调用可能导致的GC,解释了GC Locker机制及其潜在影响,并给出了相应的策略。
整体而言,处理这些问题的关键在于理解根源、合理配置参数、监控内存使用情况,并在必要时深入阅读源码。在遇到GC问题时,主动分析和优化是提升系统性能的关键。不敢相信?System.currentTimeMillis()存在性能问题
System.currentTimeMillis(),一个看似高效的基础Java API,实则在并发场景下暴露出性能问题。让我们深入了解其性能瓶颈所在。
在频繁或并发调用中,执行结果表明性能表现极不理想。例如,同时执行一百次System.currentTimeMillis()操作,耗时竟是单线程下一百次的倍!即使调用频次增加,问题依然存在,甚至可能超过创建简单对象实例所需的时间。
探究原因,需深入到HotSpot源码的hotspot/src/os/linux/vm/os_linux.cpp文件中,找到javaTimeMillis()方法,这是System.currentTimeMillis()的native实现。然而,对于其性能表现的底层解释,已有国外专家深入到汇编级别进行分析,详情见《The Slow currentTimeMillis()》。简而言之,HPET计时器性能不佳在于对时间戳请求的串行处理,而TSC计时器则得益于专用寄存器,但其稳定性较低。
解决此问题的一种常见方法是采用单个调度线程按毫秒更新时间戳,创建全局缓存,以避免时钟资源争用,但牺牲了一定的精确度。实现步骤如下:使用CurrentTimeMillisClock.getInstance().now()。
值得注意的是,在不影响程序整体效率的情况下,无需进行这种优化,这仅针对极端情况而设。
JVM详解之:HotSpot VM中的Intrinsic methods
内置方法是编译器内置的方法实现,它们在给定编程语言中使用,由编译器专门处理。内置方法通常在程序请求优化时才启用,以提高效率。因为内置方法是在编译器内部实现的,所以不同的虚拟机,其内置方法是不一样的。内置方法可以在Java源代码级别看起来与非内置方法一样,但它们的区别在于JVM的实现。有些方法在普通Java代码中无法实现,如sun.misc.Unsafe.compareAndSwapInt(),只能通过JNI或内置方法来实现,实现对Java语义的扩展。在Hotspot VM中,内置方法通常在src/share/vm/classfile/vmSymbols.hpp类中。通过参数查看代码中调用的方法是否为内置方法,或者通过底层汇编语言查看。内置方法大部分都是内联方法,通过减少函数调用开销的技术实现。内置方法的实现由三种编译器完成:javac将Java源代码编译成为字节码,在这一层只有数学方法和bootstrapping的MethodHandle实现;JIT的Client Compiler (C1);JIT的Server Compiler (C2)。例如,java.lang.System.currentTimeMillis()方法在Interpreter级别没有intrinsified,因为它是一个native方法,通过JNI调用底层的C++实现。而在C1和C2级别使用intrinsified,直接调用os::javaTimeMillis(),减少JNI的使用,提升效率。内置方法的实现可以通过修改底层的JVM实现完成。Graal是一个用Java编写的JIT编译器,可以使用Java来实现Intrinsic方法,对于不熟悉C++的开发者来说非常友好。通过Graal,内置方法的实现变得简单且容易操作。内置方法是JVM中非常有用的特性,能够显著提高程序效率,是编程时值得考虑的技术之一。