- 浏览: 98072 次
- 性别:
- 来自: 深圳
文章分类
最新评论
3.1.3 类执行机制
在完成将class文件信息加载到JVM并产生Class对象后,就可执行Class对象的静态方法或实例化对象进行调用了。在源码编译阶段将源码 编译为JVM字节码,JVM字节码是一种中间代码的方式,要由JVM在运行期对其进行解释并执行,这种方式称为字节码解释执行方式。
字节码解释执行
由于采用的为中间码的方式,JVM有一套自己的指令,对于面向对象的语言而言,最重要的是执行方法的指令,JVM采用了invokestatic、 invokevirtual、invokeinterface和invokespecial四个指令来执行不同的方法调用。invokestatic对应 的是调用static方法,invokevirtual对应的是调用对象实例的方法,invokeinterface对应的是调用接口的方 法,invokespecial对应的是调用private方法和编译源码后生成的<init>方法,此方法为对象实例化时的初始化方法,例 如下面一段代码:
public class Demo{
public void execute(){
A.execute();
A a = new A();
a.bar();
IFoo b = new B();
b.bar();
}
}
class A{
public static int execute(){
return 1+2;
}
public int bar(){
return 1+2;
}
}
class B implements IFoo{
public int bar(){
return 1+2;
}
}
public interface IFoo{
public int bar();
}
通过javac编译上面的代码后,使用javap -c Demo查看其execute方法的字节码:
public void execute();
Code:
0: invokestatic #2; //Method A.execute:()I
3: pop
4: new #3; //class A
7: dup
8: invokespecial #4; //Method A." < init > ":()V
11: astore_1
12: aload_1
13: invokevirtual #5; //Method A.bar:()I
16: pop
17: new #6; //class B
20: dup
21: invokespecial #7; //Method B." < init > ":()V
24: astore_2
25: aload_2
26: invokeinterface #8, 1; //InterfaceMethod IFoo.bar:()I
31: pop
32: return
从以上例子可看到invokestatic、invokespecial、invokevirtual及invokeinterface四种指令对应调用方法的情况。
Sun JDK基于栈的体系结构来执行字节码,基于栈方式的好处为代码紧凑,体积小。
线程在创建后,都会产生程序计数器(PC)(或称为PC registers)和栈(Stack);PC存放了下一条要执行的指令在方法内的偏移量;栈中存放了栈帧(StackFrame),每个方法每次调用都 会产生栈帧。栈帧主要分为局部变量区和操作数栈两部分,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果,栈 帧中还会有一些杂用空间,例如指向方法已解析的常量池的引用、其他一些VM内部实现需要的数据等,结构如图3.5所示。
图3.5 Sun JDK 基于栈的体系结构 |
下面来看一个方法执行时过程的例子,代码如下:
public class Demo(){
public static void foo(){
int a = 1 ;
int b = 2 ;
int c =(a+b) * 5;
}
}
编译以上代码后,foo方法对应的字节码为以及相应的解释如下:
public static void foo();
Code:
0: iconst_1 //将类型为int、值为1的常量放入操作数栈;
1: istore_0 //将操作数栈中栈顶的值弹出放入局部变量区;
2: iconst_2 //将类型为int、值为2的常量放入操作数栈;
3: istore_1 //将操作数栈中栈顶的值弹出放入局部变量区;
4: iload_0 //装载局部变量区中的第一个值到操作数栈;
5: iload_1 //装载局部变量区中的第二个值到操作数栈;
6: iadd //执行int类型的add指令,并将计算出的结果放入操作数栈;
7: iconst_5 //将类型为int、值为5的常量放入操作数栈;
8: imul //执行int类型的mul指令,并将计算出的结果放入操作数栈;
9: istore_2 //将操作数栈中栈顶的值弹出放入局部变量区;
10: return // 返回
1. 指令解释执行
对于方法的指令解释执行,执行方式为经典冯·诺依曼体系中的FDX循环方式,即获取下一条指令,解码并分派,然后执行。在实现FDX循环时有 switch-threading、token- threading、direct-threading、subroutine-threading、inline-threading等多种方式 。
switch-threading是一种最简方式的实现,代码大致如下:
while(true){
int code = fetchNextCode ();
switch(code){
case IADD:
// do add
case …:
// do sth.
}
}
以上方式很简单地实现了FDX的循环方式,但这种方式的问题是每次执行完都得重新回到循环开始点,然后重新获取下一条指令,并继续switch,这导致了大部分时间都花费在了跳转和获取下一条指令上,而真正业务逻辑的代码非常短。
token-threading在上面的基础上稍做了改进,改进后的代码大致如下:
IADD:{
// do add;
fetchNextCode();
dispatch();
}
ICONST_0:{
push(0);
fetchNextCode();
dispatch();
}
这种方式较之switch-threading方式而言,冗余了fetch和dispatch,消耗的内存量会大一些,但由于去除了switch,因此性能会稍好一些。
其他direct-threading、inline-threading等做了更多的优化,Sun JDK的重点为编译成机器码,并没有在解释器上做太复杂的处理,因此采用了token-threading方式。为了让解释执行能更加高效,Sun JDK还做了一些其他的优化,主要有:栈顶缓存(top-of-stack caching)和部分栈帧共享。
2. 栈顶缓存
在方法的执行过程中,可看到有很多操作要将值放入操作数栈,这导致了寄存器和内存要不断地交换数据,Sun JDK采用了一个栈顶缓存,即将本来位于操作数栈顶的值直接缓存在寄存器上,这对于大部分只需要一个值的操作而言,无须将数据放入操作数栈,可直接在寄存 器计算,然后放回操作数栈。
3. 部分栈帧共享
当一个方法调用另外一个方法时,通常传入另一方法的参数为已存放在操作数栈的数据。Sun JDK在此做了一个优化,就是当调用方法时,后一方法可将前一方法的操作数栈作为当前方法的局部变量,从而节省数据copy带来的消耗。
另外,在解释执行时,对于一些特殊的情况会直接执行机器指令,例如Math.sin、Unsafe. compareAndSwapInt等。
编译执行
解释执行的效率较低,为提升代码的执行性能,Sun JDK提供将字节码编译为机器码的支持,编译在运行时进行,通常称为JIT编译器。Sun JDK在执行过程中对执行频率高的代码进行编译,对执行不频繁的代码则继续采用解释的方式,因此Sun JDK又称为Hotspot VM,在编译上Sun JDK提供了两种模式:client compiler(-client)和server compiler(-server)。
client compiler又称为C1 ,较为轻量级,只做少量性能开销比高的优化,它占用内存较少,适合于桌面交互式应用。在寄存器分配策略上,JDK 6以后采用的为线性扫描寄存器分配算法 ,在其他方面的优化主要有:方法内联、去虚拟化、冗余削除等。
1. 方法内联
对于Java类面向对象的语言,通常要调用多个方法来完成功能。执行时,要经历多次参数传递、返回值传递及跳转等,于是C1 采取了方法内联的方式,即把调用到的方法的指令直接植入当前方法中。
例如一段这样的代码:
public void bar(){
…
bar2();
…
}
private void bar2(){
// bar2
}
当编译时,如bar2代码编译后的字节数小于等于35字节 ,那么,会演变成类似这样的结构 :
public void bar(){
…
// bar2
…
}
可在debug版本的JDK的启动参数加上-XX:+PrintInlining来查看方法内联的信息。
方法内联除带来以上好处外,还能够辅助进行以下冗余削除等优化。
2. 去虚拟化
去虚拟化是指在装载class文件后,进行类层次的分析,如发现类中的方法只提供一个实现类,那么对于调用了此方法的代码,也可进行方法内联,从而提升执行的性能。
例如一段这样的代码:
public interface IFoo{
public void bar();
}
public class Foo implements IFoo{
public void bar(){
// Foo bar method
}
}
public class Demo{
public void execute(IFoo foo){
foo.bar();
}
}
当整个JVM中只有Foo实现了IFoo接口,Demo execute方法被编译时,就演变成类似这样的结构:
public void execute(){
// Foo bar method
}
3. 冗余削除
冗余削除是指在编译时,根据运行时状况进行代码折叠或削除。
例如一段这样的代码:
private static final Log log = LogFactory .getLog("BLUEDAVY");
private static final boolean isDebug = log .isDebugEnabled();
public void execute(){
if(isDebug){
log.debug("enter this method: execute");
}
// do something
}
如log.isDebugEnabled返回的为false,在执行C1编译后,这段代码就演变成类似下面的结构:
public void execute(){
// do something
}
这是为什么会在有些代码编写规则上写不要直接调用log.debug,而要先判断的原因。
Server compiler又称为C2 ,较为重量级,C2采用了大量的传统编译优化技巧来进行优化,占用内存相对会多一些,适合于服务器端的应用。和C1不同的主要是寄存器分配策略及优化的范 围,寄存器分配策略上C2采用的为传统的图着色寄存器分配算法 ;由于C2会收集程序的运行信息,因此其优化的范围更多在于全局的优化,而不仅仅是一个方法块的优化。收集的信息主要有:分支的跳转/不跳转的频率、某条 指令上出现过的类型、是否出现过空值、是否出现过异常。
逃逸分析 是C2进行很多优化的基础,逃逸分析是指根据运行状况来判断方法中的变量是否会被外部读取。如不会则认为此变量是逃逸的,基于逃逸分析C2在编译时会做标量替换、栈上分配和同步削除等优化。
1. 标量替换
标量替换的意思简单来说就是用标量替换聚合量。
例如有这么一段代码:
Point point = new Point(1,2);
System.out.println(" point.x = "+point.x+" ; point.y ="+point.y);
当point对象在后面的执行过程中未用到时,经过编译后,代码会变成类似下面的结构:
int x = 1 ;
int y = 2 ;
System.out.println(" point.x = "+x+" ; point.y ="+y);
之后基于此可以继续做冗余削除。
这种方式能带来的好处是,如果创建的对象并未用到其中的全部变量,则可以节省一定的内存。而对于代码执行而言,由于无须去找对象的引用,也会更快一些。
2. 栈上分配
在上面的例子中,如果p没有逃逸,那么C2会选择在栈上直接创建Point对象实例,而不是在JVM堆上。在栈上分配的好处一方面是更加快速,另一方面是回收时随着方法的结束,对象也被回收了,这也是栈上分配的概念。
3. 同步削除
同步削除是指如果发现同步的对象未逃逸,那也没有同步的必要了,在C2编译时会直接去掉同步。
例如有这么一段代码:
Point point = new Point(1,2);
synchronized(point){
// do something
}
经过分析如果发现point未逃逸,在编译后,代码就会变成下面的结构:
Point point = new Point(1,2);
// do something
除了基于逃逸分析的这些外,C2还会基于其拥有的运行信息来做其他的优化,例如编译分支频率执行高的代码等。
运行后C1、C2编译出来的机器码如果不再符合优化条件,则会进行逆优化(deoptimization),也就是回到解释执行的方式,例如基于类层次分析编译的代码,当有新的相应的接口实现类加入时,就执行逆优化。
除了C1、C2外,还有一种较为特殊的编译为:OSR(On Stack Replace) 。OSR编译和C1、C2最主要的不同点在于OSR编译只替换循环代码体的入口,而C1、C2替换的是方法调用的入口,因此在OSR编译后会出现的现象是 方法的整段代码被编译了,但只有在循环代码体部分才执行编译后的机器码,其他部分则仍然是解释执行方式。
默认情况下,Sun JDK根据机器配置来选择client或server模式,当机器配置CPU超过2核且内存超过2GB即默认为server模式,但在32位 Windows机器上始终选择的都是client模式时,也可在启动时通过增加-client或-server来强制指定,在JDK 7中可能会引入多层编译的支持。多层编译是指在-server的模式下采用如下方式进行编译:
解释器不再收集运行状况信息,只用于启动并触发C1编译;
C1编译后生成带收集运行信息的代码;
C2编译,基于C1编译后代码收集的运行信息来进行激进优化,当激进优化的假设不成立时,再退回使用C1编译的代码。
从以上介绍来看,Sun JDK为提升程序执行的性能,在C1和C2上做了很多的努力,其他各种实现的JVM也在编译执行上做了很多的优化,例如在IBM J9、Oracle JRockit中做了内联、逃逸分析等 。Sun JDK之所以未选择在启动时即编译成机器码,有几方面的原因:
1)静态编译并不能根据程序的运行状况来优化执行的代码,C2这种方式是根据运行状况来进行动态编译的,例如分支判断、逃逸分析等,这些措施会对提升程序执行的性能会起到很大的帮助,在静态编译的情况下是无法实现的。给C2收集运行数据越长的时间,编译出来的代码会越优;
2)解释执行比编译执行更节省内存;
3)启动时解释执行的启动速度比编译再启动更快。
但程序在未编译期间解释执行方式会比较慢,因此需要取一个权衡值,在Sun JDK中主要依据方法上的两个计数器是否超过阈值,其中一个计数器为调用计数器,即方法被调用的次数;另一个计数器为回边计数器,即方法中循环执行部分代 码的执行次数。下面将介绍两个计数器对应的阈值。
CompileThreshold
该值是指当方法被调用多少次后,就编译为机器码。在client模式下默认为1 500次,在server模式下默认为10 000次,可通过在启动时添加-XX:CompileThreshold=10 000来设置该值。
OnStackReplacePercentage
该值为用于计算是否触发OSR编译的阈值,默认情况下client模式时为933,server模式下为140,该值可通过在启动时添加-XX: OnStackReplacePercentage=140来设置,在client模式时,计算规则为CompileThreshold * (OnStackReplacePercentage/100),在server模式时,计算规则为(CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage))/100。InterpreterProfilePercentage的默认值为33,当 方法上的回边计数器到达这个值时,即触发后台的OSR编译,并将方法上累积的调用计数器设置为CompileThreshold的值,同时将回边计数器设 置为CompileThreshold/2的值,一方面是为了避免OSR编译频繁触发;另一方面是以便当方法被再次调用时即触发正常的编译,当累积的回边 计数器的值再次达到该值时,先检查OSR编译是否完成。如果OSR编译完成,则在执行循环体的代码时进入编译后的代码;如果OSR编译未完成,则继续把当 前回边计数器的累积值再减掉一些,从这些描述可看出,默认情况下对于回边的情况,server模式下只要回边次数达到10 700次,就会触发OSR编译。
用以下一段示例代码来模拟编译的触发。
public class Foo{
public static void main(String[] args){
Foo foo = new Foo();
for(int i = 0 ;i < 10 ;i++){
foo.bar();
}
}
public void bar(){
// some bar code
for(int i = 0 ;i < 10700 ;i++){
bar2();
}
}
private void bar2(){
// bar2 method
}
}
以上代码采用java -server方式执行,当main中第一次调用foo.bar时,bar方法上的调用计数器为1,回边计数器为0;当bar方法中的循环执行完毕 时,bar方法的调用计数器仍然为1,回边计数器则为10 700,达到触发OSR编译的条件,于是触发OSR编译,并将bar方法的调用计数器设置为10 000,回边计数器设置为5 000。
当main中第二次调用foo.bar时,jdk发现bar方法的调用次数已超过compileThreshold,于是在后台执行JIT编译,并 继续解释执行// some bar code,进入循环时,先检查OSR编译是否完成。如果完成,则执行编译后的代码,如果未编译完成,则继续解释执行。
当main中第三次调用foo.bar时,如果此时JIT编译已完成,则进入编译后的代码;如果编译未完成,则继续按照上面所说的方式执行。
由于Sun JDK的这个特性,在对Java代码进行性能测试时,要尤其注意是否事先做了足够次数的调用,以保证测试是公平的;对于高性能的程序而言,也应考虑在程序提供给用户访问前,自行进行一定的调用,以保证关键功能的性能。
反射执行
反射执行是Java的亮点之一,基于反射可动态调用某对象实例中对应的方法、访问查看对象的属性等,无需在编写代码时就确定要创建的对象。这使得 Java可以很灵活地实现对象的调用,例如MVC框架中通常要调用实现类中的execute方法,但框架在编写时是无法知道实现类的。在Java中则可以 通过反射机制直接去调用应用实现类中的execute方法,代码示例如下:
Class actionClass =Class.forName(外部实现类);
Method method = actionClass .getMethod("execute",null);
Object action = actionClass .newInstance();
method.invoke(action,null);
这种方式对于框架之类的代码而言非常重要,反射和直接创建对象实例,调用方法的最大不同在于创建的过程、方法调用的过程是动态的。这也使得采用反射 生成执行方法调用的代码并不像直接调用实例对象代码,编译后就可直接生成对对象方法调用的字节码,而是只能生成调用JVM反射实现的字节码了。
要实现动态的调用,最直接的方法就是动态生成字节码,并加载到JVM中执行,Sun JDK采用的即为这种方法,来看看在Sun JDK中以上反射代码的关键执行过程。
Class actionClass =Class.forName(外部实现类);
调用本地方法,使用调用者所在的ClassLoader来加载创建出的Class对象;
Method method = actionClass .getMethod("execute",null);
校验Class是否为public类型,以确定类的执行权限,如不是public类型的,则直接抛出SecurityException。
调用privateGetDeclaredMethods来获取Class中的所有方法,在privateGetDeclaredMethods对Class中所有方法集合做了缓存,第一次会调用本地方法去获取。
扫描方法集合列表中是否有相同方法名及参数类型的方法,如果有,则复制生成一个新的Method对象返回;如果没有,则继续扫描父类、父接口中是否有该方法;如果仍然没找到方法,则抛出NoSuchMethodException,代码如下:
Object action = actionClass .newInstance();
校验Class是否为public类型,如果权限不足,则直接抛出SecurityException。
如果没有缓存的构造器对象,则调用本地方法获取构造器,并复制生成一个新的构造器对象,放入缓存;如果没有空构造器,则抛出InstantiationException。
校验构造器对象的权限。
执行构造器对象的newInstance方法。
判断构造器对象的newInstance方法是否有缓存的ConstructorAccessor对象,如果没有,则调用sun.reflect.ReflectionFactory生成新的ConstructorAccessor对象。
判断sun.reflect.ReflectionFactory是否需要调用本地代码,可通过 sun.reflect.noInflation=true来设置为不调用本地代码。在不调用本地代码的情况下,可转交给 MethodAccessorGenerator来处理。本地代码调用的情况在此不进行阐述。
MethodAccessorGenerator中的generate方法根据Java Class格式规范生成字节码,字节码中包括ConstructorAccessor对象需要的newInstance方法。该newInstance方 法对应的指令为invokespecial,所需参数则从外部压入,生成的Constructor类的名字以sun/reflect/ GeneratedSerializationConstructorAccessor或sun/reflect /GeneratedConstructorAccessor开头,后面跟随一个累计创建对象的次数。
在生成字节码后将其加载到当前的ClassLoader中,并实例化,完成ConstructorAccessor对象的创建过程,并将此对象放入构造器对象的缓存中。
执行获取的constructorAccessor.newInstance,这步和标准的方法调用没有任何区别。
method.invoke(action,null);
这步的执行过程和上一步基本类似,只是在生成字节码时方法改为了invoke,其调用目标改为了传入对象的方法,同时类名改为了:sun/reflect/GeneratedMethodAccessor。
综上所述,执行一段反射执行的代码后,在debug里查看Method对象中的MethodAccessor对象引用(参数为-Dsun.reflect.noInflation=true,否则要默认执行15次反射调用后才能动态生成字节码),如图3.6所示:
(点击查看大图)图3.6 反射执行代码示例 |
Sun JDK采用以上方式提供反射的实现,提升代码编写的灵活性,但也可以看出,其整个过程比直接编译成字节码的调用复杂很多,因此性能比直接执行的慢一些。 Sun JDK中反射执行的性能会随着JDK版本的提升越来越好,到JDK 6后差距就不大了,但要注意的是,getMethod相对比较耗性能,一方面是权限的校验,另一方面是所有方法的扫描及Method对象的复制,因此在使 用反射调用多的系统中应缓存getMethod返回的Method对象,而method.invoke的性能则仅比直接调用低一点。一段对比直接执行、反 射执行性能的程序如下所示:
// Server OSR编译阈值:10700
private static final int WARMUP_COUNT=10700;
private ForReflection testClass=new ForReflection();
private static Method method=null;
public static void main(String[] args) throws Exception{
method=ForReflection.class.getMethod
("execute",new Class<?>[]{String.class});
Demo demo=new Demo();
// 保证反射能生成字节码及相关的测试代码能够被JIT编译
for (int i = 0; i < 20; i++) {
demo.testDirectCall();
demo.testCacheMethodCall();
demo.testNoCacheMethodCall();
}
long beginTime=System.currentTimeMillis();
demo.testDirectCall();
long endTime=System.currentTimeMillis();
System.out.println("直接调用消耗的时间为:"+
(endTime-beginTime)+"毫秒");
beginTime=System.currentTimeMillis();
demo.testNoCacheMethodCall();
endTime=System.currentTimeMillis();
System.out.println("不缓存Method,反射调用消耗的时间为:
"+(endTime-beginTime)+"毫秒");
beginTime=System.currentTimeMillis();
demo.testCacheMethodCall();
endTime=System.currentTimeMillis();
System.out.println("缓存Method,反射调用
消耗的时间为:"+(endTime-beginTime)+"毫秒");
}
public void testDirectCall(){
for (int i = 0; i < WARMUP_COUNT; i++) {
testClass.execute("hello");
}
}
public void testCacheMethodCall() throws Exception{
for (int i = 0; i < WARMUP_COUNT; i++) {
method.invoke(testClass, new Object[]{"hello"});
}
}
public void testNoCacheMethodCall() throws Exception{
for (int i = 0; i < WARMUP_COUNT; i++) {
Method testMethod=ForReflection.class.
getMethod("execute",new Class<?>[]{String.class});
testMethod.invoke(testClass, new Object[]{"hello"});
}
}
public class ForReflection {
private Map<String, String> caches=new
HashMap<String, String>();
public void execute(String message){
String b=this.toString()+message;
caches.put(b, message);
}
}
执行后显示的性能如下(执行环境: Intel Duo CPU E8400 3G, windows 7, Sun JDK 1.6.0_18,启动参数为-server -Xms128M -Xmx128M):
直接调用消耗的时间为5毫秒;
不缓存Method,反射调用消耗的时间为11毫秒;
缓存Method,反射调用消耗的时间为6毫秒。
在启动参数上增加-Xint来禁止JIT编译,执行上面代码,结果为:
直接调用消耗的时间为133毫秒;
不缓存Method,反射调用消耗的时间为215毫秒;
缓存Method,反射调用消耗的时间为150毫秒。
对比这段测试结果也可看出,C2编译后代码的执行速度得到了大幅提升。
发表评论
-
技术文章精华合集(持续更新中)
2016-09-20 19:09 789Kafka深度解析 分库分表系列文章 来自 ... -
<<More Joel on Software>> 如何扮演程序经理的角色
2011-07-03 15:13 830制作伟大软体的秘方之 ... -
<<More Joel on Software>> 利诱管理法
2011-07-03 15:10 1003利诱管理法 Joke: A poor J ... -
<<More Joel on Software>> The Joel Test: 软件开发成功 12 法则
2011-07-03 14:42 730作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 看起来简单, 实际上复杂
2011-07-03 14:41 791作者: 周思博 (Joel Spolsky) 译: Bo Y ... -
<<More Joel on Software>> 膨胀软件与80/20的谣传
2011-07-03 14:38 916作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 每日构建(daily build)是你的朋友
2011-07-03 14:37 896作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 五个为什么
2011-07-03 14:20 982五个为什么(译文) ... -
<<More Joel on Software>> 飙高音
2011-07-03 13:54 945飙高音(译文) 作者: 阮一峰 日期: 2009年 ... -
<<More Joel on Software>> 关于战略问题的通信之六
2011-07-03 13:51 800关于战略问题的通信之六(译文) 作者: 阮一峰 日 ... -
<<More Joel on Software>> 易用性是不够的
2011-07-03 13:41 776易用性是不够的(译文) 作者: 阮一峰 日期: 2 ... -
<<More Joel on Software>> 军事化管理法
2011-07-03 13:28 1014高科技公司能否采用军事化管理?(译文) 作者: 阮一峰 ... -
<<More Joel on Software>> 寻找优秀的程序员
2011-07-03 13:24 1236=================== 寻找 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第七章 构建可伸缩的系统
2011-05-29 12:09 1142通常将通过升级或增加单机机器的硬件来支撑访问量及数据量增长的方 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第六章 构建高可用的系统
2011-05-28 11:02 1243对于互联网或企业中的大型应用而言,多数要求做到 7*24 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第五章 性能调优(二)
2011-05-22 21:34 11235.2 调优 找出性 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第五章 性能调优(一)
2011-05-22 13:43 11625.1 寻找性能瓶颈 通常性能瓶颈的 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第四章 分布式JAVA应用与JDK类库
2011-05-21 11:23 12114.1 集合包 ArrayList, Li ... -
读书笔记:《分布式JAVA应用 基础与实践》 第三章 3.3 JVM线程资源同步及交互机制(二)
2011-05-17 21:52 963接下来网上没有,貌似 ... -
《分布式JAVA应用 基础与实践》 第三章 3.3 JVM线程资源同步及交互机制(一)
2011-05-17 19:09 9213.3 JVM线程资源同步及交互机制 Java程序采用 ...
相关推荐
#### 第二章:大型分布式Java应用 **2.1 服务组件架构 (SCA)** 服务组件架构 (Service Component Architecture, SCA) 是一种标准,旨在简化企业应用程序的开发和服务组合。它提供了一个统一的编程模型,使得开发者...
移动操作系统原理与实践——基于Java语言的Android应用开发 目录 基础篇 第1章移动操作系统概论 1.1操作系统的原理与概念 1.1.1隐藏硬件细节 1.1.2资源管理 1.1.3操作系统的历史 1.2操作系统的分类 1.2.1...
分布式智慧养老平台-分布式智慧养老平台的设计与实现代码-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拥有内置的安全机制,能够有效地防止恶意代码的执行。 - **面向对象**:Java支持封装、继承和多态等面向对象编程的核心特性。 - **自动内存管理**:Java提供了垃圾回收机制来自动管理内存,减少了...
Java语言起源于Sun Microsystems公司的Green项目,最初的目的是为了开发一套适用于家用电器的分布式代码系统,以便实现设备间的互联与控制。起初项目团队考虑使用C++作为开发语言,但由于C++过于复杂且安全性不足,...
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程序、变量和数据类型的使用、流程控制语句以及数组的操作等。
通过以上章节的详细介绍,可以看出《Beginning Java™ EE 6 Platform with GlassFish™ 3 第二版》不仅覆盖了Java EE 6平台的核心技术,还包含了大量实用的代码示例和最佳实践建议。对于希望深入了解Java EE开发的...
### 三、分布式算法基础 #### 3.1 分布式算法定义 分布式算法是指设计用于运行在分布式系统上的算法。这类算法通常比传统的顺序算法更难设计和理解,因为它们必须处理诸如网络延迟、节点故障以及数据一致性等问题...
- **强大的库支持**:Java拥有丰富的第三方库,支持多种分布式技术。 - **成熟的开发工具**:如Eclipse、IntelliJ IDEA等集成开发环境提供了良好的开发体验。 #### 3.2 Java中的分布式技术框架 - **Spring Cloud**...
1. 3 Java与Internet 1. 4 Java发展简史 1.5关于Java的常见误解 第2章Java程序设计环境 2.1安装Java开发工具箱 2.1.1下载JDK 2.1.2设置执行路径 2.1.3安装库源代码和文档 2.1.4安装本书中的示例 ...
第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 ...
第3章 DNS的设置23 3.1 DNS解析23 3.2 Windows 7设置DNS服务器24 3.3 Windows设置本机域名和IP的对应关系25 第4章 Java中Socket的用法26 4.1 普通Socket的用法26 4.2 NioSocket的用法28 第5章 自己动手实现...