java面试阿里第1期--二面(1)gc四引用(2)类加载机制(3)Atomic、cas(4)CPU很高,怎么去排查(5)RPC(6)怎么实现远程过程调用(8)BIO与NIO、AIO的区别(9)AOP

1.什么时候不用分代收集的方式

2.软引用、弱引用、强引用、虚引用(GC)

强引用:
只要引用存在,垃圾回收器永远不会回收.
例如:
Object obj = new Object();
//可直接通过obj取得对应的对象 如obj.equels(new Object());
而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。

软引用:
非必须引用,内存溢出之前进行回收.
例如:
Object obj = new Object();
SoftReference sf = new SoftReference(obj);
obj = null;
sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

弱引用:
第二次垃圾回收时回收.
例如:
Object obj = new Object();
WeakReference wf = new WeakReference(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。

虚引用:
垃圾回收时回收,无法通过引用取到对象值
例如:
Object obj = new Object();
PhantomReference pf = new PhantomReference(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除。

引用更加详细的解析,请参考: https://www.cnblogs.com/yw-ah/p/5830458.html

3. 类加载机制是什么

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制.
补充:
1. 什么情况会导致类的加载
(1) 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化
生成这 4 条指令的常见 Java 代码场景:
new:使用 new 关键字实例化对象
getstatic:读取一个类的静态字段
putstatic:设置一个类的静态字段
invokestatic:调用类的静态方法
(2) 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
(3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
(4) 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类
(5) 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄时,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化

注意: 接口的加载过程与类加载过程稍有不同,接口中不能使用 static{} 语句块,但编译器仍然会为接口生成 () 类构造器,用于初始化接口中所定义的成员变量。接口与类真正有所区别是情况 (3):当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口的初始化时,并不要求其父接口全部都完成了初始化,只有在真正用到了父接口的时候才会初始化

2.什么是类加载器
可以通过一个类的全限定名来获取描述此类的二进制字节流,完成这一动作的代码模块被称为类加载器.
2.1类与类加载器
类加载器虽只用于实现类的加载动作,还可以与类一起来确立当前类在 Java 虚拟机中是否是唯一的,两个类被同一个类加载器加载,这两个类才相等.
2.2虚拟机划分
参考 java面试(七)第3点
2.3双亲委派模型
约束:双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。类加载器之间的父子关系一般不以继承关系实现,而是使用组合关系来复用父加载器的代码.
工作过程:类加载器收到一个类加载的请求,自己不会先加载,而是把该请求委派给父类加载器,每一层的类加载器都是如此,因此最终该请求会被传送到顶层的启动类加载器中,只有当父类加载器无法完成加载请求(对应搜索范围内没有找到所需的类)时,子加载器才尝试自己去加载.
好处:双亲委派模型可以保证系统中的类在各种类加载器环境中都是同一个类,即使用户自定义一个和系统同名的类,也不能被类加载器加载,保证了 Java 程序的稳定运作,因为无论哪一个类加载器要加载一个类,最终都是委派给最顶端的启动类加载器进行加载.
如何破坏双亲委派模型:
双亲委派模型不是强制性的约束模型,Java 中大部分的类加载器都遵循这个模型,但是有例外.
1.双亲委派模型是在 JDK 1.2 之后引入的,类加载器是在 JDK 1.0 就已经存在。所以在 JDK 1.2 之前,用户可用继承 java.lang.ClassLoader 去重写 loadClass() 方法
2.当基础类要调用用户的代码时,父类加载器可以请求这类加载器去完成类加载的动作
3.对程序动态性的追求,如:代码热替换、代码热部署等.

参考文章: https://blog.csdn.net/qq_21586317/article/details/80976440.

4.结合tomcat说一下双亲委派

5. 并发里面的Atomic底层

Atomic简介:
Atomic包是java.util.concurrent下的另一个专门为线程安全设计的Java包,包含多个原子操作类。这个包里面提供了一组原子变量类。
其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。
实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。—— 引自@chenzehe 的博客。
Atomic都是CAS来实现原子性,从而达到线程安全的目的。

详细参考:https://blog.csdn.net/qq_34871626/article/details/81411815

6. CAS会遇到什么问题(ABA),除了(ABA)问题呢? 怎么去优化CAS?

CAS概述:
CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术.

CAS定义:
CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。
CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

详细请参考: https://blog.csdn.net/wengyupeng/article/details/90239411

CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
1.循环时间长开销很大.
2.只能保证一个共享变量的原子操作.
3.ABA问题.

1. 循环时间长开销很大:
我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
2. 只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
3. 什么是ABA问题?ABA问题怎么解决?
问题:如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?
解释:如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题
解决:Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效.

详解请参考: https://blog.csdn.net/v123411739/article/details/79561458

CAS优化(Java8):
问题:由于线程太密集了,太多人想要修改 i 的值了,进而大部分人都会修改不成功,白白着在那里循环消耗资源
解决:为了解决这个问题,Java8 引入了一个 cell[] 数组,它的工作机制是这样的:假如有 5 个线程要对 i 进行自增操作,由于 5 个线程的话,不是很多,起冲突的几率较小,那就让他们按照以往正常的那样,采用 CAS 来自增吧。但是,如果有 100 个线程要对 i 进行自增操作的话,这个时候,冲突就会大大增加,系统就会把这些线程分配到不同的 cell 数组元素去,假如 cell[10] 有 10 个元素吧,且元素的初始化值为 0,那么系统就会把 100 个线程分成 10 组,每一组对 cell 数组其中的一个元素做自增操作,这样到最后,cell 数组 10 个元素的值都为 10,系统在把这 10 个元素的值进行汇总,进而得到 100,二这,就等价于 100 个线程对 i 进行了 100 次自增操作。

详解请参考: https://www.cnblogs.com/kubidemanong/p/10681550.html

7. 项目遇到问题,比如CPU很高,怎么去排查?

Linux端:
1 查询获取当前使用进程的pid
ps -ef | grep 进程名
2 打印当前java堆栈情况
jstack -l pid > /tmp/dz01.log
3 查看当前进程消耗资源的线程pid
top -Hp pid
4 将线程Pid转为16进制,根据该16进制值去打印的堆栈日志内查询,查看该线程所驻留的方法位置。(基本上这步已经能够定位大多数问题了)
5 若依然无法定位问题,可以查看堆中对象数量和大小
jmap -histo pid
6 另外可以通过如下命令生成堆dump文件拿到本地通过jdk/bin下自带的jvisualvm.exe打开图形化显示详细的内存信息。
jmap -dump:format=b,file=heapdump pid
扩展介绍:
jstack : 主要用于生成指定进程当前时刻的线程快照,线程快照是当前java虚拟机每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待。
jmap:主要用于打印指定java进程的共享对象内存映射或堆内存细节。
堆Dump:堆Dump是反映堆使用情况的内存镜像,其中主要包括系统信息、虚拟机属性、完整的线程Dump、所有类和对象的状态等。一般在内存不足,GC异常等情况下,我们会去怀疑内存泄漏,这个时候就会去打印堆Dump。

Windows端:

  1. 先用top命令,找到cpu占用最高的进程 PID
  2. 再用ps -mp pid -o THREAD,tid,time 查询进程中,哪个线程的cpu占用率高 记住TID
  3. jstack pid>> xxx.log 打印出该进程下线程日志
  4. sz xxx.log 将日志文件下载到本地
  5. 将查找到的 线程占用最高的 tid转成16进制
  6. 打开下载好的 xxx.log 通过 查找方式 找到 对应线程 进行排查

容易出现cpu占用过高的几点:

  1. 代码中写死循环时,一直占用cpu
  2. 在循环中不停的创建对象,也会导致GC频繁
  3. System.currentTimeMillis() 采用这种方式去做计时,大概占用了10-20%cpu,因为不停的调用
  4. C2 CompilerThread0 --这个线程大概在进程启动前一段时间会占用 10-20%cpu,后面会下降到1%

具体参考:https://blog.csdn.net/coderpopo/article/details/80332496

8. 了解RPC吗?

百度百科给出的解释是这样的:“RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。

1. 基于 XML-RPC

什么是XML-RPC?
XML-RPC是最简单,最简单的Web服务方法之一,它使计算机可以轻松地调用其他计算机上的程序。
• XML-RPC允许程序通过网络进行函数或过程调用。
• XML-RPC使用HTTP协议将信息从客户端计算机传递到服务器计算机。
• XML-RPC使用一个小的XML词汇表来描述请求和响应的性质。
• XML-RPC客户端在XML请求中指定过程名称和参数,服务器在XML响应中返回错误或响应。
• XML-RPC参数是一个简单的类型和内容列表 - 结构和数组是最复杂的类型。
• XML-RPC没有对象的概念,也没有包含使用其他XML词汇表的信息的机制。
• 使用XML-RPC和Web服务,Web成为程序连接的集合,计算机在紧密绑定的路径上交换信息。

为何选择XML-RPC?
如果您需要集成多个计算环境,但不需要直接共享复杂的数据结构,XML-RPC可以让您快速,轻松地建立通信。
即使您在单一环境中工作,RPC方法可以轻松连接具有不同数据模型或处理期望的程序,并且可以轻松访问可重用逻辑。
• XML-RPC是在计算机之间建立各种连接的出色工具。
• XML-RPC为集成商提供了使用标准词汇表和方法来交换信息的机会。
• XML-RPC最明显的应用领域是连接不同类型的环境,允许Java与Perl,Python,ASP等进行通信。
XML-RPC技术概述
XML-RPC由三个相对较小的部分组成:
XML-RPC数据模型:用于传递参数,返回值和错误(错误消息)的一组类型。
XML-RPC请求结构:包含方法和参数信息的HTTP POST请求。
XML-RPC响应结构:包含返回值或故障信息的HTTP响应。

学习参考: https://www.yiibai.com/xml-rpc/xml_rpc_summary.html

2. 基于JSON-RPC

JSON-RPC简介
JSON-RPC是一种基于JSON的跨语言远程调用协议。有文本传输数据小,便于调试扩展的特点。
请求
JSON-RPC非常简单,在请求时向服务器传输数据格式如下(基于JSON2.0)
{
“jsonrpc” : 2.0,
“method” : “sayHello”,
“params” : [“Hello JSON-RPC”],
“id” : 1
}

jsonrpc:定义JSON-RPC版本。
method:调用的方法名。
params:方法传入的参数,若无参数则为null。
id:调用标识符。可以为字符串,不推荐包含小数(不能准确二进制化),或为null(可能引起混乱)。
响应
服务器返回的数据格式也为JSON,其格式如下:
{
“jsonrpc” : 2.0,
“result” : “Hell JSON-RPC”,
“error” : null,
“id” : 1
}

• jsonrpc:定义JSON-RPC版本。
• result:方法返回值,调用成功时,不能为null,调用错误时,必须为null。
• error:调用时错误,无错误返回null,有错误时则返回一个错误对象。
• id:调用标识符,与调用方传入的标识一致,当请求中的id检查发生错误时(转换错误/无效请求),则必须返回null

学习参考: https://blog.csdn.net/jackyrongvip/article/details/87895694

3. 基于 ZeroRPC

以上介绍的两种rpc远程调用方式,如果你足够细心,可以发现他们都是http+rpc 两种协议结合实现的。
接下来,我们要介绍的这种(zerorpc),就不再使用走 http 了。
zerorpc 这个第三方库,它是基于TCP协议、 ZeroMQ 和 MessagePack的,速度相对快,响应时间短,并发高。zerorpc 和 pyjsonrpc 一样,需要额外安装,虽然SimpleXMLRPCServer不需要额外安装,但是SimpleXMLRPCServer性能相对差一些

参考:https://baijiahao.baidu.com/s?id=1637758852641939872&wfr=spider&for=pc

9.自己设计一下怎么实现远程过程调用

java面试阿里第1期--二面(1)gc四引用(2)类加载机制(3)Atomic、cas(4)CPU很高,怎么去排查(5)RPC(6)怎么实现远程过程调用(8)BIO与NIO、AIO的区别(9)AOP_第1张图片RPC 服务方通过 RpcServer 去导出(export)远程接口方法,而客户方通过 RpcClient 去引入(import)远程接口方法。
客户方像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理RpcProxy 。
代理封装调用信息并将调用转交给RpcInvoker 去实际执行。
在客户端的RpcInvoker 通过连接器RpcConnector 去维持与服务端的通道RpcChannel,并使用RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。
RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,同样使用RpcProtocol 执行协议解码(decode)。
解码后的调用信息传递给RpcProcessor 去控制处理调用过程,最后再委托调用给RpcInvoker 去实际执行并返回调用结果。

10. BIO与NIO、AIO的区别

IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。

BIO

在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端线程会等待请求结束后才继续执行。

NIO

NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题: 在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。
NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。
NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。

在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。
HTTP/1.1出现后,有了Http长连接,这样除了超时和指明特定关闭的http header外,这个链接是一直打开的状态的,这样在NIO处理中可以进一步的进化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端的应用的处理只需要执行队列里面的就可以了,这样请求处理和后端应用是异步的.当后端处理完,到全局地方得到现场,产生响应,这个就实现了异步处理。

AIO

AIO与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。

总结:
BIO是一个连接一个线程。
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO是一个请求一个线程。
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO是一个有效请求一个线程。
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。

学习参考: https://blog.csdn.net/dreamer23/article/details/80903978

11. 4核cpu,100个http连接,用bio和nio分别需要多少个线程

12.ip是不是可靠的,tcp怎么保证可靠

IP和UDP是不可靠的,而TCP是可靠的

UDP在传输数据之前不需要先建立连接,远地主机的运输层在接收到UDP报文后,不需要确认。提供不可靠交付,比如说qq用的就是UDP协议,优点是传输效率高。

TCP提供面向连接服务,传输数据前必须先建立连接,接收方确认,数据传输后要释放链接。比如说电子邮件。

ip也属于TCP/IP协议族,用来链接互联网计算机,区分计算机的,相当于一个计算机身份证吧,没啥安全不安全的说法吧。

TCP因为其拥有三次握手双向机制,这一机制保证校验了数据,保证了他的可靠性。UDP就没有了,UDP信息发出后,不验证是否到达对方,所以不可靠。但是就速度来说,还是UDP协议更高,毕竟其无需重复返回验证,只是一次性的。

13.说一下Spring AOP底层机制

概念

1.切面(Aspect):切面就是一个关注点的模块化,如事务管理、日志管理、权限管理等;

2.连接点(Joinpoint):程序执行时的某个特定的点,在Spring中就是一个方法的执行;

3.通知(Advice):通知就是在切面的某个连接点上执行的操作,也就是事务管理、日志管理等;

4.切入点(Pointcut):切入点就是描述某一类选定的连接点,也就是指定某一类要织入通知的方法;

5.目标对象(Target):就是被AOP动态代理的目标对象;

AOP动态代理模式

JDK动态代理
如果是面向接口的动态代理的实现,即JDKProxy,其代理对象必须是某个接口的实现,使用java.lang.reflect.Proxy类根据一个被代理对象产生一个代理对象userDAOProxy,通过Proxy类的调用静态方法newProxyInstance,根据要实现的接口来产生(这里为UserDao接口)(也就是说接口里面有哪些方法,我生成的代理里面就有哪些方法);以及实现java.lang.reflect.InvocationHandler接口,实现invoke方法实现方法的截获处理,也就是在方法的前后加上业务逻辑。
当你想在多个方法前后加上业务逻辑的时候,可以使用动态代理,更加灵活方便,代码的可重用性大大的提高。
根据一个被代理对象通过Proxy静态方法newProxyInstance产生代理对象:
newProxyInstance里面的参数解释:
第一个参数是说与被代理对象有同一个ClassLoader,
第二个参数说产生的代理对象实现的那个接口应该与被代理对象实现同一个接口(UserDao),也可以这样写new Class[]{UserDao.class}。
第三个参数:当产生代理之后,调用代理里面的方法后要用哪个Handler进行处理。
LogIntercepter li = new LogIntercepter();
li.setTarget(userDAO);//引入一个被代理的对象userDAO
UserDao userDAOProxy = (UserDao) Proxy.newProxyInstance(userDAO.getClass().getClassLoader(),userDAO.getClass().getInterfaces(), li);
实现InvocationHandler接口 :
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
beforeMethod();//在方法前面添加业务逻辑,也就是日志
m.invoke(target, args);//target Method方法所属的对象,表示被代理对象动态调用invoke()
return null;
}

Cglib动态代理
如果没有实现接口,也没有关系,可以用CGLib(面向Class)实现AOP。
CGLibProxy与JDKProxy的代理机制基本类似,只是其动态代理的代理对象并非某个接口的实现,而是针对目标类扩展的子类。换句话说JDKProxy返回动态代理类,是目标类所实现接口的另一个实现版本,它实现了对目标类的代理(如同UserDAOProxy与UserDAOImp的关系),而CGLibProxy返回的动态代理类,则是目标代理类的一个子类(代理类扩展了UserDaoImpl类)
Enhancer和MethodInterceptor在CGLib中负责完成代理对象创建和方法截获处理。
Enhancer创建代理对象,实现MethodInterceptor接口,实现intercept方法来进行方法截取处理。
(CGLib (Code Generation Library) 字节码类库是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO字节码的动态生成。CGLib 比 Java 的 java.lang.reflect.Proxy 类更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法。)

JDK动态代理和CGLIB字节码生成的区别:

  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类.
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
    因为是继承,所以该类或方法最好不要声明成final。
    JDK代理是不需要以来第三方的库,只要JDK环境就可以进行代理,它有几个要求
  • 实现InvocationHandler
  • 使用Proxy.newProxyInstance产生代理对象
  • 被代理的对象必须要实现接口
    CGLib 必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承
    AOP的应用:
    做权限的检查:设计完备的权限管理组件,完成以往需要大费周折才能完成的权限判定功能。
    但是目前还没有一个完善的实现,一方面是因为权限检查过于复杂多变,不同的业务逻辑中的权限判定逻辑可能多种多样;另一方面,就目前的AOP应用的粒度而言,“权限管理”作为一个切面显得过于庞大,需要进一步设计,设计复杂,实现难度大。
    ,做日志,做审计,做性能,做事务的处理。项目里面主要用在了声明式的事务处理上。

学习参考:https://www.cnblogs.com/200911/archive/2012/10/09/2716882.html

14.如何把一个ip转化为int数字,实现互相转化

对于ipv4的地址来说,如果用字符串的形式存储的话,其占用字节就比较大,比如对于IPv4地址0.0.0.0的字符串,就需要7个字节,IPv4为255.255.255.255 的字符串,需要15个字节,也就是说存储一个ip需要占用7~15个字节。

那么有没有更节省空间的存储方式呢?答案是有。

方案1: 直接把字符串中的’.'去掉,不就变成一个数字了嘛,比如 “255.255.255.255” 变成 255255255255,然而我们知道int所能表示的最大值 = Integer.MAX_VALUE = 2^31-1 = 2147483647, 255255255255 > 2^31-1,所以需要用长整形long来表示,长整形占用8个字节,也就是说我们将7~15个字节转换为8字节,在绝大多数情况下是节省空间了的。

方案2: 因为考虑到IPv4的地址本质上就是32位的二进制串,而一个int类型的数字刚好为4个字节32个bit位,所以刚好可以用一个int类型的数字转表示IPv4地址。所以,我们可以用4个字节的int数字表示一个ip地址,这样可以大大节省空间。
这里只讨论方案2 。

演示

对于ipv4地址: 192.168.1.3:
每段都用二进制表示: 192(10) = 11000000(2) ; 168(10) = 10101000(2) ; 1(10) = 00000001(2) ; 3(10) = 00000011(2) 。
所以连在一起就是:11000000101010000000000100000011,对应的int数字就是-1062731775 。

实现代码:

package com.sunjs.kit;
 
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
/**
 * IPv4地址和int数字的互换
 * @author sun
 */
public class IpKit {
 
    /**
     * IPv4地址转换为int类型数字
     * @param ipv4Addr
     * @return
     */
    public static int ipToInt(String ipv4Addr) {
        // 判断是否是ip格式的
        if (!isIPv4Address(ipv4Addr))
            throw new RuntimeException("Invalid ip address");
 
        // 匹配数字
        Pattern pattern = Pattern.compile("\\d+");
        Matcher matcher = pattern.matcher(ipv4Addr);
        int result = 0;
        int counter = 0;
        while (matcher.find()) {
            int value = Integer.parseInt(matcher.group());
            result = (value << 8 * (3 - counter++)) | result;
        }
        return result;
    }
 
    /**
     * 判断是否为ipv4地址
     * @param ipv4Addr
     * @return
     */
    private static boolean isIPv4Address(String ipv4Addr) {
        String lower = "(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])"; // 0-255的数字
        String regex = lower + "(\\." + lower + "){3}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(ipv4Addr);
        return matcher.matches();
    }
     
    /**
     * 将int数字转换成ipv4地址
     * @param ip
     * @return
     */
    public static String intToIp(int ip) {
        StringBuilder sb = new StringBuilder();
        int num = 0;
        boolean needPoint = false; // 是否需要加入'.'
        for (int i = 0; i < 4; i++) {
            if (needPoint) {
                sb.append('.');
            }
            needPoint = true;
            int offset = 8 * (3 - i);
            num = (ip >> offset) & 0xff;
            sb.append(num);
        }
        return sb.toString();
    }
     
    public static void main(String[] args) {
        String ip = "124.202.200.166";
    System.out.println(ipToInt(ip));
    System.out.println(intToIp(2093664422));
    }
     
}


学习参考:https://www.sunjs.com/article/detail/f6a49fa32bef4b63b26aa4a8ac551a79.html

Face your past without regret. Handle your present with confidence.Prepare for future without fear. keep the faith and drop the fear. 面对过去无怨无悔,把握现在充满信心,备战未来无所畏惧。保持信念,克服恐惧!一点一滴的积累,一点一滴的沉淀,学技术需要不断的积淀!

你可能感兴趣的:(java面试)