- 浏览: 101782 次
- 性别:
- 来自: 深圳
-
文章分类
最新评论
Java程序运行在JVM之上,JVM的运行状况对于Java程序而言会产生很大的影响,因此,掌握JVM中的关键机制对于编写稳定、高性能的Java程序至关重要。
JVM规范 定义的标准结构如图3.1所示。
![]() |
(点击查看大图)图3.1 JVM标准结构 |
以上标准结构是JVM规范中定义的,但各家厂商在实现时不一定会完全遵守。
JVM负责装载class文件并执行,因此,首先要掌握的是JDK如何将Java代码编译为class文件、如何装载class文件及如何执行 class,将源码编译为class文件的实现取决于各个JVM实现或各种源码编译器。class文件通常由类加载器(ClassLoader)来完成加 载;class的执行在Sun JDK中有解释执行和编译为机器码执行两种方式,其中编译为机器码又分为client和server两种模式。Sun JDK为了提升class的执行效率,对于解释执行和编译为机器码执行都设置了很多的优化策略。
Java程序无须显式分配和回收内存,因此JVM如何进行内存的分配和回收也是要关注的问题。
JVM提供了多线程支持,对于分布式Java应用而言,通常要借助线程来实现高并发,因此JVM中线程资源同步的机制及线程之间交互的机制也是需要掌握的。
各厂家在实现JVM时有所区别,本章以Sun JDK 1.6为例来对JVM中的这三个方面进行介绍。
3.1 Java代码的执行机制
要在JVM中执行Java代码,首先要编译为class文件。下面介绍Sun JDK是如何将Java代码编译为class文件的,这种机制通常称为Java源码编译机制。
3.1.1 Java源码编译机制(1)
JVM规范中定义了class文件的格式,但并未定义Java源码如何编译为class文件,各厂商在实现JDK时通常会将符合Java语言规范的 源码编译为class文件的编译器,例如在Sun JDK中就是javac,javac将Java源码编译为class文件的步骤如图3.2所示。
![]() |
(点击查看大图)图3.2 javac编译源码为class文件的步骤 |
下面简单介绍以上三个步骤:
1. 分析和输入到符号表(Parse and Enter)
Parse过程所做的为词法和语法分析。词法分析(com.sun.tools.javac.parser.Scanner)要完成的是将代码字符 串转变为token序列(例如Token.EQ(name:=));语法分析(com.sun.tools.javac.parser.Parser)要 完成的是根据语法由token序列生成抽象语法树 。
Enter(com.sun.tools.javac.comp.Enter)过程为将符号输入到符号表,通常包括确定类的超类型和接口、根据需要添加默认构造器、将类中出现的符号输入类自身的符号表中等。
2. 注解处理(Annotation Processing)
该步骤主要用于处理用户自定义的annotation,可能带来的好处是基于annotation来生成附加的代码或进行一些特殊的检查,从而节省一些共用的代码的编写,例如当采用Lombok 时,可编写如下代码:
public class User{
private @Getter String username;
}
此功能基于JSR 269 ,在Sun JDK 6中提供了支持,在Annotation Processing进行后,再次进入Parse and Enter步骤。
3. 语义分析和生成class文件(Analyse and Generate)
Analyse步骤基于抽象语法树进行一系列的语义分析,包括将语法树中的名字、表达式等元素与变量、方法、类型等联系到一起;检查变量使用前是否 已声明;推导泛型方法的类型参数;检查类型匹配性;进行常量折叠;检查所有语句都可到达;检查所有checked exception都被捕获或抛出;检查变量的确定性赋值(例如有返回值的方法必须确定有返回值);检查变量的确定性不重复赋值(例如声明为final的 变量等);解除语法糖(消除if(false) {…} 形式的无用代码;将泛型Java转为普通Java;将含有语法糖的语法树改为含有简单语言结构的语法树,例如foreach循环、自动装箱/拆箱等)等。
在完成了语义分析后,开始生成class文件(com.sun.tools.javac.jvm.Gen),生成的步骤为:首先将实例成员初始化器 收集到构造器中,将静态成员初始化器收集为<clinit>();接着将抽象语法树生成字节码,采用的方法为后序遍历语法树,并进行最后的少 量代码转换(例如String相加转变为StringBuilder操作);最后从符号表生成class文件。
上面简单介绍了基于javac如何将java源码编译为class文件 ,除javac外,还可通过ECJ(Eclipse Compiler for Java) 或Jikes 等编译器来将Java源码编译为class文件。
class文件中并不仅仅存放了字节码,还存放了很多辅助jvm来执行class的附加信息,一个class文件包含了以下信息。
结构信息
包括class文件格式版本号及各部分的数量与大小的信息。
元数据
简单来说,可以认为元数据对应的就是Java源码中"声明"与"常量"的信息,主要有:类/继承的超类/实现的接口的声明信息、域(Field)与方法声明信息和常量池。
方法信息
简单来说,可以认为方法信息对应的就是Java源码中"语句"与"表达式"对应的信息,主要有:字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试用符号信息。
以一段简单的代码来说明class文件格式
public class Foo{
private static final int MAX_COUNT = 1000 ;
private static int count = 0 ;
public int bar() throws Exception{
if(++count > = MAX_COUNT){
count = 0 ;
throw new Exception("count overflow");
}
return count;
}
}
执行javac -g Foo.java(加上-g是为了生成所有的调试信息,包括局部变量名及行号信息,在不加-g的情况下默认只生成行号信息)编译此源码,之后通过 javap -c -s -l -verbose Foo来查看编译后的class文件,结合class文件格式来看其中的关键内容。
// 类/继承的超类/实现的接口的声明信息
public class Foo extends java.lang.Object
SourceFile: "Foo.java"
// class文件格式版本号,major version: 50表示为jdk 6,49为jdk 5,48为jdk 1.4,只有高版本能执行低版本的class文件,这也是jdk 5不能执行jdk 6编译的代码的原因。
minor version: 0
major version: 50
// 常量池,存放了所有的Field名称、方法名、方法签名、类型名、代码及class文件中的常量值。
Constant pool:
const # 1 = Method #7.#27; // java/lang/Object." < init > ":()V
const # 2 = Field #6.#28; // Foo.count:I
const # 3 = class #29; // java/lang/Exception
const # 4 = String #30; // count overflow
const # 5 = Method #3.#31; // java/lang/Exception." < init > ":(Ljava/lang/String;)V
…
const # 34 = Asciz (Ljava/lang/String;)V;
{
// 将符号输入到符号表时生成的默认构造器方法
public Foo();
…
// bar方法的元数据信息
public int bar() throws java.lang.Exception;
Signature: ()I
// 对应字节码的源码行号信息,可在编译的时候通过-g:none去掉行号信息,
行号信息对于查找问题而言至关重要,因此最好还是保留。
LineNumberTable:
line 9: 0
line 10: 15
line 11: 19
line 13: 29
// 局部变量信息,如生成的class文件中无局部变量信息,则无法知道局部变量的名称,并且局部变量信息是和方法绑定的,接口是没有方法体的,所以ASM之类的在获取接口方法时,是拿不到方法中参数的信息的。
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 this LFoo;
Code:
Stack = 3 , Locals = 1 , Args_size = 1
// 方法对应的字节码
0: getstatic #2; //Field count:I
..
29: getstatic #2; //Field count:I
32: ireturn
…
// 记录有分支的情况(对应代码中if..、for、while等),在下一节"类加载机制"中会讲解这个的作用
StackMapTable: number_of_entries = 1
frame_type = 29 /* same */
// 异常处理器表
Exceptions:
throws java.lang.Exception
..
}
从上可见,class文件是个完整的自描述文件,字节码在其中只占了很小的部分,源码编译为class文件后,即可放入jvm中执行。执行时jvm首先要做的是装载class文件,这个机制通常称为类加载机制。
3.1.2 类加载机制
类加载机制是指.class文件加载到JVM,并形成Class对象的机制,之后应用就可对Class对象进行实例化并调用,类加载机制可在运行时 动态加载外部的类、远程网络下载过来的class文件等。除了该动态化的优点外,还可通过JVM的类加载机制来达到类隔离的效果,例如 Application Server中通常要避免两个应用的类互相干扰。
JVM将类加载过程划分为三个步骤:装载、链接和初始化。装载和链接过程完成后,即将二进制的字节码转换为Class对象;初始化过程不是加载类时必须触发的,但最迟必须在初次主动使用对象前执行,其所作的动作为给静态变量赋值、调用<clinit>()等。
整个过程如图3.3所示。
1. 装载(Load)
装载过程负责找到二进制字节码并加载至JVM中,JVM通过类的全限定名(com.bluedavy. HelloWorld)及类加载器(ClassLoaderA实例)完成类的加载,同样,也采用以上两个元素来标识一个被加载了的类:类的全限定 名+ClassLoader实例ID。类名的命名方式如下:
对于接口或非数组型的类,其名称即为类名,此种类型的类由所在的ClassLoader负责加载;
![]() |
图3.3 类加载过程 |
对于数组型的类,其名称为"["+(基本类型或L+引用类型类名;),例如byte[] bytes=new byte[512],该bytes的类名为:[B; Object[] objects=new Object[10],objects的类名则为:[Ljava.lang.Object;,数组型类中的元素类型由所在的ClassLoader负责加 载,但数组类则由JVM直接创建。
2. 链接(Link)
链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量及解析类中调用的接口、类。
二进制字节码的格式校验遵循Java Class File Format(具体请参见JVM规范)规范,如果格式不符合,则抛出VerifyError;校验过程中如果碰到要引用到其他的接口和类,也会进行加载; 如果加载过程失败,则会抛出NoClassDefFoundError。
在完成了校验后,JVM初始化类中的静态变量,并将其值赋为默认值。
最后对类中的所有属性、方法进行验证,以确保其要调用的属性、方法存在,以及具备相应的权限(例如public、private域权限等)。如果这个阶段失败,可能会造成NoSuchMethodError、NoSuchFieldError等错误信息。
3. 初始化(Initialize)
初始化过程即执行类中的静态初始化代码、构造器代码及静态属性的初始化,在以下四种情况下初始化过程会被触发执行:
1)调用了new;
2)反射调用了类中的方法;
3)子类调用了初始化;
4)JVM启动过程中指定的初始化类。
在执行初始化过程之前,首先必须完成链接过程中的校验和准备阶段,解析阶段则不强制。
JVM的类加载通过ClassLoader及其子类来完成,分为Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader及User-Defined ClassLoader。这4种ClassLoader的关系如图3.4所示。
![]() |
图3.4 Sun JDK ClassLoader继承关系 |
1. Bootstrap ClassLoader
Sun JDK采用C++实现了此类,此类并非ClassLoader的子类,在代码中没有办法拿到这个对象,Sun JDK启动时会初始化此ClassLoader,并由ClassLoader完成$JAVA_HOME中jre/lib/rt.jar里所有class文 件的加载,jar中包含了Java规范定义的所有接口及实现。
2. Extension ClassLoader
JVM用此ClassLoader来加载扩展功能的一些jar包,例如Sun JDK中目录下有dns工具jar包等,在Sun JDK中ClassLoader对应的类名为ExtClassLoader。
3. System ClassLoader
JVM用此ClassLoader来加载启动参数中指定的Classpath中的jar包及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。
例如一段这样的代码:
public class ClassLoaderDemo {
public static void main(String[] args) throws Exception{
System.out.println(ClassLoaderDemo.class.getClassLoader());
System.out.println(ClassLoaderDemo.class.getClassLoader().getParent());
System.out.println(ClassLoaderDemo.class.getClassLoader().getParent().getParent());
}
}
执行后显示的信息类似如下:
(sun.misc.Launcher$AppClassLoader)
(sun.misc.Launcher$ExtClassLoader)
null
按照上面的描述,就可看到典型的System ClassLoader、Extension ClassLoader,而由于Bootstrap ClassLoader并不是Java中的ClassLoader,因此Extension ClassLoader的parent为null。
4. User-Defined ClassLoader
User-Defined ClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用 于加载非Classpath中(例如从网络上下载的jar或二进制)的jar及目录、还可以在加载之前对class文件做一些动作,例如解密等。
JVM的ClassLoader采用的是树形结构,除BootstrapClassLoader外,其他的ClassLoader都会有 parent ClassLoader,User-Defined ClassLoader默认的parent ClassLoader为System ClassLoader。加载类时通常按照树形结构的原则来进行,也就是说,首先应从parent ClassLoader中尝试进行加载,当parent中无法加载时,应再尝试从System ClassLoader中进行加载,System ClassLoader同样遵循此原则,在找不到的情况下会自动从其parent ClassLoader中进行加载。值得注意的是,由于JVM是采用类名加Classloader的实例来作为Class加载的判断的,因此加载时不采用 上面的顺序也是可以的,例如加载时不去parent ClassLoader中寻找,而只在当前的ClassLoader中寻找,会造成树上多个不同的ClassLoader中都加载了某Class,并且这 些Class的实例对象都不相同,JVM会保证同一个ClassLoader实例对象中只能加载一次同样名称的Class,因此可借助此来实现类隔离的需 求,但有时也会带来困惑,例如ClassCastException。因此在加载类的顺序上要根据需求合理把握,尽量保证从根到最下层的 ClassLoader上的Class只加载了一次。
ClassLoader抽象类提供了几个关键的方法:
loadClass
此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有,则继续从parent ClassLoader中寻找;如果仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找;如果要改变类的加载顺序,则可覆盖此方法;如果加载顺序相同,则可通过覆盖 findClass来做特殊的处理,例如解密、固定路径寻找等。当通过整个寻找类的过程仍然未获取Class对象时,则抛出 ClassNotFoundException。
如果类需要resolve,则调用resolveClass进行链接。
findLoadedClass
此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。
findClass
此方法直接抛出ClassNotFoundException,因此要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。
findSystemClass
此方法负责从System ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如果仍然未找到,则返回null。
defineClass
此方法负责将二进制的字节码转换为Class对象,这个方法对于自定义加载类而言非常重要。如果二进制的字节码的格式不符合JVM Class文件的格式,则抛出ClassFormatError;如果生成的类名和二进制字节码中的不同,则抛出 NoClassDefFoundError;如果加载的class是受保护的、采用不同签名的,或者类名是以java.开头的,则抛出 SecurityException;如果加载的class在此ClassLoader中已加载,则抛出LinkageError。
resolveClass
此方法负责完成Class对象的链接,如果链接过,则会直接返回。
当Java开发人员调用Class.forName来获取一个对应名称的Class对象时,JVM会从方法栈上寻找第一个ClassLoader, 通常也就是执行Class.forName所在类的ClassLoader,并使用此ClassLoader来加载此名称的类。JVM为了保护加载、执行 的类的安全,它不允许ClassLoader直接卸载加载了的类,只有JVM才能卸载,在Sun JDK中,只有当ClassLoader对象没有引用时,此ClassLoader对象加载的类才会被卸载。
根据上面的描述,在实际的应用中,JVM类加载过程会抛出这样那样的异常,这些情况下掌握各种异常产生的原因是最为重要的,下面来看类加载方面的常见异常。
1. ClassNotFoundException
这是最常见的异常,产生这个异常的原因为在当前的ClassLoader中加载类时未找到类文件,对位于System ClassLoader的类很容易判断,只要加载的类不在Classpath中,而对位于User-Defined ClassLoader的类则麻烦些,要具体查看这个ClassLoader加载类的过程,才能判断此ClassLoader要从什么位置加载到此类。
例如直接在代码中执行Class.forName("com.bluedavy.A"),而当前类的classloader下根本就没有该类所在的jar或没有该class文件,就会抛出ClassNotFoundException。
2. NoClassDefFoundError
该异常较之ClassNotFoundException更难处理一些,造成此异常的主要原因是加载的类中引用到的另外的类不存在,例如要加载A,而A中调用了B,B不存在或当前ClassLoader没法加载B,就会抛出这个异常。
例如有一段这样的代码:
public class A{
private B b = new B();
}
当采用Class.forName加载A时,虽能找到A.class,但此时B.class不存在,则会抛出NoClassDefFoundError。
因此,对于这个异常,须先查看是加载哪个类时报出的,然后再确认该类中引用的类是否存在于当前ClassLoader能加载到的位置。
3. LinkageError
该异常在自定义ClassLoader的情况下更容易出现,主要原因是此类已经在ClassLoader加载过了,重复地加载会造成该异常,因此要注意避免在并发的情况下出现这样的问题。
由于JVM的这个保护机制,使得在JVM中没办法直接更新一个已经load的Class,只能创建一个新的ClassLoader来加载更新的 Class,然后将新的请求转入该ClassLoader中来获取类,这也是JVM中不好实现动态更新的原因之一,而其他更多的原因是对象状态的复制、依 赖的设置等。
4. ClassCastException
该异常有多种原因,在JDK 5支持泛型后,合理使用泛型可相对减少此异常的触发。这些原因中比较难查的是两个A对象由不同的ClassLoader加载的情况,这时如果将其中某个A对象造型成另外一个A对象,也会报出ClassCastException。
发表评论
-
技术文章精华合集(持续更新中)
2016-09-20 19:09 862Kafka深度解析 分库分表系列文章 来自 ... -
<<More Joel on Software>> 如何扮演程序经理的角色
2011-07-03 15:13 846制作伟大软体的秘方之 ... -
<<More Joel on Software>> 利诱管理法
2011-07-03 15:10 1020利诱管理法 Joke: A poor J ... -
<<More Joel on Software>> The Joel Test: 软件开发成功 12 法则
2011-07-03 14:42 750作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 看起来简单, 实际上复杂
2011-07-03 14:41 804作者: 周思博 (Joel Spolsky) 译: Bo Y ... -
<<More Joel on Software>> 膨胀软件与80/20的谣传
2011-07-03 14:38 929作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 每日构建(daily build)是你的朋友
2011-07-03 14:37 928作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 五个为什么
2011-07-03 14:20 1004五个为什么(译文) ... -
<<More Joel on Software>> 飙高音
2011-07-03 13:54 963飙高音(译文) 作者: 阮一峰 日期: 2009年 ... -
<<More Joel on Software>> 关于战略问题的通信之六
2011-07-03 13:51 819关于战略问题的通信之六(译文) 作者: 阮一峰 日 ... -
<<More Joel on Software>> 易用性是不够的
2011-07-03 13:41 799易用性是不够的(译文) 作者: 阮一峰 日期: 2 ... -
<<More Joel on Software>> 军事化管理法
2011-07-03 13:28 1040高科技公司能否采用军事化管理?(译文) 作者: 阮一峰 ... -
<<More Joel on Software>> 寻找优秀的程序员
2011-07-03 13:24 1251=================== 寻找 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第七章 构建可伸缩的系统
2011-05-29 12:09 1165通常将通过升级或增加单机机器的硬件来支撑访问量及数据量增长的方 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第六章 构建高可用的系统
2011-05-28 11:02 1279对于互联网或企业中的大型应用而言,多数要求做到 7*24 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第五章 性能调优(二)
2011-05-22 21:34 11455.2 调优 找出性 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第五章 性能调优(一)
2011-05-22 13:43 11865.1 寻找性能瓶颈 通常性能瓶颈的 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第四章 分布式JAVA应用与JDK类库
2011-05-21 11:23 12284.1 集合包 ArrayList, Li ... -
读书笔记:《分布式JAVA应用 基础与实践》 第三章 3.3 JVM线程资源同步及交互机制(二)
2011-05-17 21:52 980接下来网上没有,貌似 ... -
《分布式JAVA应用 基础与实践》 第三章 3.3 JVM线程资源同步及交互机制(一)
2011-05-17 19:09 9433.3 JVM线程资源同步及交互机制 Java程序采用 ...
相关推荐
#### 第一章:分布式Java应用 **1.1 基于消息方式实现系统间通讯** 在分布式Java应用中,基于消息方式进行系统间通讯是一种常见的策略。这种方式的核心在于系统之间的交互是通过发送消息来完成的,这些消息可以是...
移动操作系统原理与实践——基于Java语言的Android应用开发 目录 基础篇 第1章移动操作系统概论 1.1操作系统的原理与概念 1.1.1隐藏硬件细节 ...第3章第一个Android应用程序 3.1使用Android Studio工具创建项目
分布式智慧养老平台-分布式智慧养老平台的设计与实现代码-java-springboot-基于springboot的分布式架构智慧养老平台项目-代码-源码-项目-系统-毕设-网站 1、技术栈:java,springboot,vue,ajax,maven,mysql,...
分布式架构网上商城-分布式架构网上商城的设计与实现代码-java-springboot-基于springboot的分布式架构网上商城项目-代码-源码-项目-系统-毕设-网站 1、技术栈:java,springboot,vue,ajax,maven,mysql,...
分布式智慧养老平台的设计与实现代码-java-springboot-基于springboot的分布式架构智慧养老平台项目-代码-源码-项目-系统-毕设-网站 1、技术栈:java,springboot,vue,ajax,maven,mysql,MyBatisPlus等 2、系统的...
分布式架构网上商城的设计与实现代码-java-springboot-基于springboot的分布式架构网上商城项目-代码-源码-项目-系统-毕设-网站 1、技术栈:java,springboot,vue,ajax,maven,mysql,MyBatisPlus等 2、系统的实现...
Java语言起源于Sun Microsystems公司的Green项目,最初的目的是为了开发一套适用于家用电器的分布式代码系统,以便实现设备间的互联与控制。起初项目团队考虑使用C++作为开发语言,但由于C++过于复杂且安全性不足,...
- **安全性**:Java拥有内置的安全机制,能够有效地防止恶意代码的执行。 - **面向对象**:Java支持封装、继承和多态等面向对象编程的核心特性。 - **自动内存管理**:Java提供了垃圾回收机制来自动管理内存,减少了...
java-springboot-基于springboot的分布式架构网上商城项目-代码-源码-项目-系统-毕设-网站 1、技术栈:java,springboot,vue,ajax,maven,mysql,MyBatisPlus等 2、系统的实现 用户信息 图片素材 视频素材 摘 要 ...
java-springboot-基于springboot的分布式架构智慧养老平台项目-代码-源码-项目-系统-毕设-网站 1、技术栈:java,springboot,vue,ajax,maven,mysql,MyBatisPlus等 2、系统的实现 用户信息 图片素材 视频素材 摘...
第3章 当一个变成多个——集合框架的基本概念 53 .3.1 讲解 54 3.1.1 集合概述 54 3.1.2 Collection接口 54 3.1.3 泛型(Generics) 56 3.1.4 Map接口 57 3.2 练习 59 3.2.1 创建课程管理系统 59 3.3 小结 ...
### JAVA基础快速入门系列 #### 一、程序设计的基本概念 **1.1 什么是程序设计** ...接下来的部分将更深入地探讨Java编程的具体实践,包括编写第一个Java程序、变量和数据类型的使用、流程控制语句以及数组的操作等。
安装Java Development Kit(JDK)是学习Java的第一步。正确设置环境变量至关重要,因为它们指定了系统如何找到Java工具和库。主要有三个关键的环境变量: 1. **PATH**:确保Java应用程序(如javac和java)可以在...
#### 第3章:对象关系映射 本章进一步深入探讨对象关系映射(ORM)的概念和技术细节,重点介绍了如何在Java应用程序中使用ORM框架(如Hibernate或EclipseLink)来实现数据库操作。这包括实体类的设计原则、数据映射...
### 三、分布式算法基础 #### 3.1 分布式算法定义 分布式算法是指设计用于运行在分布式系统上的算法。这类算法通常比传统的顺序算法更难设计和理解,因为它们必须处理诸如网络延迟、节点故障以及数据一致性等问题...
- **强大的库支持**:Java拥有丰富的第三方库,支持多种分布式技术。 - **成熟的开发工具**:如Eclipse、IntelliJ IDEA等集成开发环境提供了良好的开发体验。 #### 3.2 Java中的分布式技术框架 - **Spring Cloud**...
#### 第三章:运算符,表达式和语句 ##### 3.1 运算符与表达式 - **算术运算符**:加(`+`)、减(`-`)、乘(`*`)、除(`/`)和取模(`%`)。 - **比较运算符**:等于(`==`)、不等于(`!=`)、大于(`>`)、小于(`)、大于等于(`>=...
#### 第三章:面向对象编程基础 **3.1 面向对象的基本概念** - 面向对象编程(OOP)是Java的核心特性之一,主要包括封装、继承和多态。 **3.2 Java类和对象** - **类**:定义了一组对象的属性和行为。 - **对象**:...
第3章 Java EE容器 19 3.1 什么是容器 19 3.2 Tomcat的安装和使用 20 3.3 小结 25 第4章 在Java EE中使用XML 26 4.1 什么是XML 26 4.1.1 理解XML 26 4.1.2 XML的语法 27 4.1.3 XML命名空间 31 4.2 ...