Java虚拟机10-特点

虚拟机

内存布局与对象创建

在这里插入图片描述

从图片中看,一共分为了5大区域,分别是:方法区、堆、栈、本地方法区、程序计数器。

这里我们主要了解下 方法区、堆、 *栈、*这三个区域。

2.方法区:
方法区是一块所有线程共享的内存区域。
需要保存类型信息和常量池。
类型信息
对每个加载的类型,jvm必须在方法区中存储以下类型信息:
一 这个类型的完整有效名
二 这个类型直接父类的完整有效名(除非这个类型是interface或是
java.lang.Object,两种情况下都没有父类)
三 这个类型的修饰符(public,abstract, final的某个子集)
四 这个类型直接接口的一个有序列表

类型名称在java类文件和jvm中都以完整有效名出现。在java源代码中,完整有效名由类的所属包名称加一个".",再加上类名
组成。例如,类Object的所属包为java.lang,那它的完整名称为java.lang.Object,但在类文件里,所有的"."都被
斜杠“/”代替,就成为java/lang/Object。完整有效名在方法区中的表示根据不同的实现而不同。

除了以上的基本信息外,jvm还要为每个类型保存以下信息:
类型的常量池( constant pool)
域(Field)信息
方法(Method)信息
除了常量外的所有静态(static)变量

常量池
jvm为每个已加载的类型都维护一个常量池。常量池就是这个类型用到的常量的一个有序集合,包括实际的常量(string,
integer, 和floating point常量)和对类型,域和方法的符号引用。池中的数据项象数组项一样,是通过索引访问的。
因为常量池存储了一个类型所使用到的所有类型,域和方法的符号引用,所以它在java程序的动态链接中起了核心的作用。

域信息
jvm必须在方法区中保存类型的所有域的相关信息以及域的声明顺序,
域的相关信息包括:
域名
域类型
域修饰符(public, private, protected,static,final volatile, transient的某个子集)

方法信息
jvm必须保存所有方法的以下信息,同样域信息一样包括声明顺序
方法名
方法的返回类型(或 void)
方法参数的数量和类型(有序的)
方法的修饰符(public, private, protected, static, final, synchronized, native, abstract的一个子集)除了abstract和native方法外,其他方法还有保存方法的字节码(bytecodes)操作数栈和方法栈帧的局部变量区的大小
异常表

类变量(
Class Variables
译者:就是类的静态变量,它只与类相关,所以称为类变量
)
类变量被类的所有实例共享,即使没有类实例时你也可以访问它。这些变量只与类相关,所以在方法区中,它们成为类数据在逻辑上的一部分。在jvm使用一个类之前,它必须在方法区中为每个non-final类变量分配空间。

常量(被声明为final的类变量)的处理方法则不同,每个常量都会在常量池中有一个拷贝。non-final类变量被存储在声明它的
类信息内,而final类被存储在所有使用它的类信息内。

对类加载器的引用
jvm必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。

jvm在动态链接的时候需要这个信息。当解析一个类型到另一个类型的引用的时候,jvm需要保证这两个类型的类加载器是相同的。这对jvm区分名字空间的方式是至关重要的。

对Class类的引用
jvm为每个加载的类型(译者:包括类和接口)都创建一个java.lang.Class的实例。而jvm必须以某种方式把Class的这个实例和存储在方法区中的类型数据联系起来。

方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出的错误

jdk1.6和jdk1.7方法区可以理解为永久区。

jdk1.8已经将方法区取消,替代的是元数据区。

jdk1.8的元数据区可以使用参数-XX:MaxMetaspaceSzie设定大小,这是一块堆外的直接内存,与永久区不同,如果不指定大小,默认情况下,虚拟机会耗尽可用系统内存。

3.堆:
用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。在堆中只会存储成员方法的地址,在调用的时候,根据地址去方法区中执行对应的成员方法。

  1. 栈:
    栈生命周期与线程相同。启动一个线程,程序调用函数,栈帧被压入栈中,函数调用结束,相应的是栈帧的出栈。

栈帧由局部变量表,操作数栈,帧数据区组成。

局部变量表:存放的是函数的入参,以及局部变量。

操作数栈:存放调用过程中的计算结果的临时存放区域。

帧数据区:存放的是异常处理表和函数的返回,访问常量池的指针。

对象的创建实际应该是很复杂的。这里我们仅仅站在抽象的角度去解释。我们创建普通对象

Apple apple=new Apple(“甜”)

首先 虚拟机VM 遇到new 这个关键字子 会生成对应的字节码指令 newinstance ,这个就是要创建对象了。

然后根据后面的Apple这个参数 ,去方法区的常量池中 是否能找到对应的 类的信息,如果没有 那么出发类加载流程,如果有则 在堆中 分配内存大小。VM之所以知道要分配多大的内存,是因为在类在编译完后就已经确认了大小。这里 可能会有一个遗憾。我的一个类中如果有ArrayList 大小应该是动态增加的 怎么说是已经固定了大小了?

其实这是因为,对象存储的并不是实际的内存,它只会存储 指向 另一个对象的 内存地址。一般是4个字节大小。意思就是,你这个类中 一个int类型数据 要占4个字节大小。 一个应用类型数据 也是占4个大小。就能计算出 对象 应该要占用的内存大小了。

GC

gc就是垃圾回收。哪些对象需要回收 主要通过可达性分析。即这个对象最终是否被root引用了。

在Java语言中,可作为GC Roots的对象包括下面几种:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象。 方法区中常量引用的对象。

本地方法栈中JNI(即一般说的Native方法)引用的对象。

收集算法有

标记清除

复制算法

标记整理

分代回收

堆中的内存分配和回收,采取分代:

在这里插入图片描述

新对象先在伊甸区 分配。

Class文件结构

任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接 口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。本章中,笔 者只是通俗地将任意一个有效的类或接口所应当满足的格式称为“Class文件格式”,实际上它 并不一定以磁盘文件的形式存在。 Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地 排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎 全部是程序运行的必要数据,没有空隙存在。

虚拟机会把这些信息解析出来包括

全限定名,

简单名称,

符号引用,常量信息等等

存储在方法区的常量池中。

后续字节码指令都是通过 这些信息为基础 进行操作。

类加载与解释执行

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始 化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个 部分统称为连接(Linking),这7个阶段的发生顺序如图7-1所示。

在这里插入图片描述

图7-1中,加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程 必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶 段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。注 意,这里笔者写的是按部就班地“开始”,而不是按部就班地“进行”或“完成”,强调这点是因 为这些阶段通常都是互相交叉地混合式进行的,通常会在一个阶段执行的过程中调用、激活 另外一个阶段。

什么情况下需要开始类加载过程的第一个阶段:加载?Java虚拟机规范中并没有进行强 制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,虚拟机规范则 是严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要 在此之前开始):

1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初 始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字 实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常 量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化, 则需要先触发其初始化。

3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父 类的初始化。

4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个 类),虚拟机会先初始化这个主类。

5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后
的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄 所对应的类没有进行过初始化,则需要先触发其初始化。

“加载”是“类加载”(Class Loading)过程的一个阶段,希望读者没有混淆这两个看起来 很相似的名词。在加载阶段,虚拟机需要完成以下3件事情: 1)通过一个类的全限定名来获取定义此类的二进制字节流。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据 的访问入口。

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息 符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存 都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这 时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将 会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是 数据类型的零值,假设一个类变量的定义为: public static int value=123; 那变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java 方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方 法之中,所以把value赋值为123的动作将在初始化阶段才会执行。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在前一章讲 解Class文件格式的时候已经出现过多次,在Class文件中它以CONSTANT_Class_info、 CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现,那解析阶段中所 说的直接引用与符号引用又有什么关联呢? 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可 以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的 内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各 不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义 在Java虚拟机规范的Class文件格式中。 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是 一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引 用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目 标必定已经在内存中存在。 虚拟机规范之中并未规定解析阶段发生的具体时间,只要求了在执行anewarray、 checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、 invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic这16个用于 操作符号引用的字节码指令之前,先对它们所使用的符号引用进行解析。所以虚拟机实现可 以根据需要来判断到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到 一个符号引用将要被使用前才去解析它。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点 限定符7类符号引用进行,分别对应于常量池的CONSTANT_Class_info、 CONSTANT_Fieldref_info、CONSTANT_Methodref_info、 CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、 CONSTANT_MethodHandle_info和CONSTANT_InvokeDynamic_info 7种常量类型

类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应 用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化 阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。 在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通 过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始 化阶段是执行类构造器<clinit>()方法的过程。

<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块 (static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决 定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前 面的静态语句块可以赋值,但是不能访问,如代码清单7-5中的例子所示。 代码清单7-5 非法向前引用变量 public class Test{ static{ i=0;//给变量赋值可以正常编译通过 System.out.print(i);//这句编译器会提示"非法向前引用" }static int i=1; }<clinit>()方法与类的构造函数(或者说实例构造器<init>()方法)不同,它不 需要显式地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的< clinit>()方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()方法的类肯定 是java.lang.Object。 由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子 类的变量赋值操作,如在代码清单7-6中,字段B的值将会是2而不是1。 代码清单7-6<clinit>()方法执行顺序
static class Parent{ public static int A=1; static{ A=2; }}static class Sub extends Parent{ public static int B=A; }public static void main(String[]args){ System.out.println(Sub.B); }<clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也 没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会 生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行 父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另
外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多 个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他 线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

类加载器:

从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器 (Bootstrap ClassLoader),这个类加载器使用C++语言实现[1],是虚拟机自身的一部分;另 一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且 全都继承自抽象类java.lang.ClassLoader。

启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类将器负责将存放在< JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机 识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载) 类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加 载器时,如果需要把加载请求委派给引导类加载器,那直接使用null代替即可。我们一般使用的类都是 它加载的。

扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher $ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系 统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher $App- ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回 值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类 库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一 般情况下这个就是程序中默认的类加载器。

虚拟机字节码执行引擎

执行引擎是Java虚拟机最核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概 念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、硬 件、指令集和操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制 定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。

方法调用 方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本 (即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。在程序运行时,进行方法 调用是最普遍、最频繁的操作,但前面已经讲过,Class文件的编译过程中不包含传统编译中 的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行 时内存布局中的入口地址(相当于之前说的直接引用)。这个特性给Java带来了更强大的动 态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运 行期间才能确定目标方法的直接引用。

所有方法调用中的目标方法在Class文件里面都是一个常 量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用

这 种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方 法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编 译时就必须确定下来。这类方法的调用称为解析(Resolution)。

在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法和 私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决 定了它们都不可能通过继承或别的方式重写其他版本,因此它们都适合在类加载阶段进行解 析。

方法 分派

1.静态分派

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用 是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执 行的。另外,编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不 是“唯一的”,往往只能确定一个“更加合适的”版本。这种模糊的结论在由0和1构成的计算机 世界中算是比较“稀罕”的事情,产生这种模糊结论的主要原因是字面量不需要定义,所以字 面量没有显式的静态类型,它的静态类型只能通过语言上的规则去理解和推断。

2.动态分派

了解了静态分派,我们接下来看一下动态分派的过程,它和多态性的另外一个重要体 现[3]——重写(Override)有着很密切的关联。

代码在内存中运行过程

在这里插入图片描述

从上图我们看到了一个程序在内存中执行的过程。

上图的执行流程:

1.从 disk 中将 MainApp.class 加载到 jvm 的方法区中。

2.执行 main 方法,将该 main 方法中包含的变量和函数,压到栈中。

3.开始执行 main 方法中的指令,创建一个 animal 对象, 将 new 出来的 animal 对象存储到堆中,animal 引用指向堆中的 animal 对象,堆中的 animal 对象指向方法区中的 Animal 类。

4.继续执行 main 方法中的指令,调用 animal 对象中的 printName() 方法,这时 animal 应用调用 animal 对象, animal 对象找到方法区的 Animal 类中的 printName() 字节码信息,根据该描述信息,开始执行 printName方法。

当然实际可能更加复杂。

在这里插入图片描述

从左侧我们看到有两个类,按照Java程序的执行流程,会把这两个类编译成 .class 文件,即图中最右边的 Phone.class he Demo01PhoneOne.class。

首先程序开始执行是从 main() 方法开始,这个时候会把 main() 方法压到栈中,main() 方法中的第一句代码是先创建一个 Phone 对象,当我们 new 一个对象时,会把 new 出来的对象放到堆中,相对应的给这个对象分配一个地址值,在栈中会产生一个实例 one 会指向这个地址,可以看到堆中的对象包含了自身的成员变量和成员方法的引用。

接着继续执行下面的代码,直接打印对象的属性值,由于对象属性没有进行赋值,所以输出的都是对应数据类型的默认值。 继续下面的操作,就是给对象的属性进行赋值,由于 one 是指向了对象,所以直接可以进行操作,这时在堆中的属性值就会被赋予对应的值了。再次打印的时候就会打印出对应的值。

再到后面,继续调用了对象的成员方法,这个时候需要先在堆中找到这个成员方法的应用,然后找到方法区中将对应的代码压到栈中,继续执行。调用方法会传入对应的参数,也是放到栈中的,执行完这个方法之后,压到栈中的这一部分代码就会出栈,直到 main() 方法中所有的代码执行完,栈中的内容也就全部消失,内存也就随之释放。

总结

分清什么是实例什么是对象。Class a= new Class(); 此时 a 叫实例,而不能说 a 是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁,而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

以上的栈、堆、代码段、数据段等等都是相对于应用程序而言的。每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,互不影响。并且这些内存区域是所有线程共享的。这里提到的栈和堆都是整体上的概念,这些堆栈还可以细分。

类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套,对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。

对象类型作为方法的参数或者方法的返回值时,传递的都是对象的地址值。再其他地方修改这个对象的属性值时,原有的值就会被覆盖掉。

本文,仅作为自己的学习笔记记录,参考资料

Java程序在内存中运行详解

《深入理解Java虚拟机第二版》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.下列特点中,哪个是Java 虚拟机执行特点之一? A、字节代码 B、多进程 C、静态连接 D、编译 2.Java语言的类型是 A、面向对象语言 B、面向过程语言 C、汇编程序 D、形式语言 3.下列哪个数代表单精度浮点数? A、0652 B、3.4457D C、0.298f D、0L 4.以下哪个表达式是不合法的 A、String x="Hello"; int y=9; x+=y; B、String x="Hello"; int y=9; if(x==y){} C、String x="Hello"; int y=9; x=x+y; D、String x=null; int y=(x!=null)&&(x.length()>0)? x.length():0; 5.main()方法的返回类型是: A、 int B、void C、boolean D、 static 6.下列对继承的说法,哪个是正确的? A、子类能继承父类的所有方法和状态 B、子类能继承父类的非私有方法和状态 C、子类只能继承父类public方法和状态 D、子类只能继承父类的方法,而不继承状态 7.下列关于构造方法的说法正确的是( )。 A、因为构造方法名与类名必须相同,构造方法在创建对象时被执行。 B、构造方法是给对象赋初值,所以没有返回值,必须用void来声明。 C、构造方法不能被程序调用,也不可以重载。 D、一个类只允许有一个构造方法。 8.若需要定义一个类域或类方法,应使用哪种修饰符? A、static B、package C、private D、public 9.如要抛出异常,应用下列哪种子句? A、catch B、throw C、try D、finally 10.对于catch 子句的排列,下列哪种是正确的? A、父类在先,子类在后 B、子类在先,父类在后 C、有继承关系的异常不能在同一个 try 程序段内 D、先有子类,其他如何排列都无关 11.计算机中的流是? A、流动的字节 B、流动的对象 C、流动的文件 D、流动的数据缓冲区 12.以下哪个是java.io包中的一个兼有输入输出功能的类。 A、Object B、Serializable C、RandomAccessFile D、java.io中不存在这样的类 13.下面关于线程优先级的说法中,错误的是: A、Java中的线程的优先级有三个静态常量 B、新建线程的优先级默认为最低 C、优先级高的线程优先被执行 D、一个线程运行时,有可能被比它高优先级的线程抢占运行 14.下列方法中建议使用的是: A、stop() B、supend() C、resume() D、wait() 15.如果希望所有的控件在界面上均匀排序,就使用下列哪种布局管理器? A、BosLayout B、GridLayout C、BorderLayout D、FlowLayout 16.在一个MVC用户界面中,存在三个通讯对象,其中_________是指定的逻辑表示法。 A、模型 B、视图 C、控件 D、结构 17.applet类的直接父类是: A、Component类 B、Container类 C、Frame类 D、Panel类 18.下面关于沙箱模型的叙述中错误的是

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值