自身对Java的理解(Java的一些特点):
- 平台无关性:一次编译,到处运行。
- 垃圾回收机制(GC)
- 语言特性:泛型、反射
- 面向对象:封装、继承、多态
- 自带的一些类库
- 有异常处理机制
1、Java如何实现平台无关???
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令 。
2、JVM如何加载.class文件
JVM主要由类加载器、运行时数据区、执行引擎和本地接口组成。 太主要通过类加载器将class文件加载到内存中,并通过执行引擎解析文件中的字节码并提交给操作系统运行。
- 类加载器:依据特定格式,加载class文件到内存
- 执行引擎:对命令进行解析
- 本地接口:融合不同开发语言的原生库为Java所用
- 运行时数据区:JVM内存空间结构模型
3、什么是Java反射机制
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
实例:通过java反射机制来调用Robot类中的属性和方法
//创建Robot类
package com.javabasic.bytecode.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 com.javabasic.bytecode.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("com.javabasic.bytecode.reflect.Robot");//获取类
Robot r = (Robot) rc.newInstance();//实例化对象
System.out.println("class name is" + rc.getName());
Method getHello = rc.getDeclaredMethod("throwHello", String.class);//Method类获取方法
getHello.setAccessible(true);//获取私有方法和属性时必须调用此方法,修改方法参数为true
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");//Field类获取属性
name.setAccessible(true);
name.set(r,"Bob");
sayHi.invoke(r,"welcome");
}
}
4、类从编译到执行的过程
- 编译器先将.Java原文及编译为.class字节码文件。
- 类加载器将字节码转换成JVM中的类的对象。
- JVM利用类的对象经行对象的实例化
5、谈谈类加载器
类加载器主要工作在Class的加载阶段,主要作用是从系统外部获取Class二进制数据流。它是Java的核心组件,所有的Class都是由类加载器加载的,类加载器负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等工作。
6、类加载器的种类
- BootStrapClassLoader:C++编写,加载核心库java.*。
- ExtClassLoader:Java编写,加载扩展库javax.*。
- AppClassLoader:Java编写,加载程序所在目录下的类库。
- 自定义ClassLoader:Java编写,定制化加载。
7、类加载器的双亲委派机制
双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。例如:如上图所示:MyClassLoader->AppClassLoader->Ext-ClassLoader->BootStrap.自定定义的MyClassLoader1首先会先委托给AppClassLoader,AppClassLoader会委托给ExtClassLoader,ExtClassLoader会委托给BootStrap,这时候BootStrap就去加载,如果加载成功,就结束了。如果加载失败,就交给ExtClassLoader去加载,如果ExtClassLoader加载成功了,就结束了,如果加载失败就交给AppClassLoader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的MyClassLoader1类加载器加载,如果加载失败,就报ClassNotFoundException异常,结束。
使用双亲委派机制的原因:避免类被重复加载,加载一次,多次使用。以下为classloader源码,classloader的执行顺序就是:loadClass→findClass→defineClass。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先先检查该类是否被加载,如果被加载过就不重复加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
8、类的加载方式
- 隐式加载:new
- 显式加载:loadclass,forName等
public class LoadDifference {
public static void main(String[] args) throws ClassNotFoundException {
Robot robot = new Robot();//隐式加载
ClassLoader cl = Robot.class.getClassLoader();//显式加载
Class r = Class.forName("com.javabasic.bytecode.reflect.Robot");//显式加载
}
}
9、LoadClass和forName的区别
- Class.forName得到的class是已经初始化完成的
- Classloader.loadClass得到的class是还没有链接的
10、JVM内存模型
- 线程私有:程序计数器、虚拟机栈、本地方法栈
- 线程共享:Java堆、方法区
1)、程序计数器(Program Counter Register)
- 当前线程所执行的字节码的行号指示器。
- 通过改变计数器的值来选取下一条需要执行的字节码指令,如分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 和线程是一对一关系即“线程私有”。
- 对Java方法计数,如果是Native方法则计数器值为空(Undefined)。
- 此内存区域不会发生内存泄露问题。
2)、Java虚拟机栈(Java Virtual Machine Stacks)
- 它的生命周期与线程相同。
- 它描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
其中局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。操作数栈其实就是实现对局部变量的的操作,如变量的运算、赋值等。在Java 虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
3)、本地方法栈
与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。
4)、方法区
方法区用来存储class的基本信息,如类的方法、常量、静态变量、即时编译器编译后的代码等数据。方法区有一种叫法是元空间(Meta Space),也有人叫“永久代”(Permanent Generation)。两者并不等价,但是两者都是方法区的实现。在Java7之后,原先位于方法区的字符串常量池被移动到Java堆中,在jdk1.8之后,用元空间替代了永久代。两者最大的区别是,永久代使用的是jvm的内存,而元空间使用的是本地内存。元空间相较于永久代有下面几个优势:字符串常量池在永久代中,容易出现性能问题和内存溢出;类和方法的信息大小比较难确定,给永久代大小的指定带来困难;永久代会给GC带来不必要的复杂性,效率较低。
5)、Java 堆
是对象实例的分配区域,是GC管理的主要区域。
11、常见面试题
1)、JVM三大性能调优参数-Xms、-Xmx、-Xss的含义
-Xss:规定了每个线程虚拟机栈的大小,一般256k足够,此配置将会影响此进程中并发线程数的大小。
-Xmx:堆的初始大小,即该进程刚刚创建时他的专属Java堆的大小,一旦对象的容量超过Java堆的最大容量,将会扩容至-Xms设置的大小
-Xms:堆能达到的最大值,但是在设置的时候往往将-Xmx和-Xms的大小设置为一样,因为当堆不够用要扩容时,会发生内存抖动,影响程序运行时的稳定性。
2)、Java内存模型中内存分配策略
- 静态存储:编译时确定每个数据目标在运行时的存储空间需求,这种方式不允许有可变的数据类型存在。
- 栈式存储:程序模块的数据区大小在编译的时候时未知的,只有在运行到程序模块入口前才会确定大小,从而分配内存空间
- 堆式存储:编译时或运行到程序模块入口前都无法确定所需内存大小,动态分配。
3)、Java内存模型中堆和栈的联系
引用对象、数组时,栈里定义变量来保存堆中的目标地址的首地址,如下图:
4)、Java内存模型中堆和栈的区别
- 管理方式:栈自动释放,堆需要GC
- 空间大小:栈比堆小
- 碎片相关:栈产生的碎片小于堆
- 分配方式:栈支持静态分配和动态分配,而堆只支持动态分配
- 效率:栈的效率高于堆