java基础
基本数据类型:
https://www.cnblogs.com/ilren/p/9451616.html
数据类型 | 字节数 | 二进制位数 | 范围 | 规律 |
---|---|---|---|---|
byte | 1 | 8 | -128~127 | -27~27-1 |
short | 2 | 16 | -32768~32767 | -215~215-1 |
int | 4 | 32 | -2147483648~2147483647 | -231~231-1 |
long | 8 | 64 | -9223372036854775808 ~ 9223372036854775807 | -263~263-1 |
float | 4 | 32 | 1.4E-45~3.4028235E38 | |
double | 8 | 64 | 4.9E-324~1.7976931348623157E308 | |
char | 2 | 16 | 0~65535 | 0~216-1 |
boolean | 1 | 8 | true或false | true或false |
JVM
Java虚拟机主要分为五大模块:类装载器子系统、运行时数据区、执行引擎、本地方法接口和垃圾收集模块。
类装载器子系统(类加载器):
类加载过程:
1.加载:通过类的全路径将这个类从外部加载到jvm中,同时在方法区生成该类的描述信息并在内存生成该类的Claas类型。作为方法区这个类的数据访问入口。变为字节流。
2.验证:字节码格式验证,如对jvm是否有害,释放符合当前虚拟机的要求 ,是否符合规范,有无语法错误。
3.准备:为类的静态变量分配内存并根据这个静态变量所属的数据类型进行初始化。
4.解析:将符号引用替换成直接引用
5.初始化:当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化。 初始化就是把变量赋为默认值,把控件设为默认状态,把没准备的准备好。
运行时数据区
1程序计数器
是一块较小内存空间
可看做当前线程执行的字节码的行号指示器,多线程执行时线程轮流切换时恢复到正确执行位置
线程私有
线程执行Java方法,记录虚拟机字节码指令地址,执行Native方法,计数器为空
唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError区域
2 虚拟机栈
线程私有,生命周期与线程相同
虚拟机栈描述Java方法执行的内存模型,每个方法执行都会创建一个栈帧,方法的调用和执行完成与该方法栈帧的入栈和出栈对应
栈帧是方法运行时的基础数据结构,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
此区域包含两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError;如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError
3 本地方法栈
与虚拟机栈所发挥作用相似,区别在于一个为虚拟机执行Java方法服务,一个为虚拟机使用到的Native方法服务
虚拟机规范中未规定具体实现,虚拟机可以自由实现(最流行的Sun HotSpot虚拟机将本地方法栈和虚拟机栈合二为一)
与虚拟机栈一样,抛出StackOverflowError和OutOfMemoryError异常
4 堆
Java虚拟机管理的内存中最大一块
所有线程共享
虚拟机启动时创建。唯一目的就是存放对象的实例,几乎所有的对象实例都在这里分配内存
Java堆时垃圾收集器的主要区域
java堆可以处于物理不连续的内存空间中,逻辑上连续即可。实现时,既可以固定大小,也可以时可扩展的。不过当前主流虚拟机都是可扩展的(-Xmx -Xms)
如果在堆中没有完成实例分配,并且堆无法再扩展,将会抛出OutOfMemoryError
5 方法区
所有线程共享
用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
Java虚拟机规范将其描述为堆的一个逻辑部分,但是它又一个别名叫做Non-Heap
习惯HotSpot开发部署人员更愿意把方法区称为永久代(Permanent Generation),本质上并不等价,只是HotSpot团队选择把GC分代收集扩展至方法区,以使垃圾收集像管理堆一样管理这部分内存,省去编写方法区内存管理工作,其他虚拟机(J9)不存在永久代概念,实现方法区不受虚拟机规范约束
使用永久代因为通过-XX:MaxPermSize(jdk8已经取消)限制的上限,更容易内存溢出。极少数方法(如String.intern())会因这个导致在不同虚拟机下有不同表现。JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出
此区域内存回收主要针对常量池的回收和对类型的卸载。但是回收成绩总是很差,但是确实又是有必要的,否则会有内存泄漏隐患
方法区无法满足内存分布需求时,抛出OutOfMemoryError
5.1 运行时常量池
方法区的一部分
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放
Java虚拟机对于Class文件每一部分的格式都有严格规定,但是运行时常量池没有特殊要求。不过一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存在其中
运行时常量池时方法区的一部分,受方法区内存的限制,无法申请到内存是抛出OutOfMemoryError
6 直接内存
不属于虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁的使用,也可能导致OutOfMemoryError异常
JDK1.4中加入了NIO,通过Native函数分配堆外内存,然后通过一个存储在Java堆中的DirectbyteBuffer对象作为对这块内存的引用。
这块内存收到本机总内存的限制,设置虚拟机内存时,直接内存也不可以忽略
执行引擎
https://www.cnblogs.com/blogtech/p/10073560.html
本地方法接口
垃圾收集模块
https://blog.csdn.net/viola8104/article/details/95201321
1标记-清除算法
2复制算法
3标记-整理算法
4分代收集算法
NIO
IO和NIO的区别
原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的。
IO是面向流的、阻塞的
java1.4以前的io模型,一连接对一个线程。
原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区
Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。
NIO是面向块的、非阻塞的
NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
NIO的核心实现
在标准IO API中,你可以操作字节流和字符流,但在新IO中,你可以操作通道和缓冲,数据总是从通道被读取到缓冲中或者从缓冲写入到通道中。
NIO核心API Channel, Buffer, Selector
通道Channel
NIO的通道类似于流,但有些区别如下:
\1. 通道可以同时进行读写,而流只能读或者只能写
\2. 通道可以实现异步读写数据
\3. 通道可以从缓冲读数据,也可以写数据到缓冲:
缓存Buffer
缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常遵循以下四个步骤:
\1. 写数据到缓冲区;
\2. 调用buffer.flip()方法;
\3. 从缓冲区中读取数据;
\4. 调用buffer.clear()或buffer.compat()方法;
当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
Buffer在与Channel交互时,需要一些标志:
buffer的大小/容量 - Capacity
作为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。
当前读/写的位置 - Position
当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。
信息末尾的位置 - limit
在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。
Selector
一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。
多个Channel以事件的方式可以注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。
单例模式
所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。
特点
- 类构造器私有
- 持有自己类型的属性
- 对外提供获取实例的静态方法
懒汉模式
线程不安全,延迟初始化,严格意义上不是不是单例模式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉模式
线程安全,比较常用,但容易产生垃圾,因为一开始就初始化
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
双重锁模式
线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检查模式,进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton=new Singleton()
对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile
修饰signleton
实例变量有效,解决该问题。
静态内部类单例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
反射
java提供的动态执行机制,可以动态的加载类,动态创建对象,动态执行方法。
step1:javac(java编译器)将java文件编译生成*.class文件
step2:jvm在运行过程中,根据class.forName("")中的来找到硬盘中的***.class文件(这个过程叫做类加载——classLoad)。加载并放到方法区中,这就是
为何我们在创建类的时候,类名要和文件名相同,不然jvm无法找到*.class文件。
step3:jvm根据***.class文件创建一个以cls命名的Class对象。这个对象可以通向方法区,我们可以操作cls来获得类的所有信息。从而动态调用方法,创建对象,访问属性(甚至通过setAccessible来打开属性访问权限)。
java.lang.reflect能够获取java中class文件的字节码,然后将文件中的方法,变量和构造方法映射出来。
进程间的通信方式
1信道
2信号量
3消息队列
4共享内存
5套接字
进程与线程的区别
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。