内容全部来自深入理解java虚拟机,理解能力有限,可能有错误,只是个人笔记,防止忘了
类加载过程:
package org.gerry.classLoader; /** * -XX:+TraceClassLoading会打印出类加载的过程 * 加载阶段完成三件事: * 1. 根据类的全限定名获取二进制字节流 * 2. 将二进制字节流代表静态存储结构转成方法区中的运行时数据结构 * 3. 在堆中实例化一个代表该类的java.lang.Class的对象,作为程序中访问方法区中该类数据的外部接口 * * 验证: * 1. 文件格式验证———— 检查格式是否有问题,如我将魔数改了 * 2. 元数据验证———— 基于JAVA语法的验证,如两个方法签名相同, * 3. 字节码验证———— 验证代码逻辑,如iadd的操作数不能是long * 4. 符号引用验证———— 如引用的字段不存在,访问了无权访问的内容如private字段 * 文件格式验证成功后,虚拟机外的字节流就按照虚拟机所需的格式存放在方法区中 * 2-4都是基于方法区的存储结构进行 * * 准备: * 为类变量分配内存并赋默认初值(属性表中有ConstantValue的会赋具体值),这些内存在方法区中分配 * (实例变量随着对象实例化时一起在堆中分配内存,这得在初始化之后) * * 解析: * 将常量池中的符号引用替换成直接引用 * 符号引用:用字面量来描述要引用的目标,只要能定位即可(如常量表中的index) * 直接引用:可以直接指向目标的指针、相对偏移量或能间接定位到目标的句柄。如果有了直接引用,那么目标肯定己经在内存中了 * * 符号引用中在Class文件中以Class_info,Fieldref_info,Methodref_info,InterfaceMethodref_info类型的常量出现 * 并未规定解析发生的时间,但是在执行以下13个指令之前,一定要对指令使用到的符号引用进行解析: * anewarray multianewarray new getfield putfield getstatic putstatic * invokeinterface invokespecial invokestatic invokevirtual checkcast instanceof * * 缓存直接引用,在运行时常量池中记录直接引用,并标识为己解析,对于同一个实体,解析一次后后面的都获取同一个 * * 解析的目标有: * 1. 类或接口 ———— CONSTANT_Class_info * 2. 字段 ———— CONSTANT_Fieldref_info * 3. 方法 ———— CONSTANT_Methodref_info 类中方法 * 4. 接口方法 ———— CONSTANT_InterfaceMethodref_info 接口中的方法 * * 类或接口解析过程:如Demo中引用了Son * 对应的常量池: * const #32 = class #33; // org/gerry/classLoader/Son * const #33 = Asciz org/gerry/classLoader/Son; * 对应的字节码: * 0: ldc #32; //class org/gerry/classLoader/Son * 2: invokevirtual #34; //Method java/lang/Object.getClass:()Ljava/lang/Class; * 根据#32的符号引用找到#32对应的Constant_Class_info常量池打到对应的#33,取出其中的全称类名 * 根据类名加载对应的类(有父类的话会触发父类加载),这样在方法区中就有对应数据了,最后在测试直接引用是否有效 * (如无权限的情况下就报非法访问java.lang.IllegalAccessError) * 如果是数组的话,会先加载数据元素类型 * * 字段解析: * 1. 解析Constant_Field_ref中的class_index对应的类假如为C(用上面的方式) * 2. 如查C中有NameAndTyperef_info中对应的字段就直接返回字段的直接引用 * 3. 2中没找到,就看其有无接口, 在接口(如果找不到,再到其父接口中找)中查找对应字段,如果有就返回其直接引用 * 4. 3中没找到,如果不是java.lang.Object,就从下到上依其继承关系找,找到就返回 * 5. 抛出java.lang.NoSuchFieldError * 如果找到,要对其进行引用验证 * * 类方法解析: * 1. 找到C * 2. 如果C是接口就报错java.lang.IncompatibleClassChangeError * 3. 在类中找 * 4. 在父类中找 * 5. 在接口中找(在这里找到只能证明这是个抽象类,且这是个抽象方法,报java.lang.AbstractMethodError) * 6. java.lang.NoSuchMethodError * * 接口方法解析: * 1. 打到C * 2. 如果C是类就报错java.lang.IncompatibleClassChangeError * 3. 在接口中找 * 4. 在父接口中找 * 5. java.lang.NoSuchMethodError * * 初始化: * 初始化过程是执行类构造器clinit()方法的过程 * 1. clinit里面的内容是合并static块与static变量组成,static块只能访问在它之前定义的static变量 * 2. 虚拟机保证父类的clinit会先于子类clinit执行,最先执行的clinit肯定是java.lang.Object的 * 3. 父类static字段会先赋值,这个2 * 4. 如果没有static块或节段,不会生成clinit * 5. 对于接口,只有在子接口中使用到父接口中变量时,父接口才会初始化,接口实现类初始化时不会执行接口的clinit * 6. 虚拟机保证多线程下只有一个线程能执行类的clinit,会被加锁与同步 */ public class Demo { static { System.out.println( ConstantA.love );//对于常量,在编译时就直接写进类里面了 } //入口类会优先加载,只有加载了才有初始化 public static void main(String[] args) throws ClassNotFoundException { // System.out.println( Son.value );//对于静态字段,只有直接定义它的类才会被初始化,这里只会初始化父类 // Son[] sons = new Son[10];//new一个数组,会有对象被初始化,[Lorg.gerry.classLoader.Son; // Class.forName("org.gerry.classLoader.Parent");//加载,且初始化 Son.class.getClass();//加载,不初始化,会倒致父类加载 // SonInterface.class.getClass();//加载,不初始化,会倒致接口加载 // Demo.class.getClassLoader().loadClass("org.gerry.classLoader.Parent");//加载,不初始化 } /** * 四种情况会初始化: * 1. new, getstatic, putstatic, invokestatic 例外:final修饰、在编译期被入入常量池的静态变量 * 2. 用java.lang.refelect包中的方法对类进行发射调用时 * 3. 初始化类时,如果父类没初始化,先初始化父类 * 4. 虚拟机启动时初始化主类 * 上面四种情况属于主动调用 * * 下面三种情况属于被动调用: * 1. 引用常量,如上面的System.out.println( ConstantA.love );这个常量直接被写到类中,执行时己与Constant类无关 * 2. 子类引用父类静态变量,static字段只初始化直接定义的类,但是会加载子类,如System.out.println( Son.value ); * 3. 定义某种类型的数组 , 不会初始化这个类,但是会加载,如Son[] sons = new Son[10]; * * 一定要区分加载与初始化,加载是将类文件加载到虚拟机,初始化是分配内存或给值 */ /** 只有这句时:System.out.println( Son.value ); [Loaded org.gerry.classLoader.Demo from file:/D:/work/workspace/readCode/bin/] 1314 [Loaded org.gerry.classLoader.Parent from file:/D:/work/workspace/readCode/bin/] [Loaded org.gerry.classLoader.Son from file:/D:/work/workspace/readCode/bin/] parent init... 123 先加载父类,再加载子类,然后初始化父类,静态字段的调用,只会加载直接定义它的类 */ /** 只有这句时:Son[] sons = new Son[10]; [Loaded org.gerry.classLoader.Demo from file:/D:/work/workspace/readCode/bin/] 1314 [Loaded org.gerry.classLoader.Parent from file:/D:/work/workspace/readCode/bin/] [Loaded org.gerry.classLoader.Son from file:/D:/work/workspace/readCode/bin/] 可以看出只是加载了Son与Parent,并没有初始化 */ /** Class.forName("org.gerry.classLoader.Parent")会倒致Parent的初始化 [Loaded org.gerry.classLoader.Demo from file:/D:/work/workspace/readCode/bin/] 1314 [Loaded org.gerry.classLoader.Parent from file:/D:/work/workspace/readCode/bin/] parent init... */ /** Demo.class.getClassLoader().loadClass("org.gerry.classLoader.Parent");只加载,不初始化 [Loaded org.gerry.classLoader.Demo from file:/D:/work/workspace/readCode/bin/] 1314 [Loaded org.gerry.classLoader.Parent from file:/D:/work/workspace/readCode/bin/] */ /** * Son.class.getClass(); Demo.class.getClassLoader().loadClass("org.gerry.classLoader.Parent");只加载,不初始化 [Loaded org.gerry.classLoader.Demo from file:/D:/work/workspace/readCode/bin/] 1314 [Loaded org.gerry.classLoader.Parent from file:/D:/work/workspace/readCode/bin/] [Loaded org.gerry.classLoader.Son from file:/D:/work/workspace/readCode/bin/] */ }
类加载器的双亲委派模型:
package org.gerry.classLoader; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; /** * * 类加载器在加载一个类的时候,先查看该类是否己经加载了(loadClass方法里面的逻辑),如果没有,那么就由父类去找, * 如果到了最顶层类加载器还没找到,就从顶层类加载器开始加载那个类,加载不成功的话就由下一层的类接着加载 * * 为什么下面的例子中Parent会被加载两次,分别由自定义类加载器与父类加载器加载,主要原因是直接覆盖了loadClass方法 * 破坏了双新委派模式引起的 * AppClassLoader从下到上找不到Parent,并且最后由它自己加载 * 自定义定加载器在加载类时没有从下到上的找,只是自己加载了Parent,不管在前还是再后AppClassLoader都找不到由它加载的Parent * 所以加载了两次 * * 至于线程上下文,我估计就是为了防止SPI(如JDBC)的类被不同的类加载器加载,所以对于JDBC的类加载,都是取出线程里的 * 线程上下文类加载器后,再由它来加载,这样就不会出现两个不同类加载器加载的类不能赋值的情况出现 * System.out.println( ( Parent )sonCl.newInstance() ); * 要注意: * ArrayList与MyList的例子 * MyList由AppClassLoader加载 * ArrayList由引导加载 * 而ArrayList ar = new MyList();是不会出现那( Parent )sonCl.newInstance()的强制改化错误的 * 看来赋值操作只管类型的可见性,即findLoadedClass能否找到 * * 对于当前类中出现的Parent是由AppClassLoader加载的,而Son由匿名类加载,它也会加载对应的Parent, * 这个时候匿名类加载器只能看到它自己加载的Parent,而强制转化的Parent并不是它的,所以会报错,下面将 * Parent改成匿名类的父类即AppClassLoader加载后就不会报错了,因为变成可见了 * */ public class TestClassLoader { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { /** * 一般是覆盖findClass方法,这样的话就不会破坏双亲委派模型,它会保证尽量由父类去加载子类,防止出现一个类 * 由两个类加载器去加载, * 这里覆盖loadClass强制加载指定的类,就出现了同一个类由两个类加载器加载 */ //这是个匿名类,也会被加载 ClassLoader cl = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { String fileName = name.substring( name.lastIndexOf(".") + 1 ) + ".class"; // System.out.println(getClass().getClassLoader().getResource(""));//在bin目路 //getClass().getResource("")在当前目录 InputStream is = getClass().getResourceAsStream( fileName ); if( is == null || fileName.equals("Parent.class" ) ) { return super.loadClass(name);//报ClassNotFoundException异常 } // else // { // return super.loadClass(name); //如果这里改成这样,那么类就是由父类加载的,TestClassLoader.class.getClassLoader获取的将会是AppClassLoader // } byte[] bt; try { bt = new byte[is.available()]; is.read(bt); return defineClass(name, bt, 0, bt.length );//报NoClassDefFoundException } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.loadClass(name); } }; // Thread.currentThread().setContextClassLoader( cl );//设置线程上下文之后,在当前的线程中遇到的类默认以它来加载 // System.out.println(Parent.class.getClassLoader()); ClassLoader l = TestClassLoader.class.getClassLoader(); //这个类己经被加载了,在双亲委派模式下不会被从新加载 Class tClass = l.loadClass("org.gerry.classLoader.TestClassLoader"); System.out.println( TestClassLoader.class == tClass ); //比较不是从同一个类加载器加载的类是无意义的,一个类由类加载器与它本身决定其唯一性 Class tClass2 = cl.loadClass("org.gerry.classLoader.TestClassLoader"); System.out.println( TestClassLoader.class == tClass2 ); System.out.println( tClass2.getClassLoader() ); Class thisClass = TestClassLoader.class; // 64: ldc #1; //class org/gerry/classLoader/TestClassLoader // 66: astore 5 // 在文件格式验证之后字节流以虚拟机要求的格式存进了方法区,在解析完成后,符号引用就转成了直接引用, //也就是说这里的#1己经替换成直接引用了,而TestClassLoader.class能获取到这个直接引用 System.out.println( TestClassLoader.class.getClassLoader() ); //一个类的定义类加载器,是该类中其它类的初始化加载器 //调用loadClassr的是初始化加载器调用的 //调用defineClass的是定义类加载器,区分class的类加载器是看定义类加载器,也就是说,谁加载的类谁就是定义类加载器 //如这里的匿名内部类org.gerry.classLoader.TestClassLoader$1就是由加载TestClassLoader的类AppClassLoader作为初始化类加载器 /** * rt下面的类是由启动类加载器加载的,JAVA程序是无法获得启动加载器 */ System.out.println( String.class.getClass().getClassLoader() );//null System.out.println( Thread.currentThread().getClass().getClassLoader() );//null Class ParentCl = cl.loadClass("org.gerry.classLoader.Parent"); Class sonCl = cl.loadClass("org.gerry.classLoader.Son");//它也会倒致ParentCl的加载, //可以看出自定义类加载器 System.out.println( ( Parent )sonCl.newInstance());//强制转化异常,字节码指令为checkcast System.out.println( "===============不同类加载器加载的类不能赋值===============" ); System.out.println( sonCl.getClassLoader().toString() ); System.out.println( Son.class.getClassLoader().toString() ); System.out.println( (Son)new Son()); System.out.println( (Son)sonCl.newInstance() );//报错 System.out.println( "=============================" ); Class myListCl = l.loadClass("org.gerry.classLoader.MyList"); ArrayList ar = new MyList(); /** * ArrayList与MyList分别由两个类加载器加载,但是不影响它们的给值 * 同一个类被两个类加载器加载,那么它们生成的对象是不能相互给值的,因为它们是不同的类 */ System.out.println( ArrayList.class.getClassLoader() ); System.out.println( MyList.class.getClassLoader() ); // Class pl = cl.loadClass("org.gerry.classLoader.Parent");//同一个加载器第二次加载会报错 System.out.println( Parent.class.toString() + '|' + ParentCl.getClassLoader().toString() ); } } class MyList extends ArrayList { }
方法分派:
package org.gerry.codeExecute; /** * 活动线程中只有最顶端的栈桢是有效的,称为当前栈桢,它对应的方法称为当前方法 * 局部变量表: * 1. 局部变量表中第0位slot默认存储方法所有者实例的引用(名字叫this) * 2. 虚机通过局部变量表完成参数值到参数列表的传递,如setValue(1,2)对应方法setValue(x,y) * 那么会将1与2存入局部变量表中的x与y,按顺序排在this后面,之后再为方法内的变量分配slot * * 操作数栈(后进先出) * * * 动态链接 * 1. 栈桢中有一个指向方法区中所属方法的符号引用 * 2. 方法调用指令是以方法的符号引用作为参数的,符号引用有一部分是在加载类或第一次使用时转为 * 直接引用,这称为静态解析(invokestatic,invokespecial对应的方法符号引用,可以确定调用的是什么方法 * ————编译期确定,执行期不可变), * 还有一部分在每次运行期间才转成直接引用,称为动态链接(invokevirtual多态调用,只有到运行时你才 * 能知道是谁在调用这个方法,才能去决定调用那个方法) * * 方法返回地址: * 1. 调用当前方法(对就调用者的一条指令)的方法称为方法的调用者(main中调用test,那么test执行时它的调用者就是main) * 2. 方法正常返回时,要恢复调用者方法,可以把调用者PC计数器作为返回地址,栈桢中应该会保留这个地址。 * 方法异常退出参考异常信息表 * 3. 方法退出时要做的事,首先恢复调用者方法的本地变量表,操作数栈,如果有返回值就将其压入操作数栈, * 接着将PC计数器指向方法调用指令的下一条指令。 * * * 方法调用: * 1. 方法调用是确定调用方法的版本(那一个方法),不是指运行 * 2. 在编译期就能确定版本,在运行期不可变的方法,在编译期就可以将符号引用转成直接引用,这类方法的调用叫解析 * 3. 解析是针对非虚方法(invokestatic,invokespecial指令对应的方法, * 对应的方法有,构造方法,父类方法,私有方法,静态方法,final方法不能覆盖,也算能确定版本,但是静态分派能影响它吧) * 4. 解析并一定能唯一的确定方法,因为方法是可以重载的,那么怎么选择重载的方法呢? * 这里就轮到静态分派了. * 静态分派:根据参数的静态类型来决定重载版本(Parent p = new Son(),Parent是静态类型,son是实际类型) * —————————— 静态分派发生在编译阶段(就是选择重载的版本) * 对于重载的方法,如下面的invoke(Human human)与invoke(Parent parent)及invoke(Son son) * 当执行invoke(son)时有三个方法都是满足条件的,son可以看成parent与human, * 这时候编译器会根据一定的规则来选择最合适的方法,当invoke(Son son)存在时它最合适,无它时找Parent一至向上找 * 如char-->int-->long-->character-->serializable-->object-->args...展示了传入char时方法选择的顺序 * * 动态分派:在运行期,根据方法接收者的实际类型(调用方法的对象如A.test()类型就是A)来决定调用版本(选择覆盖的版本) * invokevirtual的多态查找过程: * 1.找到操作数栈顶的元素所指向的实际类型 * 2.在对应的实际类型中查找名称与描述符到致的方法,找到就OK,找不到就向父亲中找 * * 单分派与多分派: * 方法接收者与参数是影响方法版本的两个因素,如果由其中一项来决定版本就是单分派否则多分派 * 对于方法重载:要关心调用者的静态类型与参数类型,它是多分派 * 对于重写:因为在单分派中己经决定了方法的重载版本,所以它只关心调用者的实际类型,所以它是单分派 * * 虚拟机实现动态分派的方式: * 因为动态分派要经常去查找相应方法,很费性能,于是虚方法表出现了,用于替代在元数据中查找方法 * 结构:虚方法表中存放的是各个方法的实际入口,子类没有重写的方法存放的地址与父类相同,如果重写了,那么 * 放它自己的地址 * Parent Object Son * toString-------------------->toString<------------------toString * getClass-------------------->getClass<------------------getClass * test<-----------------------------------------------------test(子类中没有) * abc ------------------------------------------------------abc(子类中也有) * 具有相同签名的方法在子类与父类中应该具有相同的索引,这样当从Son切到Parent时直接就可以找到对应方法 * * 方法表在连接阶进行初始化,当准备阶段完成后,虚拟机会把该类的方法表也初始化完毕 * * 基于栈的解释器执行: * int i = 100;的执行过程:bipush 100; istore_1; * 指令 程序计数 操作数栈(长度为1) 本地变量表(maxLocals 2) * bipush 100; 1 100 this * istore_1; 2 空了 this 100(第二个有值了) */ public class Demo { { int a= 1; System.out.println( a ); } public Demo() { } public Demo( int i) { } /** * maxLocals:4(因为long要占用两个slot,加上this与i就是4个slot) * maxStack:2(操作数栈可以存放任何类型数据,这里long只占一个) */ public void maxStackNum() { // final int a = 12;//有无final在字节码中没区别,编辑器来确定它不会被变 long l = 2l;//lconst_1是最大的,超过就先idc_w,将常量推送到栈顶,这个2l会被存进Long_info常量池 int i = 65535;//iconst_5是最大的了,超过5就要判断了,先bipush(-128~127),再sipush(-32768~32767),超过了就常量池上 // int m = i;//对应指令为iload_3 istore 4,别然iload_3为最大,但超过后用在后面跟4代替,所以不管有多少都没影响 // long a1 = 1l; // long a2 = 2l; // long a3 = 3l; // long a4 = 4l; // long a = a4; // int[] barr = new int[ 5 ];//astore 4 aload 4根本就没用数组的操作啊 // int[] carr = barr; // synchronized (this) { // monitorenter //获取对象的monitor,用于同步方法或同步块 // monitorexit //释放 对象的monitor // } } public static void main(String[] args) { StaticDispatch.Human parent = new StaticDispatch.StaticParent(); StaticDispatch.StaticSon son = new StaticDispatch.StaticSon(); StaticDispatch sd = new StaticDispatch(); sd.invoke( son ); // StaticDispatch.invoke( son ); //这里调用类方法的是invokestatic,但是它也会受到参数的影响啊,去掉invoke(parent)就自动跑到invoke(human) //里面去了,那它也是要在运行时才能确定方法的版本啊,那它怎么能叫解析?是分派才对啊 //看来方法有无static对重载的方法都有影响 //看来应该是静态分派针对重载(根据参数静态类型来),动态分派针对覆盖(实际类型内的方法选择,如果此时方有重载呢?) } } // public 1 // final 1 super 2 // interface abstract 24 // synthetic annotation enum 1 2 4 class StaticDispatch { //public private protected static 1248 //final 1 //interface abstract 24 //synthetic annotation enum 1 2 4 static class Human { // public private protected static 1248 // final volatile transient 148 // // synthetic enum 14 int i; } static class StaticParent extends Human { } static class StaticSon extends StaticParent { } //public private protected static 1248 //final synchronize bridge varargs 1248 //native abstract strict 148 //synthetic 1 public void invoke( Human human) { System.out.println("Human"); } public static void invoke( StaticParent parent) { System.out.println("Parent"); } public void invoke( StaticSon son) { System.out.println("Son"); } }
相关推荐
深入理解 Java 虚拟机笔记 Java 虚拟机(JVM)是 Java 语言的运行环境,它负责解释和执行 Java 字节码。下面是 Java 虚拟机相关的知识点: 虚拟机内存结构 Java 虚拟机的内存结构主要包括以下几个部分: * 方法...
1. **加载和实例化**: 类加载器加载Servlet类并创建Servlet实例。 2. **初始化**: 调用init()方法,初始化Servlet。 3. **服务阶段**: 对每个请求,Servlet容器创建一个ServletRequest和ServletResponse对象,然后...
3. 服务:每次有请求到达,Servlet容器都会调用`service()`方法,该方法会根据请求类型分派到`doGet()`或`doPost()`等具体处理方法。 4. 销毁:当Servlet不再使用或Web应用停止时,会调用`destroy()`方法释放资源。 ...
4. **Servlet生命周期**:包括加载、初始化、服务、销毁四个阶段,涉及多个回调方法。 5. **文件和目录结构**:遵循一定的标准,如WEB-INF下存放web.xml和类文件。 【实战MVC】 1. **创建Web应用**:从需求分析、...
当服务器首次接收到对Servlet的请求,或者在服务器启动时如果配置了Servlet的加载,服务器会加载Servlet类并创建一个Servlet实例。接着,调用Servlet的`init()`方法进行初始化,这里通常进行一些一次性配置。在服务...
### Windows驱动程序开发学习笔记知识点解析 #### 一、Windows驱动程序开发概述 - **学习背景**: 学习者最初接触Windows驱动程序开发时遇到不少困难,经历了一段时间的摸索后逐渐入门。这一过程中,作者参考了...
在处理请求时,Servlet容器会调用service()方法,根据请求类型分派到doGet()或doPost()等方法。最后,当Servlet不再需要时,服务器会调用destroy()方法,释放资源。 三、Servlet部署 Servlet通常通过在Web应用的web...
在初始化阶段,服务器加载Servlet类并调用`init()`方法;在服务阶段,每次请求都会创建一个线程来调用`service()`方法;最后,服务器关闭时调用`destroy()`方法。 - **Servlet映射路径匹配顺序**:URL路径到Servlet...
根据给定的文件标题“VC++深入详解学习笔记”及描述和部分内容,下面将提炼出相关的VC++知识点。 ### VC++深入详解学习笔记 #### Lesson1: Windows原生开发 本节主要介绍了如何使用VC++进行Windows原生开发的基础...
4. 服务:每当有新的请求到达,Servlet容器会调用Servlet的`service()`方法,该方法会根据请求类型分派到`doGet()`或`doPost()`等具体方法。 5. 销毁:当服务器关闭或Servlet不再需要时,容器会调用Servlet的`...
1. **初始化ActionServlet**:Web应用启动时,ActionServlet被加载并初始化,从struts-config.xml文件中读取配置信息。 2. **处理用户请求**:当用户请求到达,ActionServlet查找匹配的ActionMapping,创建...
**无操作系统的计算机**:早期的计算机没有操作系统,用户需要手动加载程序到内存。 **单道批处理系统**:一次只能执行一道程序,但可以通过批量方式处理多个作业。 **多道批处理系统**:允许多个程序同时存在于...
2. **GenericServlet**:这是一个抽象Servlet类,实现了Servlet和ServletConfig接口,提供了基本的`service()`方法,可以根据请求类型分派到`doGet()`, `doPost()`等方法。 3. **HttpServlet**:进一步抽象了...
操作系统期末复习笔记 操作系统是一系列程序,运行在内核模式下,管理硬件资源,提供服务和接口给用户,同时也避免错误的产生和不正当的使用。操作系统主要包括两个方面的定义,一方面是管理硬件资源的分配,另一...
- **特权指令**:为了保护计算机系统安全,一些指令只能由操作系统来执行,这类指令被称为特权指令。例如,修改内存映射表的指令只能由操作系统执行。 - **系统调用**:系统调用是用户程序向操作系统请求服务的一...
1. **处理器管理**:讲解如何有效地调度和分派处理器资源,包括进程的概念、上下文切换以及抢占式和非抢占式调度算法。 2. **内存管理**:探讨虚拟内存的原理,如页表、页面替换算法(如LRU、FIFO)、内存映射以及...