Java
2024-09-26 20:01:02 14 举报
AI智能生成
Java是一种广泛使用的计算机编程语言,由James Gosling在1995年创建。Java可以生成可在不同操作系统和设备上运行的软件,因为它的设计目标是"编写一次,随处运行"。Java源代码被编译成一种名为字节码的中间代码,然后在Java虚拟机(JVM)上运行。这使得Java程序具有高度的可移植性和平台独立性。Java是一种面向对象的编程语言,支持继承、封装、多态和抽象等特性,这使得开发复杂系统更加简单和易于维护。Java还提供了丰富的标准库,包括用于网络、并发、安全和图形用户界面的工具。此外,Java还支持多种编程范式,如面向过程编程和函数式编程。Java文件通常以.java为扩展名,源文件需要经过编译生成字节码文件(.class),然后由JVM执行。
作者其他创作
大纲/内容
jvm
栈stack
运行时单位,代表处理逻辑
栈帧
对象的引用(4字节的Heap内存地址)
对象的方法
局部变量表、操作数栈、动态链接、方法返回地址
对象的方法
局部变量表、操作数栈、动态链接、方法返回地址
每个线程包含一个栈区,栈中的数据是私有的
只保存基础数据类型的对象和自定义对象的引用
由编译器自动分配释放
本地方法栈
堆heap
存储时单位,代表了数据
对象的实例(new创建的对象、数组),只存储对象本身。
保存对象实例的属性值、属性的类型和对象本身的类型标记。
每个对象包含一个与之对应的class信息
保存对象实例的属性值、属性的类型和对象本身的类型标记。
每个对象包含一个与之对应的class信息
jvm只有一个堆区(Heap)被所有现场共享
一般由程序分配释放,或在程序结束时由OS回收
堆中的共享常量和缓存可以被所有栈访问,节省了空间
字符串常量池
String
静态区static area
方法区
方法区
被所有的线程共享。包含所有的class和static变量
在整个程序中永远唯一的元素
存放类信息、类的静态变量、常量、运行时常量池。大小是可以动态扩展的
类名、访问修饰符、常量池、字段描述、方法描
类名、访问修饰符、常量池、字段描述、方法描
程序计数器
线程内的
用来记录程序的当前位置
jvm
运行时数据区
方法区:Method Area
保存静态变量(static)、常量(final)、类信息(Class模板)(构造方法、接口定义)、运行时常量池
堆
实例变量
Java栈 Stack
程序正在执行的方法,一定在栈的顶部
栈帧 stack frame
一个方法对应一个栈帧
先进后出
队列是先进先出
栈内存,主管程序的运行,生命周期和线程同步。线程结束,栈内存释放,对于栈来说,不存在垃圾回收
8大基本类型 + 对象引用 + 实例的方法
方法索引、输入输出参数、本地变量、ClassFile引用、父帧、子帧
本地方法栈 Native Method Stack
程序计数器(PC寄存器)
线程私有,每个线程都有一个程序计数器。指向方法区中的方法字节码
记住下一条jvm指令的执行地址的作用。多线程切换时可以继续不乱的往下执行下一个指令
类加载器
加载class文件
类是模板,同一个;实例是具体的,引用地址不一样
分类
启动类(根)加载器
负责加载JRE的核心类库,如lib目标下的rt.jar,charsets.jar等
扩展类加载器
主要加载JAVA_HOME/lib/ext目录中的类库
应用程序加载类
负责加载用户路径ClassPath路径下的类包
用户自定义类加载器
负责加载用户自定义路径下的类包
类加载机制
全盘负责委托机制
当一个ClassLoader加载一个类的时候,除非显示的使用另一个ClassLoader,否则该类所依赖和引用的类也由这个ClassLoader载入
双亲委派机制 安全
加载类优先在根加载器(Bootstrap classLoader)加载,即rt.jar,尽量不要动
主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader
其次在扩展加载器(ExtClassLoaderr)加载,即ext下的jar包,可修改
主要负责加载jre/lib/ext目录下的一些扩展的jar
最后在应用程序加载器(AppClassLoader)加载,即当前应用程序
主要负责加载应用程序的主函数类
子主题
1.类加载器收到类加载的请求
2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器 boot。依次检测类是否加载过,已经加载过,不再加载;
3.如果都没有加载过,启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则抛出异常,依次通知子类加载器加载
。
4.最后引用加载器加载不上 就会报错 Class NOT Found
2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器 boot。依次检测类是否加载过,已经加载过,不再加载;
3.如果都没有加载过,启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则抛出异常,依次通知子类加载器加载
。
4.最后引用加载器加载不上 就会报错 Class NOT Found
优势
沙箱安全机制:主要限制系统资源访问。比如自己写的String.class类不会被加载,这样可以防止核心库被随意篡改
避免类的重复加载:当父ClassLoader已经加载了该类的时候,就不需要子ClassLoader再加载一次
沙箱安全机制
字节码校验器
确保java类文件遵循Java语言规范,实现内存保护。但并不是对所有类文件进行字节码校验,比如核心类
类装载器
阻止恶意代码干涉善意代码
守护了被信任的类库边界
将代码归入保护域,确定了代码可以进行哪些操作
类加载的生命周期
加载
将.class文件从磁盘读到内存
连接
验证
验证字节码文件的正确性
准备
给类的静态变量分配内存,并赋予虚拟机默认的初始值
解析
类装载器装入类所引用的其他所有类
初始化
为静态变量赋予程序编写者为变量分配的真正初始值,并执行静态代码块
使用
卸载
native
带native关键字的方法,即为底层C的库。会进入本地方法栈,调用本地方法(java native interface)
JNI的作用:扩展java的应用,融合不通的编程语言,为java使用
在内存区域中专门开辟了一块标记区域:native method stack,登记native方法
JNI的作用:扩展java的应用,融合不通的编程语言,为java使用
在内存区域中专门开辟了一块标记区域:native method stack,登记native方法
一、内存Memory过高
堆空间 -Xmx
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Java heap space
Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整
代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)
永久代 -XX:MaxPermSize
java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: PermGen space
程序启动类需要加载大量的第三方jar包
减少不必要的对象创建,同时避免内存泄漏
tomcat下部署了太多的应用
大量动态生成的反射类
java内存三代
新生代New
新建的对象都存放这里
老生代Old
存放从新生代New中迁移过来的生命周期较久的对象。新生代New和老生代Old共同组成了堆内存
永久代Perm
保存在直接内存里,不属于堆内存
是非堆内存的组成部分。主要存放加载的Class类级对象如class本身,method,field等等
这个区域常驻内存的,用来存放JDK自身携带的Class对象、Interface元数据、存储的事Java运行时的一些环境或类信息
这个区域不存在垃圾回收,关闭JVM虚拟机就会释放这个区域的内存
jdk1.6前:永久代:常量池在方法区
jdk1.7:永久代,提出“去永久代”,常量池在堆中
jdk1.8之后,无永久代,常量池在元空间
jdk1.7:永久代,提出“去永久代”,常量池在堆中
jdk1.8之后,无永久代,常量池在元空间
避免内存泄露
定义:如果GC无法回收内存中不再使用的对象,则定义为内存有泄露
打开一个新的网络连接、数据库连接以及IO流。如果在业务处理中异常,则有可能导致程序不能执行关闭资源类的代码
关闭在finally中进行
关闭在finally中进行
未正确实现equals()和hashCode()
Map<Person, Integer> map = new HashMap<>();
for(int i=0; i<100; i++) {
map.put(new Person("jon"), 1);
}
Person类没有实现equals方法,因此使用Object的equals方法,直接比较实体对象的地址,new Person的地址肯定不同,
所以map.size() == 100
for(int i=0; i<100; i++) {
map.put(new Person("jon"), 1);
}
Person类没有实现equals方法,因此使用Object的equals方法,直接比较实体对象的地址,new Person的地址肯定不同,
所以map.size() == 100
使用重写finalize()方法的对象时,千万不要瞬间产生大量的对象
配置Tomcat,打印GC信息
在/bin/catalina.sh文件最上面加
JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -Xloggc:/root/logs/gc.log"
JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -Xloggc:/root/logs/gc.log"
处理OOM问题
尝试扩大堆内存,看结果。分析内存,看一下哪个地方出现了问题
程序中输出堆信息:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
JProfile
内存分析工具
-Xms 设置初始化内存分配大小
-Xmx 设置最大分配内存
-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError oom DUMP
-Xmx 设置最大分配内存
-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError oom DUMP
垃圾回收 GC
作用区域:堆(包含方法区),大部分回收发生在新生代
轻GC(minor gc)、重GC(full gc)
引用计数法(基本不用)
对象被引用了计数器+1,引用结束-1,计数器为0时清除
复制算法
最佳使用场景:对象存活度较低的时候,即新生区
1、每次GC都会将Eden活的对象移到幸存区中:一旦Eden区被GC后,就会是空的
当一个对象经历了15次(默认)GC后,都没有死,就会进入养老区
好处:幸存区有两个是为了解决对象放不下内存碎片化的问题
坏处:浪费了部分内存空间。。多了一半空间是空to,保证 to区是空的;
坏处:浪费了部分内存空间。。多了一半空间是空to,保证 to区是空的;
标记清除算法
子主题
扫描:扫描这些对象,对或者的对象进行标记;
清除:对没有标记的对象,进行清除
清除:对没有标记的对象,进行清除
优点:不需要额外的空间
缺点:两次扫描严重浪费时间,会产生内存碎片
缺点:两次扫描严重浪费时间,会产生内存碎片
标记压缩
在标记清除算法的基础上
压缩:在此扫描,向一端移动存活的对象,防止内存碎片产生
压缩:在此扫描,向一端移动存活的对象,防止内存碎片产生
缺点:多了移动成本
节省时间:可在多次标记清除之后,进行一次压缩
GC分代收集算法
年轻代:对象存活率低,使用复制算法
老年代:内存区域大,对象存活率高,使用标记清除+标记压缩混合 实现
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
JMM: Java Memory Model
1、什么是JMM,JAVA内存模型
2、干啥的:官方、其他人的博客,对应的视频
作用:
1、缓存一致性协议,用于定义数据读写的规则
2、JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)
3、解决共享对象可见性这个问题:volilate
作用:
1、缓存一致性协议,用于定义数据读写的规则
2、JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)
3、解决共享对象可见性这个问题:volilate
3、它该如何学习 8个指令
volilate:保证可见性和有序性
volilate:保证可见性和有序性
子主题
二、cpu占用过高
查看连接数
查看连接等待、建立连接数、等待数
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
iostat:查看io使用情况
cpu高的原因
死循环
调用方法的复杂度太高
内存相关问题
1、JVM的内存结构和分区,每个区分别放什么
2、堆里面的分区有哪些
3、GC的算法有哪些
标记清除法、标记整理(压缩)、复制算法、分带收集算法
轻GC和重GC分别在什么时候发生
框架
aop:面向切面编程
作用:
1、在调用某些程序之前先调用其他的程序,例如servlet中的Filter
2、监控函数调用、事务控制、权限管理
3、捕获异常的发生
1、在调用某些程序之前先调用其他的程序,例如servlet中的Filter
2、监控函数调用、事务控制、权限管理
3、捕获异常的发生
AOP的实现:
ioc:控制反转
容器控制程序对讲之间的关系。IOC容器管理bean,包含创建和销毁
通过BeanFactory,另一个是ApplicationContext。。其中ApplicationContext extends BeanFactory
所有的类都会在spring容器中登记,通过xml的方式/@bean注解里面起一个ID属性
一个是@resource,一个是@autowire取出这个对象。@resource注解取出对象是按照对象的名字来取的,而@autowire是按类型来取对象的.IOC的底层使用的是有一个map来做这个IOC的容器
典型的工厂模式
DI:依赖注入
即组件之间的依赖关系由容器在运行期决定。即由容器动态地将某种依赖关系注入到组件之中
1、通过构造方法注入(Construct注入)
2、通过setter方法注入
3、基于注解注入 @Autowired
@Component:可以用于注册所有bean
@Repository:主要用于注册dao层的bean
@Controller:主要用于注册控制层的bean
@Service:主要用于注册服务层的bean
2、通过setter方法注入
3、基于注解注入 @Autowired
@Component:可以用于注册所有bean
@Repository:主要用于注册dao层的bean
@Controller:主要用于注册控制层的bean
@Service:主要用于注册服务层的bean
依赖注入(DI)实现了控制反转(IoC)的思想
线程
线程池的优势
降低资源消耗
通过重复利用现有的现场来执行任务,避免多次创建和销毁线程
提高响应速度
因为省去了创建线程这个步骤,所以在拿到任务时,可以立刻开始执行
提供附加功能
线程池的可拓展性使得我们可以自己加入新的功能,比如说定时、延时来执行某些线程
提高线程的可管理性
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。
使用线程池可以进行统一的分配,监控和调优
使用线程池可以进行统一的分配,监控和调优
同步和异步的区别
同步是阻塞模式。指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,
那么这个进程将会一直等待下去,知道收到返回信息才继续执行下去
那么这个进程将会一直等待下去,知道收到返回信息才继续执行下去
异步是非阻塞模式,进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。
当有消息返回时,系统会通知进程进行处理,这样可以提高执行的效率
当有消息返回时,系统会通知进程进行处理,这样可以提高执行的效率
锁
概念
乐观锁/悲观锁
乐观锁
总是认为不存在并发问题,每次去取数据的时候,总认为不会有其他线程对数据进行修改,因此不会上锁。
更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用“数据版本机制”或“CAS操作”来实现
更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用“数据版本机制”或“CAS操作”来实现
适合读非常多的场景,不加锁会带来大量的性能提升
数据版本机制
在数据表中加一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值+1。
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读到的version值为
当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读到的version值为
当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
CAS操作(Compare and Swap)
当多个线程尝试使用CAS同时更新同一个变量,只有其中一个线程能更新变量的值,其他线程都失败,
失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试
失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试
悲观锁
认为对于同一个数据的并发操作,一定会发生修改的。哪怕没有修改,也会认为修改。
因此对一份数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁并发操作一定会有问题
因此对一份数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁并发操作一定会有问题
适合写操作非常多的场景
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking),
如果加锁失败,说明该记录正在被修改,那么当前查询可能就要等待或者抛出异常。
如果加锁成功,那么就可以对记录做修改,事务完成后就会解锁了。
期间如果有其他对该记录做修改或加排他锁的操作,都会等待解锁或直接抛出异常
如果加锁失败,说明该记录正在被修改,那么当前查询可能就要等待或者抛出异常。
如果加锁成功,那么就可以对记录做修改,事务完成后就会解锁了。
期间如果有其他对该记录做修改或加排他锁的操作,都会等待解锁或直接抛出异常
独享锁/共享锁
独享锁
该锁一次只能被一个线程所持有
Java ReentrantLock而言就是独享锁
Java ReadWriteLock,读锁是共享锁,写锁是独享锁
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的
synchronized 是独享锁
共享锁
该锁可被多个线程所持有
互斥锁/读写锁
互斥锁:ReentrantLock
读写锁:ReadWriteLock
可以用于“读多写少”的场景,支持多个读操作并发执行,写操作智能有一个线程来操作
如果写锁已经被其他任务持有,那么任何读取者都不能访问,直至这个写锁被释放为止
可重入锁
指的是某一个线程中可以多次获得同一把锁,在线程中多次操作用所的方法
公平锁/非公平锁
分段锁
偏向锁/轻量级锁/重量级锁
自旋锁
公平锁/非公平锁
关键字
Synchronized
synchronized
特性
原子性
因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放,这中间的过程无法被中断(除了已经废弃的stop()方法),即保证了原子性;
volatile 不具备原子性
volatile 不具备原子性
可见性
多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的
一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,
并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性
并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性
volatile
修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性
有序性
synchronized保证了每个时刻都只有一个线程访问同步代码块,
也就确定了线程执行同步代码块是分先后顺序的,保证了有序性
也就确定了线程执行同步代码块是分先后顺序的,保证了有序性
可重入性
ReentrantLock也是可重入锁
当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,
但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁
通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁
但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁
通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁
Java中的关键字,是一种同步锁。保证方法或代码块在运行时,同一时刻只有一个线程可以进入到临界区(互斥性),
同时它还保证了共享变量的内存可见性。
同时它还保证了共享变量的内存可见性。
修饰一个代码块
被修饰的代码块称为同步语句块,其作用的范围是{}括起来的代码,
作用的对象是调用这个代码块的对象
3.锁住的当前类
作用的对象是调用这个代码块的对象
3.锁住的当前类
修饰成员函数
被修饰的方法称为同步方法,其作用的范围是整个方法。
作用的对象是调用这个方法的对象
2.锁住的是当前实例
作用的对象是调用这个方法的对象
2.锁住的是当前实例
修改一个静态的方法
作用范围是整个静态方法,
1.锁住的是特定的对象
1.锁住的是特定的对象
互斥的最基本条件是:共用同一把锁
静态方法的锁是所在类的Class对象,普通方法的锁是this对象
对同一个线程,synchronized锁可以支持重入
静态方法的锁是所在类的Class对象,普通方法的锁是this对象
对同一个线程,synchronized锁可以支持重入
类锁与实例锁不相互阻塞,但相同的类锁,相同的当前实例锁,相同的对象锁会相互阻塞
Synchronized与Lock的区别
synchronized是关键字,而lock是java类的接口
synchronized不能判断是否获取所得状态,lock可以判断是否获取到锁
synchronized(隐式锁)可以自动释放锁,1、执行完同步代码时释放锁,2、代码出现异常释放锁;
lock(显示锁)需要手动在finally中释放锁unlock,否则容易造成线程死锁
lock(显示锁)需要手动在finally中释放锁unlock,否则容易造成线程死锁
synchronized适合同步少量代码,lock锁适合同步大量代码
lock用来同步代码块
synchronized采用的CPU悲观锁机制,lock用的是乐观锁机制
缺点
只有一个条件与锁相关联,
Lock
Synchronized
ReentrantLock
ReentrantLock和synchronized该如何选择
Java线程池使用和常用参数
newCachedThreadPool
创建一个可缓存的线程池,适用于执行短期异步任务的场景
当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程
当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程
newFixedThreadPool
Executors.newFixedThreadPool(50)
创建一个固定大小的线程池,因为采用无界的阻塞队列,每当提交一个任务就创建新的线程池直到最大数量,这时实际线程数量永远不会变化
核⼼线程数量和总线程数量相等,都是传⼊的参数nThreads,所以只能创建核⼼线程,不能创建⾮核⼼线程
适用于负载较重的场景,对当前线程数量进行限制,保证线程数可控
newSingleThreadExecutor
创建一个单线程的线程池,只有一个工作线程执行任务。由于采用LinkBlockingQueue,能够保证任务依照队列的顺序来执行
newScheduledThreadPool
适用于执行延时或者周期性任务
常用参数
核心线程数、存活时间(线程空闲多久回收)、
corePoolSize :线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程
maximumPoolSize:线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务
unit:超时时间的单位
keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间
workQueue:工作队列,保存未执行的Runnable 任务
threadFactory:创建线程的工厂类
handler:饱和策略:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略
java实现多线程的几种方式
继承Thread类实现多线程
实现Runnable接口方式实现多线程
试下Callable实现多线程
使用ExecutorService、Callable、Future实现有返回结果的多线程
sleep和wait区别
sleep()
程序暂停指定的时间,让出cpu给其他线程,但线程不会释放对象锁
是线程类Thread的静态方法,让调用线程进入睡眠状态。让出执行机会给其他线程,
等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间
等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间
因为sleep()是static静态的方法,不能改变对象的机锁。
wait()
是Object()类的方法,会放弃对象锁
调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池
当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,
同时释放对象的机锁,使得其他线程能够访问,通过notify,notifyAll方法来唤醒等待的线程
同时释放对象的机锁,使得其他线程能够访问,通过notify,notifyAll方法来唤醒等待的线程
调用wait()方法之后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列中
ThreadLoacl
把用户名、用户ip存成了ThreadLocal
ThreadLocal对象是一个「弱引用」
ThreadLocal本身不存储值,它依赖于Thread类中的ThreadLocalMap。
Thread将自身作为Key,值作为Value,保存到map中
Thread将自身作为Key,值作为Value,保存到map中
多个线程访问同一个变量是,如果不做同步控制,就会出现数据不一致的问题
会造成内存泄漏
原因:由于ThreadLocal对象是弱引用,如果外部没有强引用指向它,它就会被GC回收,导致Entry的Key为null,如果这时value外部也没有强引用指向它,那么value就永远也访问不到了,按理也应该被GC回收,但是由于Entry对象还在强引用value,导致value无法被回收,这时「内存泄漏」就发生了,value成了一个永远也无法被访问,但是又无法被回收的对象
如何避免内存泄漏
将其声明为static final类型的,避免频繁创建ThreadLocal实例
避免存储大对象,如果非要存,尽量访问完成后及时remove()删除掉
为什么类加载是线程安全的
对象创建步骤:1、分配空间并赋默认值。2、对象初始化<init>函数,即构造器。3、地址分配即建立引用关系
JVM类加载器在类初始化时会给<init>()方法上锁保证线程安全,所以如果<init>()方法中有耗时操作,可能会产生难以发现的阻塞
其他线程阻塞结束后不会进入<init>()方法,保证只执行一次
集合
hashmap的底层数据结构
put流程
put流程
key是不允许重复,但是允许为空
1.7中 数组+单向链表
1.8中 数组+单向链表+红黑树
1.8中 数组+单向链表+红黑树
数组
初始化有固定的大小长度 16,有顺序的下标,扩展条件为0.75
最大容量为2的30次方
单向链表
每个节点包含一个data,和指向下一个节点地址的next
解决哈希冲突的问题
红黑树
单项链表向下排Node节点到8个时,出现红黑树
当红黑树的叶子结点数小于6个时,程序将红黑树转为单项链表
每个节点或者红色、或者黑色
根节点是黑色
每个为空的叶子结点是黑色
如果一个节点是红色,则它的子节点必须是黑色的
从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
put流程
1、HashMap使用Key hashCode()和哈希算法来找出存储key-value对的索引
2、如果索引处为空,则直接插入到对应的数组中
3、否则,判断是否是红黑树,若是,则红黑树插入
4、否则遍历链表,若长度不小于8,则将链表转为红黑树,转成功之后 再插入
2、如果索引处为空,则直接插入到对应的数组中
3、否则,判断是否是红黑树,若是,则红黑树插入
4、否则遍历链表,若长度不小于8,则将链表转为红黑树,转成功之后 再插入
List、Map、Set三个接口,存取元素时,各有什么特点
Set不允许有重复的元素
存元素:add方法,有一个boolean的返回值,
取元素:通过迭代器遍历所有元素,再逐一遍历各个元素
存元素:add方法,有一个boolean的返回值,
取元素:通过迭代器遍历所有元素,再逐一遍历各个元素
List表示有先后顺序的结合,可重复
存元素:add
取元素:get
存元素:add
取元素:get
Map,key不可重复,可为空
存元素:put
取元素:get
存元素:put
取元素:get
HashSet和ArrayList的区别
ArrayList实现了List接口,HashSet实现了Set接口。List和Set都继承Collection接口
AyyayList底层是动态数组,HashSet底层是哈希表
ArrayList存放的事对象的引用,HashSet存放之前监所对象的HashCode。所以当存入对象时要重写hashCode(),
如果只是比较对象,只需要重写equals()方法
如果只是比较对象,只需要重写equals()方法
ArrayList是有序可重复的,HashSet是无序不可重复的
HashMap和HashTable的区别
HashMap是线程不安全,Hashtable线程安全
原因:在hashmap的put方法调用addEntry()方法,假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失
解决方案:ConcurrentHashMap
解决方案:ConcurrentHashMap
Hashtable集成自Dictionary类,但其父类已经被废弃
HashMap的key和value为null值,有containsValue()和containsKey()方法。
Hashtable的key和value不能为null,会报空指针异常。保留了contains方法
Hashtable的key和value不能为null,会报空指针异常。保留了contains方法
扩容方式不同:
HashTable扩容为原容量的2倍加1;
HashMap扩容为原来的2倍,原来数组中的元素依次重新计算存放位置,并重新插入
HashTable扩容为原容量的2倍加1;
HashMap扩容为原来的2倍,原来数组中的元素依次重新计算存放位置,并重新插入
Arraylist和linkedlist的区别
ArrayList
线程不安全
底层是数组
增删慢、查询快
时间复杂度是 O(1)
LinkedList
线程安全
底层是链表,线性的数据存储方式
增删快,查询慢
时间复杂度 是 O(n)
网络
描述一下htpp的三次握手和四次挥手
建立连接三次握手
第一次:主机A发送位码为syn=1,随机产生seq number = 1234567的数据包到服务器。
主机B由syn=1知道,A要求建立联机
主机B由syn=1知道,A要求建立联机
第二次:主机B收到请求后要确认联机信息,想A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包
第三次:当A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1。
若正确,主机A会再发送ack number=(主机的seq+1),ack=1,主机B收到后确认seq值与ack=1连接建立成功
若正确,主机A会再发送ack number=(主机的seq+1),ack=1,主机B收到后确认seq值与ack=1连接建立成功
三次握手完成后,主机A与主机B开始发送数据
断开连接四次挥手
客户端和服务端都可主动发起挥手动作
第一步:客户端发送一个FIN标志位置为1的包,表示自己没有数据要发送了,但是可以接收数据
发送完毕之后,进入FIN_WAIT_1状态
发送完毕之后,进入FIN_WAIT_1状态
第二步:服务器端确认客户端
第三步:
第四步:
Get和POST的区别
Get
会产生一个TCP包
URL长度有限制,Get请求不能发送大量数据
POST
会产生两个TCP数据包
不能被缓存
java基础
java常用的包
Java.lang
基础类
Java.io
数据流、系统输入、输出等
Java.util
包含collection框架、各种实用工具类等(生成随机数)
Java.sql
处理数据库的api
String/StringBuilder/StringBuffer
String 值不可变字符序列;修改会生成新的String对象,太频繁会导致内存增长
StringBulider
效率高,线程快,单线程时推荐使用,线程不安全
StringBuffer
效率低多线程时,线程安全
每个StringBuffer对象都有一定的缓冲区容量,当字符串没有超过容量是,不会
分配新的容量,当字符串大小超过容量时,会自动增加容量
分配新的容量,当字符串大小超过容量时,会自动增加容量
重写和重载的区别
都是多态的一种展现形式
重载
编译时的多态性
重载发生在一个类型,同名的方法有不同的参数列表视为重载
对返回类型没有特殊的要求。不能根据返回类型进行区分
重写
运行时的多态性
发生在子类和父类之间
要求子类重写方法与父类被重写方法有相同的返回类型
不能比父类被重写的方法声明更多的异常。
锁
死锁
死锁的条件有哪些
四个必要条件
互斥条件
进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。
如果此时还有其他进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
如果此时还有其他进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
请求与保持条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有。
此时请求进程被阻塞,但对自己已获得的资源保持不放
此时请求进程被阻塞,但对自己已获得的资源保持不放
不可剥夺条件
进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,
即只能由获得该资源的进程自己来释放(主动释放)
即只能由获得该资源的进程自己来释放(主动释放)
循环等待条件
只在发生死锁时,必然存在一个进程--资源的环形链,即进程集合{P0,P1,P2},
P0等着P1占用的资源,P1等着P2占用的资源,P2等着P0占用的资源
P0等着P1占用的资源,P1等着P2占用的资源,P2等着P0占用的资源
是否遇到过死锁,如何解决
对接程序中启动了两个线程:一个线程A对接在押人员,另一个线程B对接监室号,
B线程对接会更新A中的监室号,A需要查询监室号
B线程对接会更新A中的监室号,A需要查询监室号
写一个死锁的程序
原则:相同的资源互相等待
public class Lock implements Runnable{
private boolean flag;
public Lock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag){
while(true){
deadLockab();
}
}else{
while(true){
deadLockba();
}
}
}
private static Object objectA = new Object();
private static Object objectB = new Object();
private void deadLockab() {
String name = Thread.currentThread().getName();
synchronized (objectA){
System.out.println(name+"...lock 1 on "+objectA);
synchronized (objectB){
System.out.println(name+"...lock 2 on "+objectB);
}
}
}
private void deadLockba() {
String name = Thread.currentThread().getName();
synchronized (objectB){
System.out.println(name+"...lock 3 on "+objectB);
synchronized (objectA){
System.out.println(name+"...lock 4 on "+objectA);
}
}
}
}
private boolean flag;
public Lock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag){
while(true){
deadLockab();
}
}else{
while(true){
deadLockba();
}
}
}
private static Object objectA = new Object();
private static Object objectB = new Object();
private void deadLockab() {
String name = Thread.currentThread().getName();
synchronized (objectA){
System.out.println(name+"...lock 1 on "+objectA);
synchronized (objectB){
System.out.println(name+"...lock 2 on "+objectB);
}
}
}
private void deadLockba() {
String name = Thread.currentThread().getName();
synchronized (objectB){
System.out.println(name+"...lock 3 on "+objectB);
synchronized (objectA){
System.out.println(name+"...lock 4 on "+objectA);
}
}
}
}
public class DeadLockDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(new Lock(true));
Thread t2 = new Thread(new Lock(false));
t1.start();
t2.start();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Lock(true));
Thread t2 = new Thread(new Lock(false));
t1.start();
t2.start();
}
}
bio,nio,aio区别
bio
Block,同步并阻塞的io。数据的读取必须阻塞在一个线程内等待其完成
ObjectInputStream,在java.io包下面的代码实现
适用于连接数目比较小且固定的架构,对服务器资源要求比较高
nio
以块的方式处理数据。
按块处理数据比按(流式的)字节处理数据要快的多
按块处理数据比按(流式的)字节处理数据要快的多
多路复用,同步非阻塞
线程不断的轮询IO操作,看状态是否发生了变化。从而进行下一步操作
适用于连接数据多且连接比较短的架构,比如聊天服务器
FileInputStream,FileChannel, 选择器
io
以流的方式处理数据,一次一个字节地处理数据。
一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。
一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。
aio
Async,异步非阻塞IO
不需要一个线程去轮询所有的io,而是等相应的io操作状态发生改变之后,
系统会通知对应的线程来处理
系统会通知对应的线程来处理
适用于练市数据多连接比较长的(重操作),比如相册服务器
AsynchronousFileChannel
nio和io的区别
异常与error
exception和error的区别
都继承了Throwable类,可以被抛出和捕获
Error
正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序处于非正常状态,不可恢复状态。
不便于也不需要捕获,比如OOM
不便于也不需要捕获,比如OOM
Exception
可检查和不检查异常,
可检查异常在源码里必须显示的进行捕获处理,这是编译期的一部分。
不检查异常即运行时异常,类型NullPointerException,数组越界等可以编码避免的逻辑错误,根据需要来判断是否需要捕获,无强制要求
可检查异常在源码里必须显示的进行捕获处理,这是编译期的一部分。
不检查异常即运行时异常,类型NullPointerException,数组越界等可以编码避免的逻辑错误,根据需要来判断是否需要捕获,无强制要求
运行时异常与受检异常有何异同
runtime exception
运行时异常
运行时异常
程序代码运行时发生的异常
checked exception
受检异常
受检异常
跟程序运行的上下文环境有关
FileInputStream fileIn = new FileInputStream("E:\\a.txt");
必须进行异常处理
必须进行异常处理
Java编译器要求方法必须声明抛出可能发生未被捕获的受检异常,否则会提示“java: 未报告的异常错误java.io.FileNotFoundException; 必须对其进行捕获或声明以便抛出”
不要求必须声明抛出运行时异常
垃圾回收:GC
标记-压缩清理的方法
GC复制算法
标记-清除算法
主动进行垃圾回收:System.gc()方法的调用
可达性
引用计数法
Object的常用方法
toString
equals
hashCode
返回其所在对象的物理地址
OOM异常的解决办法
堆内存溢出
检查系统参数 -Xmx -Xms
设置VM参数,方便导出dump文件:-XX:HeapDumpPath=/Users/wangchengming/Desktop/
通过jmap抓dump
虚拟机栈和本地方法栈溢出
检查栈容量 -Xss
StackOverflowError:线程请求的栈深度大于虚拟机所允许的最大深度
public class TestStack {
static int stackLength = 1;
public static void main(String[] args) {
stackLeak();
}
public static void stackLeak() { //递归
stackLength++;
stackLeak();
}
}
static int stackLength = 1;
public static void main(String[] args) {
stackLeak();
}
public static void stackLeak() { //递归
stackLength++;
stackLeak();
}
}
就需要仔细检查代码有没有深度递归的情况
是不是有死循环
OOM:系统在扩展栈时,无法申请到足够的内存空间
运行时产生大量的类,会填满方法区,造成溢出
java.lang.OutOfMemoryError: unable to create new native thread
单线程下,栈帧太大,或者虚拟机栈容量太小,当内存无法分配的时候
栈帧
int和Integer的区别,拆装箱
1、Integer是int的包装类,int是基本数据类型
2、Integer变量必须实例化后才能使用;int变量不需要
3、Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值
4、Integer默认是null,int默认值是0
接口和抽象类的区别
1、抽象类可以有构造方法,但接口不能有构造方法
2、抽象类中可以有普通成员变量。但接口中没有普通成员变量:默认是public、static、final类型的,必须被显式的初始化
3、抽象类中可以包含非抽象的普通方法和抽象方法;接口中的所有方法都必须是抽象的,不能有非抽象的普通方法
4、抽象类的抽象方法访问类型可以是public、protected和default,但接口中的抽象方法只能是public,并且默认即为public abstract
5、抽象类中可以包含静态方法,接口中不能包含静态方法(在jdk1.8中,接口里可以有静态方法,接口里的有静态方法 必须要有body)
6、一个类可以实现多个接口,但只能继承一个抽象类
应用上的区别:接口更多用于定义模块之间的通信七月。而抽象类可以是实现代码的重用
public、protected
default :本包指的是同一个目录下
Subtopic
java中变量类型
成员变量
静态变量
局部变量
类和对象的区别
对象,也叫实例
是类的一个实例,有属性和方法
占用存储空间
具体的
类
是创建对象的一个模板,描述一类对象的行为和状态
不占用内存
抽象的
举例
男孩、女孩为类,具体某个人指对象;汽车为类,具体一辆车是对象。
this/super
this
1、当一个对象创建后,JVM就会给这个对象分配一个引用自身的指针,这个指针的名字就是this。
2、this只和特定的对象关联,而不是和类关联,同一个类的不同对象有不同的this。
2、this只和特定的对象关联,而不是和类关联,同一个类的不同对象有不同的this。
3、this只能在类中的非静态方法中使用,静态方法和静态的代码中绝对不能出现this。 -- 否则会出现编译时报错
原因:
a、this代表的是调用这个函数的对象的引用。而静态方法是属于类的,不属于对象,静态方法成功加载后,对象还不一定存在
b、静态优于对象存在。方法被静态修饰之后方法先存在。
原因:
a、this代表的是调用这个函数的对象的引用。而静态方法是属于类的,不属于对象,静态方法成功加载后,对象还不一定存在
b、静态优于对象存在。方法被静态修饰之后方法先存在。
4、使用this来区分当前对象
4.1、 构造方法中-----指该构造器所创建的新对象
4.2、方法中-----指调用该方法的对象
4.2、方法中-----指调用该方法的对象
代码示例
public class Student {
Student(String name){
this.nameN = name;
System.out.println("==Student===="+this);
}
private String nameN;
public void setName(String name) {
System.out.println("setName=this="+this);
this.nameN = name;
this.setName("王二麻子");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("张三");
System.out.println("===main======"+student);
student.setName("张三");
Student student1 = new Student("李四");
System.out.println("===main======"+student1);
student1.setName("李四");
}
}
Student(String name){
this.nameN = name;
System.out.println("==Student===="+this);
}
private String nameN;
public void setName(String name) {
System.out.println("setName=this="+this);
this.nameN = name;
this.setName("王二麻子");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("张三");
System.out.println("===main======"+student);
student.setName("张三");
Student student1 = new Student("李四");
System.out.println("===main======"+student1);
student1.setName("李四");
}
}
4.3、在类本身的方法或构造器中引用该类的实例变量(全局变量)和方法
实例变量与局部变量
1、当实例变量和局部变量重名,Java平台会按照先局部、后实例变量的顺序寻找。如果没有找到,会有编译错误
2、如果使用this.a则不会在局部变量中寻找变量a,而是直接去实例变量中去找,如果找不到,会有编译错误
3、在一个方法内,如果没有出现局部变量和实例变量重名的情况下,是否使用this关键字是没有区别的
4、在同一个类中,Java普通方法的调用可以省略this,直接使用方法名+参数。
1、当实例变量和局部变量重名,Java平台会按照先局部、后实例变量的顺序寻找。如果没有找到,会有编译错误
2、如果使用this.a则不会在局部变量中寻找变量a,而是直接去实例变量中去找,如果找不到,会有编译错误
3、在一个方法内,如果没有出现局部变量和实例变量重名的情况下,是否使用this关键字是没有区别的
4、在同一个类中,Java普通方法的调用可以省略this,直接使用方法名+参数。
public class Student {
Student(String name){
xm = name;
System.out.println("==Student===="+this);
}
private String xm; //此处为实例变量名
public void setName(String name) {//此处为局部变量名
System.out.println("setName=this="+this);
xm = name;
}
public String getName() {
return xm;
}
}
Student(String name){
xm = name;
System.out.println("==Student===="+this);
}
private String xm; //此处为实例变量名
public void setName(String name) {//此处为局部变量名
System.out.println("setName=this="+this);
xm = name;
}
public String getName() {
return xm;
}
}
5、在构造器中使用this来调用对象本身的其他构造器,但在一个构造器中最多调用一个其他的构造器。
并且对其他构造器的调用动作必须放在构造器的起始处(首行),否则编译的时候将会出现错误。
并且对其他构造器的调用动作必须放在构造器的起始处(首行),否则编译的时候将会出现错误。
6、this关键字还有一个重大的作用就是返回类的引用。如在代码中,可以使用return this来返回某个类的引用。
此时this关键字就代表类的名称
此时this关键字就代表类的名称
1、当要把自己当做参数传递给别的对象时
2、注意匿名类和内部类中的this,this指的是匿名类或内部类本身
3、让类的一个方法,访问该类的另一个方法或者属性
super
定义:代表父类的引用,用于访问父类的属性、方法、构造
访问父类的属性:super.属性名= 值;
访问父类的方法:super.方法名(实参列表)
访问父类的构造:super(实参列表);注意:必须在子类的构造器的第一句
访问父类的方法:super.方法名(实参列表)
访问父类的构造:super(实参列表);注意:必须在子类的构造器的第一句
this和super的区别
通过super访问成员时,先从直接父类找,如果还找不到,继续网上追溯叫间接父类,直到找到为止;
通过this访问成员时,先从本类中查找,如果找不到再从直接父类找,一直往上
通过this访问成员时,先从本类中查找,如果找不到再从直接父类找,一直往上
this是一个指向本对象的指针,super是一个Java关键字
算法
复杂度
时间复杂度
大O表示法:算法的渐进时间复杂度
T(n) = O(f(n))
T(n) = O(f(n))
举例:for循环i到n
for(int i=1;i<=n;i++){
x++;
}
for(int i=1;i<=n;i++){
x++;
}
简化为O(n)次
空间复杂度
内存空间增长的趋势
0 条评论
下一页