Java笔试算法题OJ
根据平台说明,先将easy level的算法题做一做。
Java虚拟机栈是Java方法执行的内存模型,每一个方法在执行的时候都会创建一个栈帧(Stack Frame),JVM负责push和pop栈帧到Java虚拟机栈中。
Stack Frame(栈帧)
Index 0
表示this
应用,紧接着就是参数和方法内声明的局部变量Java虚拟机的操作是基于栈进行的,方法执行时,大部分的操作都是在局部变量表和操作数栈之间进行数据的更新。
Java虚拟机栈在编译的时候就确定了内存的大小。如果线程请求的栈深度大于虚拟机所能够提供的深度,将会抛出StackOverflowError
;如果虚拟机在动态扩展栈时无法申请到足够的内存空间将会抛出OutofMemoryError
。
局部变量表中存放的是方法参数和方法内部定义的局部变量、各种基本的数据类型、对象引用(reference)和ReturnAddress类型。
Java堆是用来存放Java对象和数组的地方,又叫做”gc堆”,其内存物理上可以不连续,逻辑上必须连续。
方法区用来储存:已经被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。
方法区又称为永久代,是堆的一个逻辑部分,运行时的常量池也是方法区的一部分。
Class文件除了类的版本、字段、方法、接口等描述信息之外,还有一项是Class文件常量池,这部分在加载之后将放在方法区的运行时常量池中。运行时的常量池相对于Class文件常量池的另一个特点就是动态性。运行期间可以将新的常量放入池中,较多用的是String类的inter()
方法。
详细内容请参考:《深入理解Java虚拟机》
一般商用的JVM实现都使用的是分代收集算法:在对象存活率较低的新生代(10%对象存活)使用的是复制算法。在对象存活率高的老生代和方法区(永久代)采用标记清除算法或者标记整理算法。
复制算法:Java堆新生代又分为Eden
,Survivor0
和Survivor1
;它们之间的占比大约是8:1:1
;新生成的对象就放在Eden
中,当Eden
满时,就把存活的对象复制到S0
中,清除Eden
;当下一次GC回收时(S0也满了时),把Eden
和S0
中的存活对象复制到S1
中,清空Eden
和S0
;当再下一次GC时,S0
和S1
的角色就换了,JVM只扫描Eden
和S1
中的对象,如此往复进行。实际上S0
和S1
同时只有一个配合Eden
在工作。如果S0
或者S1
不足以存放复制过来的对象时,就会放到老生代中。
详细内容请参考:《深入理解Java虚拟机》
Java的类加载大体上按照加载 –> 验证 –> 准备 –> 解析 –> 初始化的顺序进行的。
其中,加载、验证、准备和初始化时严格按照顺序开始的,解析可能发生在初始化之后(目的是为了支持Java运行时绑定),也可能发生在初始化之后。虽然上述阶段是按照严格顺序开始的,但不是按照顺序进行或者完成的,通常是互相交叉混合进行的。
Java当中的绑定指的是,将一个方法的调用与一个方法的主题关联起来,分为静态绑定和动态绑定。静态绑定是compile-time绑定,也叫前期绑定。Java当中只有final、static、private和constructor方法是前期绑定的;动态绑定是run-time绑定,又叫后期绑定,运行时根据对象的类型进行绑定,几乎所有的方法都是动态绑定。
通过一个类的全限定名获取其二进制字节流,将这个字节流代表的静态储存结构转化为方法区运行时数据结构,在java堆中生成一个java.lang.Class对象,最为方法区某些数据的访问入口。
可以使用系统或者自己定义的类加载器完成类的加载,类加载器包括:
jdk/jre/lib/java core API
(java开头的文件)。启动类加载器无法被Java程序直接引用。jdk/jre/lib/ext
目录中(javax开头的文件)。扩展类加载器可以在开发中直接引用。自定义加载器 --> 应用程序加载器 --> 扩展类加载器 --> 启动加载器
称之为”双亲委派模型”。后面的加载器是前面加载器的父加载器,他们之间并不是继承关系,而是通过composition来复用父加载器的代码。
加载器的工作流程是:收到类加载请求 --> 把请求委托给父加载器完成 --> 依次向上 --> 到启动类加载器 --> 父类加载无法加载(找不到相应的class文件) --> 自己完成加载
。
验证的目的是确保class文件的字节流包含的信息符合当前虚拟机的要求,而不会危害虚拟机自身的安全,验证的内容:文件格式、元数据、字节码和符号引用。
为类变量分配内存并设置类变量的初始值,这些内存都将在方法区中分配。这时候的内存分配仅包含类变量(static),不包含实例变量,实例变量会在对象实例化时随对象分配在堆中,这里的初始值为默认初始值,而不是Java代码中assign的值。
Note:
将常量池中的符号引用转化为直接引用的过程,解析可能发生在初始化之前或者之后,分为静态解析和动态解析。static变量发生在静态解析阶段。
在这一个阶段真正的执行类中定义的Java程序代码
加载 --> 验证 --> 准备 --> 解析(没有严格顺序) --> 类初始化 --> 对象实例化
参考文章: 设计模式
参考书籍:《Java与模式》作者:阎宏
参考文章: 一部一个脚印学习数据结构与算法
参考文章:排序算法总结
(待续)…
(待续)…
(待续)…
(待续)…
参考总结:秋季校招笔面试
public class Child extends Father{
private String name = "孩子";
public static void main(String args[]){
Child c = new Child();
System.out.println(c.getName());
}
}
class Father {
private String name = "父亲";
public String getName(){
return name;
}
}// output: "父亲"
Answer:
Child
类继承了Father
类,当然也继承了Father
类中的getName()
方法。当Child
对象调用getName()
方法的时候,要去Father
类中进行调用,name
属性的值为”父亲”。这里,我的理解是自动向上转型。
当代码变为如下:
public class Child extends Father{
private String name = "孩子";
@Override
public String getName(){
return name;
}
public static void main(String args[]){
Father c = new Child();// 或者 Child c = new Child()
System.out.println(c.getName());
}
}
class Father {
private String name = "父亲";
public String getName(){
return name;
}
}// output: "孩子"
输出的结果都是:”孩子”。
当代码变为如下:
public class Child extends Father{
private String name = "孩子";
@Override
public String getName(){
return name;
}
public static void main(String args[]){
Father c = new Father();
System.out.println(c.getName());
}
}
class Father {
private String name = "父亲";
public String getName(){
return name;
}
}// output: "父亲"
输出结果是: “父亲”。