JAVA中的反射
一、说说你对反射的理解
反射是要能获取到java类中的字节码,获取字节码的方法有3种:
-
Class.forName(ClassName);
-
类名.class;
-
this.getClass();
然后将字节码中的变量、方法、构造函数映射成相应的Field、Method、Construct等,供我们进行丰富的操作。
JAVA中的动态代理
1、动静态代理的区别,什么场景使用?
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道要代理的是什么,二动态代理不知道要代理什么东西,只有在运行的时候才知道。
动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,就是时你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。
还有一种动态代理CgLib,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。
AOP编程就是基于动态代理实现的,比如著名的Spring框架、Hibernate框架等等都是动态代理的使用例子。
JAVA中的设计模式&回收机制
一、你所知道的设计模式有哪些?
java中一般认为有23种设计模式,我们不需要所有的都会,但是掌握的越多越好。
总体来讲设计模式分为三大类:
创建型模式(5):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构性模式(7):适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为型模式(11):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
二、单例模式
饿汉式
public class SingletonHunger {
private static volatile SingletonHunger single = new SingletonHunger();
public SingletonHunger() {
}
public SingletonHunger getInstance() {
return single;
}
}
懒汉式
public class SingletonLazy {
private static volatile SingletonLazy single = null;
public SingletonLazy() {
}
public SingletonLazy getInstance() {
if (single == null) {
synchronized (this) {
if (single == null) {
single = new SingletonLazy();
}
}
}
return single;
}
}
三、工厂模式
工厂模式的好处是把任务操作的代码和创建任务的代码进行了解耦,对于不同的任务,需要分别创建该任务的工厂和执行方式,确保每个任务都是相对独立的;
但是对于客户端使用时,只需要确认使用的是哪个工厂,其余的功能和流程完全不受影响,甚至可以考虑使用反射,对工厂进行动态判断,极大地简化了操作流程。
工厂接口
public interface Factory {
public Producer produce();
}
操作接口
public interface Producer {
public void produce();
}
任务执行工厂
public class ShirtFactory implements Factory {
@Override
public Producer produce() {
return new ShirtProducer();
}
}
public class ShoesFactory implements Factory {
@Override
public Producer produce() {
return new ShoesProducer();
}
}
任务执行类
public class ShirtProducer implements Producer {
@Override
public void produce() {
System.out.println("produce Shirt");
}
}
public class ShoesProducer implements Producer {
@Override
public void produce() {
System.out.println("produce shoes");
}
}
四、建造者模式
建造者模式和工厂模式的区别在于,工厂模式把对象创建出来的时候就已经是一个完整的不可改变的对象,但是建造者模式把对象的各个零件都进行了拆分,在创建对象的时候,可以根据创建者想要的效果自行搭配零件,最终创建出来的对象是使用者自己设计过的独一无二的产品。
实体类
public class Computer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Computer(Builder builder) {
cpu = builder.getCpu();
screen = builder.getScreen();
memory = builder.getMemory();
mainboard = builder.getMainboard();
}
}
建造者
public class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard
public String getCpu() {
return cpu;
}
public String getScreen() {
return screen;
}
public String getMemory() {
return memory;
}
public String getMainboard() {
return mainboard;
}
public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public Computer build() {
return new Computer(this);
}
}
客户端
public class Main {
public static void main(String[] args) {
Computer computer = new Builder()
.cpu("cpu")
.screen("screen")
.memory("memory")
.mainboard("mainboard")
.build();
}
}
五、适配器模式
常出国的人都清楚,国内和国外的电源插头标准是不一样的,所以转换插头是国外出行必不可少的物品。它的作用就是把国外的电源标准转换为国内的电器使用的标准,保证旅行者即使在国外,也能使用国内电源标准的电器。
适配器模式就是这样的作用,把不同标准的接口、类、对象转化为一个统一标准的输出。
原有标准类
public class OriginalStandard {
public void method1() {
System.out.println("this is original Standard");
}
}
新标准接口
public interface TargetStandard {
public void method2();
}
适配器类
public class Adapter extends OriginalStandard implements TargetStandard {
@Override
public void method2() {
method1();
System.out.println("start adapting method1");
System.out.println("adapt finished,now this is method2");
}
}
客户端
public class Main {
public static void main(String[] args) {
TargetStandard target = new Adapter();
target.method2();
}
}
六、装饰者模式
装饰者模式就是要给对象加上一些新的功能,同时又不影响对象已有的功能,这要求装饰者的对象和被装饰者的对象实现的是同一个接口,装饰者对象持有被装饰者的实例
被装饰者
public abstract class AttachedPropertiesDecorator implements Man {
private Man man;
public AttachedPropertiesDecorator(Man man) {
this.man = man;
}
public void getManDesc() {
man.getManDesc();
}
}
被装饰者实体
public interface Man {
public void getManDesc();
}
public class NormalMan implements Man {
private String name = null;
public NormalMan(String name) {
this.name = name;
}
@Override
public void getManDesc() {
System.out.print(name + ": ");
}
}
装饰者
public class DepositDecoratorImpl extends AttachedPropertiesDecorator {
private String deposit = "有存款";
public DepositDecoratorImpl(Man man) {
super(man);
}
public void addDeposit() {
System.out.print(deposit + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
addDeposit();
}
}
public class HouseDecoratorImpl extends AttachedPropertiesDecorator {
private String house = "有房";
public HouseDecoratorImpl(Man man) {
super(man);
}
public void addHouse() {
System.out.print(house + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
addHouse();
}
}
public class QualityDecoratorImpl extends AttachedPropertiesDecorator {
private String quality = "有好品质";
public QualityDecoratorImpl(Man man) {
super(man);
}
public void addQuality() {
System.out.print(quality + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
}
}
public class CarDecoratorImpl extends AttachedPropertiesDecorator{
private String car = "有车";
public CarDecoratorImpl(Man man) {
super(man);
}
public void addCar() {
System.out.print(car + " ");
}
@Override
public void getManDesc() {
super.getManDesc();
addCar();
}
}
客户端
public class Main {
public static void main(String[] args) {
Man man = new NormalMan("张三");
Man man1 = new CarDecoratorImpl(man);
Man man2 = new HouseDecoratorImpl(man1);
Man man3 = new DepositDecoratorImpl(man2);
System.out.println("层层装饰:");
man3.getManDesc();
System.out.println();
System.out.println("重复装饰(有两个'有存款'):");
Man man4 = new DepositDecoratorImpl(man3);
man4.getManDesc();
System.out.println();
System.out.println("任意修饰:");
Man man5 = new QualityDecoratorImpl(man1);
man5.getManDesc();
System.out.println();
System.out.println("直接得到修饰结果:");
Man man6 = new HouseDecoratorImpl(new DepositDecoratorImpl(new NormalMan("李四")));
man6.getManDesc();
System.out.println();
}
}
输出
七、策略模式
策略模式比较接近于简单工厂模式,它通过简单地判断,确定使用具体的那种方式执行任务,只不过简单工厂对状态进行判断,策略模式直接对需要执行的策略进行判断,或者直接调用需要执行的策略(算法)。
算法接口
public abstract class Sale {
abstract public double calculate(double price);
}
具体算法实现
public class HalfSale extends Sale {
@Override
public double calculate(double price) {
return price * 0.5;
}
}
客户端调用
public class Main {
public static void main(String[] args) {
Sale halfSale = new HalfSale();
double price = halfSale.calculate(10);
}
}
八、观察者模式
观察者模式类似于推送订阅服务,只要订阅了当前服务,在服务有更新的时候,就会把消息及时通知给你。
接收者
public interface Employee {
public void observe();
}
public class Employee1 implements Employee {
@Override
public void observe() {
System.out.println("Employee1 has received");
}
}
public class Employee2 implements Employee {
@Override
public void observe() {
System.out.println("Employee2 has received");
}
}
观察者
public interface Observer {
public void add(Employee employee);
public void del(Employee employee);
public void notifyEmployee();
public void operation();
}
public class ObserverOperation implements Observer {
List<Employee> employees = new ArrayList<>();
@Override
public void add(Employee employee) {
employees.add(employee);
}
@Override
public void del(Employee employee) {
employees.remove(employee);
}
@Override
public void notifyEmployee() {
for (Employee employee : employees) {
employee.observe();
}
}
@Override
public void operation() {
System.out.println("Boss is coming!");
notifyEmployee();
}
}
客户端
public class Main {
public static void main(String[] args) {
ObserverOperation observer = new ObserverOperation();
observer.add(new Employee1());
observer.add(new Employee2());
observer.operation();
}
}
输出
九、JVM垃圾回收机制和常见算法
GC(Garbage Collector)再回首对象前首先必须发现哪些是无用对象,如何发现这些无用对象?常用的算法如下:
1、引用计数器算法(废弃)
引用计数器算法就是给每个对象设置一个计数器,当有地方调用这个引用对象的时候,计数器就+1,当引用失效的时候就-1,当计数器为0的时候,JVM就认为该对象已经被不再使用,可被回收。
引用计数器不能解决循环引用的问题,比如A引用B,B引用A,但是没有其他地方应用A、B,同时每次计数器的增加和减少都会带来很多额外开销,所以在JDK1.1之后,这个算法已经被不再使用。
2、根搜索算法
根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径称为引用链(Reference Chain),当一个对象没有被GC Roots的引用链链接的时候,说明这个对象是不可用的
GC Roots对象包括:
-
虚拟机栈(栈帧中的本地变量表)中的引用的对象
-
方法去中的类静态属性引用的对象
-
方法区中常亮用用的对象
-
本地方法中JNI(Native方法)的引用对象
通过上面的算法搜索到无用对象之后,就是回收过程,回收算法如下
1、标记--清除算法
标记--清除算法包括两个阶段:标记和清除。在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。标记--清除算法是最基础的收集算法,标记和清除阶段的效率不高,而且清楚后会产生大量的不连续空间,这样当程序需要分配大内存对象是,可能无法找到足够的连续空间。
2、复制算法
赋值算法是把内存分成大小相等的两块,每次实用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清除掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一般,造成内存的利用率不高。现在的JVM用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是1:1(大概是8:1)。
3、标记--整理算法
标记--整理算法和标记--清除算法一样,但是标记--整理算法不是吧存货对象复制到另一块内存,而是把存货对象往内存的一端移动,然后直接回收边界以外的内存,标记--整理算法提高了内存的利用率,并且它适合在收集对象存货时间较长的老年代。
4、分代收集
分带手机是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记--整理算法。
十、谈谈JVM的内存结构和内存分配
java内存模型
java虚拟机将其管辖的内存大致分为三个逻辑部分:方法区,java栈和java堆。
-
方法去是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会再运行时改变。常量池,源代码中的命名常量、String常量和static变量保存在方法去。
-
java stack是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。最典型的Stack应用是方法的调用,java虚拟机每调用一次方法就创建一个方法帧,退出该方法则对应的方法帧被弹出。占中存储的数据也是运行时确定的。
-
java堆分配意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命周期在编译时无法确定的。java对象的内存总是在heap中分配。
java的内存分配
-
基础数据类型直接在栈空间分配。
-
方法的形式参数,直接在站空间分配,当方法调用完成后从站空间回收。
-
引用数据类型,需要用new来创建,即在栈空间分配一个地址空间,又在堆空间分配对象的类变量。
-
方法的引用参数,在栈空间分配一个地址变量,并指向堆空间的对象区,当方法调用完后从栈空间回收。
-
局部变量new出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。
-
方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放。
-
字符串常量在DATA区域分配,this在堆空间分配。
-
数组即在栈空间分配,又在堆空间分配数据实际大小。
十一、Java中引用类型都有哪些?(重要)
Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
强引用(StrongReference)
这个就不多说,我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用,那么垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
Java的对象是位于heap中的,heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下代码:
第一行在heap堆中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。
第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的abc对象已经有3个引用,显然此时abc对象仍是强可及的。
第四行之后heap中对象不再是强可及的,变成软可及的。
第五行执行之后变成弱可及的。
软引用(SoftReference)
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高
速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用时执行以下过程,以上面的softRef为例:
1 首先将softRef的referent(abc)设置为null,不再引用heap中的new String("abc")对象。
2 将heap中的new String("abc")对象设置为可结束的(finalizable)。
3 当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放,softRef被添加到它的ReferenceQueue(如果有的话)中。
注意:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有。
被Soft Reference 指到的对象,即使没有任何Direct Reference,也不会被清除。一直要到JVM 内存不足且没有Direct Reference 时才会清除,SoftReference 是用来设计object-cache 之用的。如此一来SoftReference 不但可以把对象cache 起来,也不会造成内存不足的错误(OutOfMemoryError)。
弱引用(WeakReference)
如果一个对象只具有弱引用,那该类就是可有可无的对象,因为只要该对象被gc扫描到了随时都会把它干掉。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null。先看一下和gc交互的过程再说一下他的作用。
1不把referent设置为null, 直接把heap中的new String("abc")对象设置为可结束的(finalizable)。
2 与软引用和弱引用不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象。