整理一些自己不太熟的JAVA基础知识。
JDK、JRE、JVM
JDK(Java Development Kit) 是整个JAVA的核心,包括了Java运行环境(Java Runtime Environment),Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
JRE(Java Runtime Environment,Java运行环境),包含JVM标准实现及Java核心类库。
JVM(Java Virtual Machine),即java虚拟机, java运行时的环境。
基本数据类型
1、基本类型:short 二进制位数:16
包装类:java.lang.Short
最小值:Short.MIN_VALUE=-32768 (-2的15此方)
最大值:Short.MAX_VALUE=32767 (2的15次方-1)
2、基本类型:int 二进制位数:32
包装类:java.lang.Integer
最小值:Integer.MIN_VALUE= -2147483648 (-2的31次方)
最大值:Integer.MAX_VALUE= 2147483647 (2的31次方-1)
3、基本类型:long 二进制位数:64
包装类:java.lang.Long
最小值:Long.MIN_VALUE=-9223372036854775808 (-2的63次方)
最大值:Long.MAX_VALUE=9223372036854775807 (2的63次方-1)
4、基本类型:float 二进制位数:32
包装类:java.lang.Float
最小值:Float.MIN_VALUE=1.4E-45 (2的-149次方)
最大值:Float.MAX_VALUE=3.4028235E38 (2的128次方-1)
5、基本类型:double 二进制位数:64
包装类:java.lang.Double
最小值:Double.MIN_VALUE=4.9E-324 (2的-1074次方)
最大值:Double.MAX_VALUE=1.7976931348623157E308 (2的1024次方-1)
6、笔试踩过的坑:
//-128到127之间不会封装对象而是用常量池的值,不在这个范围才会创建对象
Integer a = 223;
Integer b = 223;
System.out.print(a==b); //false
Integer a = 123;
Integer b = 123;
System.out.print(a==b); //true
几种容器
1. Vector VS. ArrayList:Vector由于使用了synchronized方法-线程安全,性能比ArrayList要差。(非线程安全的集合在多线程中可以使用,但并不能作为多个线程共享的属性,可以作为线程独享属性)
2. HashMap VS. HashTable:HashTable的方法是同步的,而HashMap不是。HashTable不允许null值(key和value均),HashMap允许null(key和value均)。HashTable使用Enumeration,HashMap使用Iterator。Iterator支持fail-fast事件(当多个线程对同一个集合的内容进行操作时可能产生的一种错误机制)。
3. HashMap VS. Concurrent HashMap:ConcurrentHashMap将整个Hash桶进行了分段segment,每个segment上都有锁。访问HashTable的线程都要竞争同一把锁,而Concurrent HashMap实现了锁分段。get方法将要使用的共享变量都定义成volatile,能够在线程之间保持可见性,能够被多线程同时读,且保证不会读到过期的值。
4. LinkedList
ArrayList 基于动态数组实现,LinkedList 基于双向链表实现;
ArrayList 支持随机访问,LinkedList 不支持;
LinkedList 在任意位置添加删除元素更快。
字符串 String
1. String, StringBuffer, StringBuilder
· 都是final,都不允许继承
· String长度是不可变的,StringBuffer、StringBuilder长度是可变的。
·StringBuffer是线程安全的(添加了synchronized修饰),String Builder不是线程安全的。
· 经常改变内容的字符串最好不要用 String。
序列化
1. Serializable接口:将对象转化成字节序列并能够在以后将字节序列完全恢复成原来的对象,可以弥补不同操作系统之间的差异。不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
· writeObject()和readObject()方法。
·可以使用transient关键字修饰不必进行序列化的属性。
2. Externalizable接口:对序列化过程进行控制。
· 添加了writeExternal()和readObject()方法。
3. 注意事项:
· 被static修饰的属性不会被序列化。
· 对象的类名、属性都会被序列化,方法不会被序列化。
· 要保证序列化对象所在类的属性也是可以被序列化的。
·通过网络、文件进行序列化时,必须按照写入的顺序读取对象。
· 反序列化时必须有序列化对象时class文件。
· 最好显式地声明serializableID,因为不同的JVM之间默认生成的serializableID可能不同,会造成反序列化失败。(默认的:private static final long serialVersionUID = 1L; 或根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段)
4. 常见的序列化协议
· XML&SOAP
· JSON
· Thrift、Protobut、 Avro
GC
1. JAVA 是否有内存泄漏和内存溢出?
- 静态集合类, 他们的生命周期跟应用程序一样长。
- 监听器未删除
- 物理连接,比如数据库连接和网络连接,在try里创建,在final中释放。
- 内部类和外部模块等的引用
- 单例模式
2. JAVA垃圾回收机制中的算法
- 如何判断可被回收:
1)引用计数法:引用计数器为0的对象实例被当作垃圾收集。(无法检测循环引用)
2)可达性分析算法:以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。 - 标记-清除算法:从堆栈和静态存储区出发,遍历所有引用,对所有存活的引用设一个标记,当标记工作完成时,清理动作才会开始。所以剩下的堆空间是不连续的。
- 标记-整理算法:在标记-清除算法的基础上,将存活对象往左端空闲区移动。
- 停止-复制算法:把存活对象复制到新堆。
- “自适应”技术:跟踪“标记-清扫”的效果,如果堆空间出现很多碎片,就会切换回“停止-复制”方式。
- 分代回收:
新生代回收:(复制算法)
老年代回收:(标记-清除算法/标记-整理算法)
永久代回收:(即方法区回收)
JVM的方法区,也被称为永久代。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。
I/O
-
事件分离器(把IO请求和读写操作分离):根据处理机制的不同,分为同步的Reactor和异步的Proactor。
Reactor 模型:一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。
(图片转载自https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20IO.md) BIO(同步阻塞IO):
Socket编程就是BIO,一个socket连接一个处理线程(这个线程负责这个Socket连接的一系列数据传输操作)。阻塞的原因在于:操作系统允许的线程数量是有限的,多个socket申请与服务端建立连接时,服务端不能提供相应数量的处理线程,没有分配到处理线程的连接就会阻塞等待或被拒绝。NIO(同步非阻塞new IO): 基于Reactor模型。
1)IO是面向流的,NIO是面向缓冲区的。 数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。
2) Java IO的各种流是阻塞的,NIO是非阻塞式的。
3)NIO是对BIO的改进,“一个请求一个线程”,客户端的socket连接到服务端时,就会在事件分离器注册一个 IO请求事件 和 IO 事件处理器。只有需要进行IO操作的才能获取服务端的处理线程进行IO。AIO(异步阻塞IO):基于Proactor模型。
AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作;NIO则是发出IO请求后,由线程不断尝试获取IO权限,获取到后通知应用程序自己进行IO操作。
5. 通道与缓冲区
- Channel:是对原I/O包中流的模拟。但与流的区别在于,流只能在一个方向上移动(InputStream,OutputStream)但通道是双向的,可以用于读、写或同时用于读写。
通道包括:FileChannel,DatagramChannel(通过UDP读写网络数据),SocketChannel(TCP),ServerSocketChannel(监听新进来的 TCP 连接) - Buffer:向通道读取或发送的所有数据都必须先放到缓冲区。
缓冲区包括:ByteBuffer,CharBuffer,......
类加载过程
当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。
- 加载:通过类的完全限定名称获取定义该类的二进制字节流;将该字节流表示的静态存储结构转换为方法区的运行时存储结构;在内存中生成一个代表该类的Class对象,作为方法区中该类各种数据的访问入口。
- 验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求。并且不会危害虚拟机自身的安全。
- 准备:为类变量(static)分配内存并设置初始值,使用的是方法区的内存。
- 解析:将常量池的符号引用替换为直接引用的过程。(即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。直接引用。可以理解为一个内存地址,或者一个偏移量。)其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
- 初始化:对类变量初始化,执行类构造器。如果初始化一个类的时候,父类尚未初始化,则优先初始化父类。
类加载和双亲委派模型
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
双亲委派模型可以防止黑客自定义同名类,并加入“病毒代码”。
Reflection(反射)
反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,并能直接操作任意对象的内部属性及方法。
RTTI(Run-Time Type Identification)
Java反射机制主要提供了以下功能:
- 在运行时构造任意一个类的对象
- 在运行时获取任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法(属性)
- 生成动态代理