试题2020.1.14

1.下列哪些语句关于内存回收的说明是正确的? (  )

正确答案: B   你的答案: D (错误)

程序员必须创建一个线程来释放内存
内存回收程序负责释放无用内存
内存回收程序允许程序员直接释放内存
内存回收程序可以在指定的时间释放内存对象

链接:https://www.nowcoder.com/questionTerminal/c8435ed863a14f84afc3f32d8f1e28bf
来源:牛客网

选B。
A、JVM一旦启动,就会创建一个守护线程来监测是否需要有对象内存被释放。
C、无法直接释放。
D、不可以指定时间,System.gc(),只是提醒JVM可以进行一次Full GC,但是什么时候真正执行,还是不知道的。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
2.下列说法正确的是( )

正确答案: C   你的答案: C (正确)

volatile,synchronized 都可以修改变量,方法以及代码块
volatile,synchronized 在多线程中都会存在阻塞问题
volatile能保证数据的可见性,但不能完全保证数据的原子性,synchronized即保证了数据的可见性也保证了原子性
volatile解决的是变量在多个线程之间的可见性、原子性,而sychroized解决的是多个线程之间访问资源的同步性

synchronized关键字和volatile关键字比较:

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性

3.关于中间件特点的描述.不正确的是()

正确答案: A   你的答案: A (正确)

中间件运行于客户机/服务器的操作系统内核中,提高内核运行效率
中间件应支持标准的协议和接口
中间件可运行于多种硬件和操作系统平台上
跨越网络,硬件,操作系统平台的应用或服务可通过中间件透明交互

链接:https://www.nowcoder.com/questionTerminal/bb7b8938da2c403c96e4d320d88163f6
来源:牛客网

中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源。中间件位于客户机/ 服务器的操作系统之上,管理计算机资源和网络通讯。是连接两个独立应用程序或独立系统的软件。相连接的系统,即使它们具有不同的接口,但通过中间件相互之间仍能交换信息。执行中间件的一个关键途径是信息传递。通过中间件,应用程序可以工作于多平台或OS环境。
(简单来说,中间件并不能提高内核的效率,一般只是负责网络信息的分发处理)
中间件特点的描述:
1. 中间件应支持标准的协议和接口
2. 中间件可运行于多种硬件和操作系统平台上
3. 跨越网络,硬件,操作系统平台的应用或服务可通过中间件透明交互
 
4.下列哪项不属于jdk1.6垃圾收集器?

正确答案: D   你的答案: D (正确)

Serial收集器
parNew收集器
CMS收集器
G1收集器

1.Serial收集器 
单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为Stop The World,下称STW),使用复制收集算法,虚拟机运行在Client模式时的默认新生代收集器。 

2.ParNew收集器 
ParNew 收集器就是Serial的多线程版本,除了使用多条收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器一摸一样。对 应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果。 

3.Parallel Scavenge收集器 
Parallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是 以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。 

4.Serial Old收集器 
Serial Old是单线程收集器,使用标记-整理算法,是老年代的收集器,上面三种都是使用在新生代收集器。 

5.Parallel Old收集器 
老年代版本吞吐量优先收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。 

6.CMS(Concurrent Mark Sweep)收集器 
CMS 是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或 者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的。CMS收集器使用的是标记-清除算法,也就是说它在运行期间会 产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。 


5.
下面有关java hashmap的说法错误的是?

正确答案: C   你的答案: C (正确)

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。
HashMap 的实现不是同步的,意味着它不是线程安全的
HashMap通过开放地址法解决哈希冲突
HashMap中的key-value都是存储在Entry数组中的

1. 开放定址法:线性探测再散列、二次探测再散列、再随机探测再散列;

2. 再哈希法:换一种哈希函数;

3. 链地址法 :在数组中冲突元素后面拉一条链路,存储重复的元素;

4. 建立一个公共溢出区:其实就是建立一个表,存放那些冲突的元素。

什么时候会产生冲突

HashMap中调用 hashCode() 方法来计算hashCode。

由于在Java中两个不同的对象可能有一样的hashCode,所以不同的键可能有一样hashCode,从而导致冲突的产升。
HashMap底层是 数组和链表 的结合体。底层是一个线性数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。数组是 Entry[] 数组,静态内部类。 E ntry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用 next ,这就构成了链表。所以 很明显是链地址法。
具体过程:
当我们往HashMap中put元素的时候:当程序试图将一个key-value对放入HashMap中时,
1 . 程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置;
2 . 若 Entry 的存储位置上为 null ,直接存储该对象;若不为空,两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同,
3 . 循环遍历链表,如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖;如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部


6.DBMS 中实现事务持久性的子系统是()

正确答案: D   你的答案: D (正确)

安全性管理子系统
完整性管理子系统
并发控制子系统
恢复管理子系统

原子性:事务是一组不可分割的操作单元,这组单元要么同时成功要么同时失败(由DBMS的事务管理子系统来实现);
一致性:事务前后的数据完整性要保持一致(由DBMS的完整性子系统执行测试任务);
隔离性:多个用户的事务之间不要相互影响,要相互隔离(由DBMS的并发控制子系统实现);
持久性:一个事务一旦提交,那么它对数据库产生的影响就是永久的不可逆的,如果后面再回滚或者出异常,都不会影响已提交的事务(由DBMS的恢复管理子系统实现的)

7.假设 a 是一个由线程 1 和线程 2 共享的初始值为 0 的全局变量,则线程 1 和线程 2 同时执行下面的代码,最终 a 的结果不可能是()


boolean isOdd = false;

for(int i=1;i<=2;++i)
{
if(i%2==1)isOdd = trueelse isOdd = false;
a+=i*(isOdd?1:-1);
}

正确答案: D   你的答案: D (正确)
-1
-2
0
1
易知:每个线程对a 均做了两次读写操作,分别是 “ +1 ” 和 “ -2 ”
而题目问了是最终a 的结果,所以 a 的结果取决于各自线程对 a 的先后读写的顺序
结论:a的可能取值为-1、0、-2
如图:


链接:https://www.nowcoder.com/questionTerminal/2be86af464c04b9c9d7895d62dd46732
来源:牛客网

不管怎样线程对a的操作就是+1后-2
1.线程1执行完再线程2执行,1-2+1-2=-2
2.线程1和2同时+1,再-2不同时,1-2-2=-3
3.线程1和2不同时+1,同时-2,1+1-2=0
4.线程1和2既同时+1又同时-2,1-2=-1
没有结果为1的情况

8.关于下面的一段代码,以下哪些说法是正确的:


public static void main(String[] args) {
    String a = new String("myString");
    String b = "myString";
    String c = "my" + "String";
    String d = c;
    System.out.print(a == b);
    System.out.print(a == c);
    System.out.print(b == c);
    System.out.print(b == d);
}

正确答案: A D   你的答案: C D (错误)
System.out.print(a == b)打印出来的是false
System.out.print(a == c)打印出来的是true
System.out.print(b == c)打印出来的是false
System.out.print(b == d)打印出来的是true

链接:https://www.nowcoder.com/questionTerminal/2e91232158704054a48f54d73619953f
来源:牛客网

A:a指向堆内存,b指向常量池,因此地址不相等,false
B:java有常量优化机制,c也指向常量池,且与b指向同一个,则a与c地址不相等,false;
C:b与c地址相等,true
D:d是c的副本,地址相同,所以b与d地址相等,true
 
9.Java7特性中,abstract class和interface有什么区别。
正确答案: A B D   你的答案: A B D (正确)
抽象类可以有构造方法,接口中不能有构造方法
抽象类中可以有普通成员变量,接口中没有普通成员变量
抽象类中不可以包含静态方法,接口中可以包含静态方法
一个类可以实现多个接口,但只能继承一个抽象类。

链接:https://www.nowcoder.com/questionTerminal/76a029a6196c488699c2f27c10b76c09
来源:牛客网

抽象类和接口的区别:

1. 抽象类可以有构造方法,接口中不能有构造方法。
2. 抽象类中可以有普通成员变量,接口中没有普通成员变量。
3. 抽象类中可以包含非抽象普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的方法。
4. 抽象类中的抽象方法的访问权限可以是 public、protected 和默认类型,接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。
5. 抽象类中可以包含静态方法,在 JDK1.8 之前接口中不能不包含静态方法,JDK1.8 以后可以包含。
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问权限可以是任意的,但接口中定义的变量只能是 public static final 类型的,并且默认即为 public static final 类型。
7. 一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类,接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。
接口中可以包含静态方法 ”,jdk1.8中可以有,而jdk1.7编译错误

10.关于多线程和多进程,下面描述正确的是():
正确答案: A C   你的答案: A C (正确)
多进程里,子进程可获得父进程的所有堆和栈的数据;而线程会与同进程的其他线程共享数据,拥有自己的栈空间。
线程因为有自己的独立栈空间且共享数据,所有执行的开销相对较大,同时不利于资源管理和保护。
线程的通信速度更快,切换更快,因为他们在同一地址空间内。
一个线程可以属于多个进程。

1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4、处理机分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体。


 1 11.关于下面一段代码,以下说法正确的是: ()
 2 
 3 public class Test {
 4     private synchronized void a() {
 5     }
 6     private void b() {
 7         synchronized (this) {
 8         }
 9     }
10     private synchronized static void c() {
11     }
12     private void d() {
13         synchronized (Test.class) {
14         }
15     }
16 }
17 
18 正确答案: A C D   你的答案: A C D (正确)
19 同一个对象,分别调用方法a和b,锁住的是同一个对象
20 同一个对象,分别调用方法a和c,锁住的是同一个对象
21 同一个对象,分别调用方法b和c,锁住的不是同一个对象
22 同一个对象,分别调用方法a、b、c,锁住的不是同一个对象

链接:https://www.nowcoder.com/questionTerminal/718f28312bf34cbdb00bc80ee42b83e4
来源:牛客网

  • 同步代码块(synchronized(this),synchronized(类实例对象),锁是小括号()中的实例对象)
  • 同步非静态方法(synchronized method),锁的是当前对象的实例对象
获取类锁
  • 同步代码块(synchronized(类.class)),锁是最小括号 () 中的类对象(Class对象)
  • 同步静态方法(synchronized static method),锁是当前对象的类对象(Class 对象)
总结
  • 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块
  • 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会被阻塞。
  • 若锁住的是同一个对象,一个线程在访问对象的同步方法时,另一个访问对象的同步方法的线程会被阻塞。
  • 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象同步方法的线程会被阻塞,反之亦然。
  • 同一个类的不同对象的锁互不干扰
  • 类锁由于也是一种特殊的对象锁,因此表现和上述一致,而由于一个类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的
  • 类锁和对象锁互不干扰
 
链接:https://www.nowcoder.com/questionTerminal/718f28312bf34cbdb00bc80ee42b83e4
来源:牛客网

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
D是错的。
同一个对象,分别调用a,b,c三个方法,
a是普通方法,锁住的是当前类的class对象,
b是同步方法块,锁是括号里面的对象。this是调用该方法的对象本身。
c是静态同步方法,锁是当前类的class对象
所以a和c是一样的。
 
1 12.关于Java中的ClassLoader下面的哪些描述是错误的:(    )
2 正确答案: B D F   你的答案: B D F (正确)
3 默认情况下,Java应用启动过程涉及三个ClassLoader: Boostrap, Extension, System
4 一般的情况不同ClassLoader装载的类是不相同的,但接口类例外,对于同一接口所有类装载器装载所获得的类是相同的
5 类装载器需要保证类装载过程的线程安全
6 ClassLoader的loadClass在装载一个类时,如果该类不存在它将返回null
7 ClassLoader的父子结构中,默认装载采用了父优先
8 所有ClassLoader装载的类都来自CLASSPATH环境指定的路径

链接:https://www.nowcoder.com/questionTerminal/fc4945cd85144adca811aae468a77c20
来源:牛客网

B:先讲一下双亲委派机制,简单来说,就是加载一个类的时候,会往上找他的父类加载器,父类加载器找它的父类加载器,直到最顶层的类加载。为什么要使用双亲委派机制呢?例如类java.lang.Object,他存放在rt.jar中,无论那一个类加载器要加载这个类,最终都会到启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。相反,如果没有使用双亲委派机制,有各个类加载各自去加载的话,用户自定义一个java.lang.Object,并且放在程序的classpath中,系统中将会出现多个Object类,java类型体系中最基础的行为就得不到保障了。(摘自<深入理解JVM虚拟机>第231页)
    综上:ClassLoader装载的类时相同的。
D:不是返回null,而是抛出异常ClassNotFoundException。
F:很明显嘛,我们自己定义的类并没有放在classpath环境指定的路径,不一样会加载。
    个人拙见,如果错误之处,敬请指出。
 
  从java虚拟机的角度讲,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用的是c++实现的,是虚拟机的一部分,另一类是就是所有其他类加载器,这些类加载器都由java语言实现,独立于虚拟机外部,并且全都继承自抽象类。
从开发人员的角度看,类加载器还可以划分为3种系统类加载器,启动类加载器(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$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称它为系统类加载器(System ClassLoader)。他负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。对此,如果有必要开发者可以加入自己定义的类加载器。
    一般对于我们java程序员来说,类的加载使用的是双亲委派模型,即当一个类需要加载时,会将类传给Application ClassLoader,但是Application ClassLoader并不会加载,不管它是否能加载,而是传给它的"父类" Extension ClassLoader,Extension ClassLoader同样不会加载,同样传给 Bootstrap ClassLoader(注意不是我们常说的那种父类,但是可以这样理解),这时Bootstrap ClassLoader会判断它是否能加载,能加载就直接加载了,不能加载就传给Extension ClassLoader,Extension ClassLoader同样的判断是否能加载,能加载就直接加载,不能加载就传给Application ClassLoader,然后Application ClassLoader也判断能否加载,如果还是不能加载应该就是报ClassNotFoundException了。这就是双亲委托模型的简单理解了。
    对于上面的"父类"为什么要打引号,因为它们并不是真的像java中继承的关系,而是组合的关系,即在"子类"中存在一个成员变量指向"父类"的引用。
    所以AE对DF错了。
    对于C,很容易理解,因为我们知道一个类只需要加载一次就够了,所以要保证线程安全。
    难点就在B了,其实也好理解,就是体现双亲委托模型的优势的时候了,之所以使用双亲委托机制是为了保证java程序的稳定运作,比如当你使用的不是双亲委托模型的时候,然后刚好开发者又定义了一个类,一个java.lang.String这样一个类,如果不使用双亲委托模型,当类加载的时候就有可能会加载开发者定义的String类,这导致了java代码的一片混乱,可读性极差。(PS:但这并不意味着类加载器只要双亲委托模型就行了,没有完美的模型,只有最合适的模型,有不同的需求使用不同的模型。比如破坏双亲委派模型,有兴趣的牛友可以自行了解),所以可以这么说,不同的类加载器加载出来的类是不一样的,不同的类加载器加载同一个类会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同的Class实例。对于接口,其实就是一个特殊的类,和类一样,在堆中产生不同的class对象。
    纯手打,个人理解,欢迎大佬指出错误。
 
Java语言系统自带有三个类加载器: 
- Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。 
- Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%libext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。 
- Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。
加载顺序:
1. Bootstrap CLassloder 
2. Extention ClassLoader 
3. AppClassLoader

1 13.下列哪些方法是针对循环优化进行的
2 正确答案: A B D   你的答案: A B D (正确)
3 强度削弱
4 删除归纳变量
5 删除多余运算
6 代码外提

常见的代码优化技术有:复写传播,删除死代码, 强度削弱,归纳变量删除

(下面抄几页PPT和别人的博客,QAQ,关键是编译原理没认真学过,咱家不太会。。。)

如果有同学有研究过这部分内容,并总结过,有更好的答案,请联系我删除本篇非原创解答

复写传播:

图片说明

  • 复写语句:形式为f = g 的赋值
    • 优化过程中会大量引入复写
    • 复写传播变换的做法是在复写语句f = g后,尽可能用g代表f
    • 复写传播变换本身并不是优化,但它给其他优化带来机会
      • 常量合并(编译时可完成的计算)
      • 死代码删除

死代码删除

  • 死代码是指计算的结果决不被引用的语句
  • 一些优化变换可能会引起死代码

代码外提

  • 代码外提是循环优化的一种
  • 循环优化的其它重要技术
    • 归纳变量删除
    • 强度削弱

例: 

1 while(i <= limit - 2) ...
2 // 代码外提后变成
3 t = limit - 2;
4 while(i <= t) ...

归纳变量删除

1 j = j - 1
2 t4 = 4 * j
3 t5 = a[t4]
4 if t5 > value goto B3
  • j和t4的值步伐一致地变化,这样的变量叫作归纳变量
  • 在循环中有多个归纳变量时,也许只需要留下一个
  • 这个操作由归纳变量删除过程来完成
  • 对本例可以先做强度削弱,它给删除归纳变量创造机会

强度削弱

  • 强度削弱的本质是把强度大的运算换算成强度小的运算,例如将乘法换成加法运算。

参考资料

  1. "某个不知名PPT"
  2. "《编译原理之代码优化》(By 墨篙和小奶猫)"




 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
原文地址:https://www.cnblogs.com/KujoJotaro/p/12192885.html