文章目录
- 谈谈你对java的理解
- 平台无关性如何实现
- 为什么JVM不直接将源码解析成机器码去执行
- JVM如何加装.class文件
- 什么是反射
- 写一个反射的例子
- 谈谈ClassLoader
- 类从编译到执行的过程
- 谈谈ClassLoader
- ClassLoader的种类
- 自定义ClassLoader的实现
- 双亲委派机制
- 过程
- 为什么要设计这种机制
- 类加载方式
- loadClass和forName的区别
- 类的装载过程
- java 的内存模型
谈谈你对java的理解
- 平台无关性
- GC
- 语言特性(泛型、反射、兰姆达表达式)
- 面向对象(封装、继承、多态)
- 类库
- 异常处理
平台无关性如何实现
Compile Once, Run Anywhere
java源码首先被编码成字节码(.class文件),再由不同平台的JVM进行解析。java语言在不同平台上运行时不需要进行重新编译,java虚拟机在执行字节码的时候,把字节码转换为具体平台上的机器指令。
为什么JVM不直接将源码解析成机器码去执行
- 准备工作:每次执行都需要进行各种检查
- 兼容性:也可以将别的语言解析成字节码
JVM如何加装.class文件
什么是反射
java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
反射就是把java类中的各种成分映射成一个个java对象
写一个反射的例子
package javabasic.reflect;
public class Robot {
private String name;
public void sayHi(String helloSentence){
System.out.println(helloSentence + " "+ name);
}
private String throwHello(String tag){
return "Hello" + tag;
}
}
package javabasic.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectSample {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class rc = Class.forName("javabasic.reflect.Robot");
Robot r = (Robot) rc.newInstance();
System.out.println("Class name is "+ rc.getName());
//r.sayHi("bob");
Method getHello = rc.getDeclaredMethod("throwHello",String.class);
getHello.setAccessible(true);//throwHello是私有方法,直接访问会报错
Object str = getHello.invoke(r, "Bob");
System.out.println("getHello result is "+ str);
Method sayHi = rc.getMethod("sayHi", String.class);
sayHi.invoke(r, "Welcome");
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(r,"Alice");
sayHi.invoke(r, "Welcome");
}
}
//input
Class name is javabasic.reflect.Robot
getHello result is HelloBob
Welcome null
Welcome Alice
Process finished with exit code 0
谈谈ClassLoader
类从编译到执行的过程
- 编译器将Robot.java源文件编译成Robot.class字节码文件
- ClassLoader将字节码转换为JVM中的Class对象
- JVM利用Class对象实例化Robot对象
谈谈ClassLoader
ClassLoader主要工作在类加载阶段,其主要作用是从系统外获得Class二进制数据流。
它是java的核心组件,所有的Class都由ClassLoader进行加载,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给java虚拟机进行连接、初始化等操作。
ClassLoader的种类
自定义ClassLoader的实现
关键函数:
protected Class<T> findClass(String name) throws ClassNotFoundException{
throw new ClassNotFoundException(name);
}
protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError{
return defineClass(null, b, off, len, null);
}
package javabasic.reflect;
import java.io.*;
public class MyClassLoader extends ClassLoader{
private String path;
private String classLoaderName;
public MyClassLoader(String path, String classLoaderName){
this.path = path;
this.classLoaderName = classLoaderName;
}
//用于寻找类文件
public Class findClass(String name){
byte[] b = new byte[0];
try {
b = loadClassData(name);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, b, 0,b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) throws IOException {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(new File(name ));
out = new ByteArrayOutputStream();
int i = 0;
while((i = in.read()) != -1){
out.write(i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
out.close();
in.close();
}
return out.toByteArray();
}
}
双亲委派机制
过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。
为什么要设计这种机制
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
类加载方式
- 隐式加载 new
- 显式加载 loadClass,forName等
loadClass和forName的区别
Class.forName得到的class是完成初始化的
Classloader.loadClass得到的class是还没完成链接的
类的装载过程
- 类加载: 通过classloader加载字节码文件,生成class对象
- 链接
- 校验:检查加载的class的正确性和安全性
- 准备:为类变量分配存储空间并设置类变量的初始值
- 解析:JVM将常量池内的符号引用转换为直接引用
- 初始化 :执行类变量赋值和静态代码块
java 的内存模型
- 程序计数器
- 当前线程所执行的字节码行号指示器(逻辑)
- 改变计数器的值来选取下一条所需要执行的字节码指令
- 和线程是一对一的关系(线程私有)
- 对java方法计数,如果是native方法则计数器的值为undefined
- 不必担心内存泄漏
- java虚拟机栈
- java方法执行的内存模型
- 包含多个栈帧,栈帧包含了局部变量表,操作栈,动态链接,返回地址等
局部变量表和操作数栈
局部变量表包含方法执行过程中的所有变量
操作数栈:入栈,出栈,赋值,交换,产生消费变量
- 本地方法栈 :与虚拟机栈类似,主要作用于标注了native的方法
- 元空间和永久代的区别
元空间使用本地内存,永久代使用的是jvm的内存 - meta Space比permGen的优势
- 字符串常量池存在永久代中,容易出现性能问题和内存溢出
- 类方法的信息大小难确定,给永久代的大小指定带来困难
- 永久代会给GC带来不必要的复杂性
- 方便HotSpot与其他JVM如Jrockit的集成
- Java堆
- 对象实例的分配区域
- GC管理的主要区域