java面试(自用)

java只有值传递,基本类型中存的是真实值,引用类型中存的地址值
值传递将实际参数复制一份传递到函数中,会创建副本。
引用传递将实际参数的地址直接传递到函数中。
public native int hashCode(); native 标识方法为非java代码编写
代理模式:使用代理对象代替对真实对象的访问,这就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式主要作用就是扩展目标对象的功能,代理模式分为静态代理和动态代理
静态代理:对目标对象的每个方法增强都是手动完成的,修改起来比较麻烦
静态代理在编译时就将接口、实现类、代理类这些变成了一个个实际的class文件
静态代理的实现步骤:1.定义一个接口及实现类,2.创建一个代理类同样实现这个接口,3.将目标对象注入到代理类中,在代理类中对应方法调用目标类中的对应方法,完成增强代码
每个目标类都单独创建一个代理类
动态代理:更加灵活,动态代理是在运行时动态生成类字节码,spring aop,RPC
分为jdk动态代理,CGLIB动态代理
步骤:1.定义一个接口及其实现类,2.定义代理类并实现invocationhandler接口,并重写invoke方法method.invoke(目标对象,args)。3.通过Proxy.newProxyInstance(目标的类加载器,目标的接口,代理类对象)生成代理对象。代理对象可以直接调用目标对象的方法。
CGLIB:使用继承的方式实现代理
两个动态代理的对比:JDK动态代理只能代理实现了接口的类或者直接代理接口。而CGLIB可以代理未实现任何接口的类,CGLIB是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此代理类中不能声明final的类和方法。大部分情况下JDK效率高,
静态和动态对比:灵活性,动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且不需要针对每个目标类都创建一个代理类,静态代理一旦新增方法,目标对象和代理对象都需要修改。 静态代理在编译阶段就生成了class文件,动态代理在运行时动态生成类字节码
集合:
List:存储元素是有序的,可重复的,(ArrayList底层是数组,LinkedList底层是链表,)
Set:存储的是无序的、不可重复
Map:kv键值对,key是无序的不可重复,value是无序的、可重复的,kv之间一对多映射
List:ArrayList 数组,Vector 数组,LinkedList 双向链表(jdk1.6之前为循环链表,jdk1.7取消循环)链头/尾进行插入删除更加快捷(大部分时间插入删除在链头/尾)。
Set:HashSet:无序唯一,基于HashMap实现,用key保存数据,LinkedListSet:是HashSet的子类,TreeSet:有序、唯一:
Map:HashMap:jdk1.8之前为数组加链表,之后是当链表长度大于8,会将其转换到红黑树,但是在转换红黑树之前会判断数组长度是否大于64,小于则扩容,转换红黑树加快搜索时间。因为红黑树需要进行左旋,右旋,变色操作来保持平衡,所以当数组长度小于64,使用数组加链表比使用红黑树查询速度要更快、效率要更高。当红黑树小于6时,转成链表。LinkedHashMap继承自HashMap,增加一条双向链表,使得上面的结构可以保持键值对的插入顺序,before、after保证插入顺序,next下一个。Hashtable:数组加链表,TreeMap:红黑树
如何选用集合:

为啥要用集合:数组声明之后长度不可变,使用集合可以提高数据存储的灵活性
ArrayList:底层使用Object[]存储,线程不安全,适用于频繁查找
vector:线程安全
ArrayList与LinkedList区别:都不保证线程安全,
ArrayList底层是数组,便于查找,不便于修改,LinkedList底层是链表便于修改,不便于查找。ArrayList插入删除受元素位置影响。LinkedList在头尾插入或者删除不受元素位置影响,在其他位置要查找元素。ArrayList结尾会预留一些空间,LinkedList会保存指针
RandomAccess接口标识这个类可以随机访问
ArrayList扩容:以无参方式创建ArrayList时,实际上初始化赋值是空数组,当真正添加元素时才分配容量,10,每次扩容1.5倍
Comparable接口是java.lang包下有一个compareTo(Object obj)
Comparator接口是java.util下的,compare(obj1, obj2)
无序性是指存储的数据在底层数组中并非按照数组的索引添加,而是根据数据的哈希值决定。不可重复性表示添加元素时,按照equals判断为false
Hashset由hashMap实现,线程不安全,可以存贮null,无序不可重复
LinkedHashSet可以按照添加顺序遍历。
TreeSet底层使用红黑树,能够按照添加元素的顺序进行遍历
HashMap 非线程安全,HashTable线程安全,HashMap效率高,HashMap可以存储null的key和value,HashTabl不允许null键和null值,Hashtable默认大小11,每次扩容2n+1,HashMap默认16,每次扩容2倍,创建指定大小的hashtable时,直接使用给定的大小,而hashMap则会将其扩充为2的幂次方大小。HashTable只有数组和链表
HashSet与HashMap:hashset实现类set,仅存储key,
hashmap和treemap都继承自AbstractMap接口,TreeMap还实现了NavigableMap接口和SortedMap 接口。NavigableMap接口可以集合内元素搜索,SortedMap 可以根据key排序
HashSet怎么检查重复:先hashcode后equals
HashMap的长度为啥是2次方:2的幂次方可以将取余操作转化(hash%length==hash&(length-1))提高运算效率
HashMap的头插法在多线程操作下可能出现死循环,jdk1.8的尾插法不会
两个线程同时触发扩容,在移动节点时会导致一个链表中的2个节点相互引用,从而生成环链表
HashMap的遍历方式:迭代器,for,entryset,keyset, entryset的效率高
ConcurrentHashMap和Hsahtable的区别:
ConcurrentHashMap,在jdk1.7之前采用分段数组和链表,jdk1.8之后数组+链表/红黑树
Hashtable使用数组+链表。jdk1.7之前,concurrenthashMap分段锁,对数组分段,提高性能,到1.8已经摒弃segment,直接用Node数组+链表/红黑树的数据结构来实现了,并发控制使用synchronized+cas操作只锁定当前链表或红黑二叉树的首节点。,Hashtable使用synchronized来保证线程安全,锁的是实例对象,效率非常低下,一个线程获得锁,其他线程将不能put和get

javaweb中pojo类使用包装类,因为可以为null
使用equals时避免空指针异常。
或者使用Objects.equals(), return (a == b) || (a != null && a.equals(b));
所有的整型包装类比较必须用equals
Integer i1=40;i1直接使用的是常量池中的对象,而Integer i1=new Integer(40);会直接创建新的对象。
BigDecimal ,浮点数之间的等值判断,基本类型不能用==,包装类型不能equals
使用BigDecimal来定义浮点数值,保留精度
a.compareTo(b)
setScale(3,) 保留几位小数
为防止精度丢失,使用BigDecimal(String)构造方法来创建对象。
BigInteget 大整数
所有pojo类属性必须使用包装数据类型。所有局部变量推荐使用基本数据类型,包装类型默认为null,没有初值,需显示初始化。
数组转List,Arrays.asList 转为list后底层仍未数组,返回的是Arrays的内部类,没有实现集合的修改等方法。不可修改list,修改数组,list中的值也修改
Arrays.asList是泛型方法,传入的对象必须是对象数组。当传入基本类型数组时,得到不是数组中的元素,而是数组对象本身,get(0),将打印地址,size=1
final:修饰类,方法,变量
static:1.修饰成员变量和成员方法,
2.静态代码块,定义在类内,方法外,执行顺序:静态代码块,非静态代码块,构造方法。该类不管创建多少对象,静态代码块只执行一次,当类第一次被调用时执行
3.静态内部类:static修饰类只能修饰静态内部类,非静态内部类编译完成后会隐含的保存一个引用,该引用指向创建他的外围类,但是静态类没有。静态类的创建不需要依赖外围类,他不能使用外围类的非static的成员变量和方法,
并发:
进程:是程序的一次执行过程,是系统进行资源分配和调度基本单位,是线程的容器
线程:比进程更小的执行单位,一个进程在其执行的过程中可以产生多个线程。多个线程共享堆和方法区,每个线程有自己的程序计数器、虚拟机栈和本地方法栈,
线程与进程的关系、区别和优缺点:线程是进程划分的更小的运行单位,之间最大的不同在于基本上各进程是独立的,而进程则不一定,因为同一进程中的线程可能会相互影响,线程的开销小,但不利于资源的管理和保护(线程通信或者上下文切换),而进程正相反。
进程是资源分配最小单位,线程是程序执行的最小单位,
进程有自己独立的地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段,堆栈段和数据段,而线程是共享进程中的数据的,cpu切换线程的花费小,创建线程的开销也小很多。
线程之间通信方便,统一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(ipc)的方式
线程也成为轻量级进程。
程序计数器为啥私有:,程序计数器的作用:字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制,程序计数器用于记录当前线程执行的位置,在线程切换回来以后可以知道程序进行到哪里了。私有主要是为了线程切换后能恢复到正确的执行位置,
虚拟机栈和本地方法栈为啥私有:为了保证线程中的局部变量不被其他线程访问到,虚拟机栈和本地方法栈是线程私有的
虚拟机栈:其中主要保存的是栈帧,每一次函数调用都会有一个对应的栈帧·被亚茹虚拟机栈,每一个函数调用结束后,被栈帧弹出,栈帧保存的是局部变量表,操作数栈,动态连接、方法出口信息,
本地方法栈:和虚拟机栈作用差不多,区别主要是虚拟机栈为java方法服务,本地方法栈为虚拟机用到的Native方法服务。
堆和方法区是所有线程共享的资源,堆是进程中最大的一块内存,主要用于存放新建的对象,方法区主要用于存放已被加载的类信息,常量、静态变量、即时编译器(JIT解释)编译的代码等数据。
并发:同一时间段,多个任务都在执行(单位时间内不一定同时执行)
并行:单位时间内,多个任务同时执行
为啥要使用多线程:提高程序的执行效率提高程序运行速度,
多线程的缺点:内存泄漏、死锁、线程不安全等等
上下文切换:一个cpu核心在任意时刻只能被一个线程实现,为了让这些线程都能得到有效执行,cpu采用时间片轮转,当一个线程的时间片用完,线程就会处于就绪状态。当前任务使用完cpu时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以在加载这个任务的状态,任务从保存到再加载的过程就是一次上下文切换。
上下文切换消耗着大量的cpu时间。
linux上下文切换和模式切换的时间消耗非常少
线程死锁:多个线程同时被阻塞,线程A持有资源1等待资源2,而线程B持有资源2等待资源1,
死锁产生的四个条件:互斥条件:该资源任意时刻只由一个线程占用
请求与保持条件:一个进程因请求资源而被阻塞时,对已获资源保持不放
不剥夺条件:线程已获得的资源在未使用之前不能被其他线程强行剥夺,只有在使用完毕后才释放资源,
循环等待条件:若干个进程之间形成一种头尾相接的循环资源等待关系, 存在循环等待链中
预防死锁:破坏请求保持条件:一次性申请所有资源,破坏不剥夺条件:占用部分资源的线程进一步申请资源时,如果申请不到,可以主动释放它占有资源。
破坏循环等待条件:按序申请资源,将资源统一编号,按照升序顺序申请
避免死锁:在资源分配时,借助于算法对资源分配进行计算评估,使其进入安装状态。
sleep和wait区别:sleep没有释放锁,wait释放锁 两者都可以暂停线程的执行,wait通常被用于线程间交互/通信,sleep通常用于暂停执行,wait方法后,线程不会自动唤醒,需要notify或者notifyall。sleep方法执行完成后,线程会自动苏醒,或者wait(time)超时候也会自动线程唤醒
start与run方法:start方法可启动线程并使进程进入就绪状态,直接执行run就不会以多线程的方式执行,
synchronized:解决的是多个线程之间的访问资源的同步性,可以保证被他修饰的方法和代码块在任意时候只有一个线程执行,
早期的synchronized属于重量级锁,效率低下。
早期monitor监视器锁,是依赖于操作系统的Mutex Lock来实现,java的线程映射到操作系统上的原生线程,挂起或者唤醒线程需要操作系统帮忙,而操作系统实现线程之间的切换需要从用户态转到内核态,这个转换时间成本高。java6之后对其进行了优化如自适应锁、锁消除、锁粗化、偏向锁、轻量锁等减少锁的开销,现在各种开源框架的底层都大量使用了synchronized关键字。
synchronized:三种方式(说说你是怎么使用)
1, 修饰实例方法:作用于当前对象实例加锁,进入同步代码块之前要获取当前对象实例的锁。2.修饰静态方法,也就是给当前class模板加锁,会作用于所有的对象实例,3.修饰代码块:指定加锁对象,对给定对象和类加锁,
synchronized不可用在构造方法上,构造方法本身是线程安全的
synchronized的底层原理:synchronized同步语句块的实现使用monitorenter和monitorexit指令,其中enter是开始的地方,exit是指同步代码块结束的地方。
当执行monitorenter指令时,线程试图获取锁也就是获取对象监视器monitor的持有权。执行monitor时,会尝试获取锁,若锁的计数器为0表示锁可以获取,获取后计数器加1,执行monitorexit时,将锁的计数器置为0,表示锁释放。
wait和notify依赖于monitor,所以这两个方法只能在同步代码块调用
synchronized修饰方法没有monitorenter和exit,取代的是ACC_synchronized标识。
jdk1.6做了synchronized的优化:JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
并发编程的三个重要特性:
原子性:一个操作或者多次操作,要么都执行,要不都不执行synchronized可以保证代码片段的原子性,
可见性:当一个变量对共享变量进行了修改,那么其他线程立即看到最新的值,volatile
有序性:代码在执行的过程中的先后顺序,Java在编译以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile关键字可以禁止指令进行重排序优化。
synchronized与volatile的区别:volatile是线程同步轻量级实现,性能好,但只能修饰变量,synchronized可修饰方法和代码块,volatile保证数据的可见性,不能保证数据的原子性,synchronized都能保证,volatile解决变量在多个线程之间的可见性,而synchronize解决多个线程之间访问资源的同步性,
ThreadLocal:为每个线程创建私有变量
GC判断对象是否存活:使用可达性分析和引用计数法
java堆中的对象都会有一个引用计数器 引用保存在栈,对象保存在堆
强引用:当引用计数器非0时,绝不会回收,oom
软引用:空间充足,不会gc,当空间不足时,会被gc,可以和引用队列联合使用,gc后将引用加入到队列中,尽可能保留新创建的
弱引用:空间充足与否,都会被gc,先引用置为null,再通知gc,可以和引用队列联合使用,gc后加入引用队列
虚引用:像没有引用一样,虚引用主要用于跟踪对象被gc的活动,必须和引用队列联合使用,回收前加入引用队列
ThreadLocal的ThreadLocaMap中key为弱引用,value为强引用,会出现key为null的键值对,会导致有的value值无法回收,ThreadLocalMap中的set、get、remove会清理key为null的键值对
线程池的好处:降低资源消耗、提高响应速度、提高线程的可管理性
runnable与callable的区别,callable可以有返回值,可以抛出异常,两者方法不同run和call
callable需要futuretask的支持,使用futuretask.get()获得返回值,

三大方法:execute和submit的区别,execute用于提交不需要返回值任务,无法判断任务的成功与否,submit用于提交需要返回值的任务,线程池会返回一个Future的任务,future.get获取返回值,阻塞到任务完成
不使用FixedThreadPool 和 SingleThreadExecutor是因为阻塞队列值为Integer.max会oom
CachedThreadPool 和 ScheduledThreadPool 允许创建的线程数为整型最大值会导致oom
atomic原子类:指操作不可中断,操作一旦开始,不会被其他线程干扰
基本类型 整型原子类:AtomicInteger,长整型原子类:AtomicLong,布尔型原子类:AtomicBoolean
数组类型
引用类型:
对象的属性修改属性:
AtomicInteger应用cas+volatile+native方法来保证原子性
AQS:AbstractQueuedSynchronizer
AQS是一个用来构建锁和同步器的框架,原理:AQS的核心思想就是,如果被请求的共享资源空闲,则将当前请求资源的线程置为有效的工作线程,且将共享资源置为所动状态,如果被请求资源被占用,那么就将该线程加入到FIFO虚拟(不存在队列实例)双向队列中
AQS对资源的共享方式:独占,只有一个线程可以访问,如reentrantLock
共享:CountDownLatch、Semaphore、CyclicBarrier、ReadWriteLock
Semaphore信号量,CountDownLatch倒计时器,CyclicBarrier循环栅栏,
CountDownLatch只能用一次,CyclicBarrier可以用次CyclicBarrier调用await将相乘阻塞,完成CyclicBarrier中的任务才会向后执行。

http与https:
超文本传输协议HTTP被用于在web浏览器和网站服务器之间传递消息,HTTP协议被以明文的方式发送内容,不提供任何方式加密,http不适合传输敏感信息
安全套接字层超文本传输协议HTTPs,加入了SSL协议,依靠证书来验证服务器的身份,并为浏览器和服务器之间通信加密。
http(应用层)是一个简单的请求响应协议,是面向事务的客户服务器协议,运行在TCP(传输层)之上,有两种报文请求报文和响应报文
http和https的区别:
https需要申请ca证书,http明文传输,https是加密传输,http是tcp三次握手连接,而https除了tcp外还有ssl握手的9个包,http响应快,http端口为80,https端口为443
http连接简单,无状态,
http1.0与http1.1
长连接,http1.1支持长连接和请求的流水线处理,在一个tcp连接上可以穿送多个http请求和响应,减少了建立和关闭连接的消耗和延迟,在http1.1中默认开启长连接,一定程度上弥补了http1.0每次请求都要创建连接的缺点,
节约带宽:http1.1支持只发送header,http1.0不支持断点续传
HOST域:http1.1支持host域,一台服务器可以部署两个站点
缓存处理:在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。缓存处理的方式更多
错误通知管理:http1.1新增24个错误状态码
http1.1与http2.0
多路复用:http2.0同一个连接并发处理多个请求,
头部数据压缩:http1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
服务器推送:HTTP2.0引入了server push,它允许服务端推送资源给浏览器,客户端可以直接从本地加载这些资源
JUC: java.util.concurrent
java默认两个线程,main、GC线程
java开不了线程,只能通过native方法执行
wait/sleep的 区别:来自不同类wait在object是实例方法,需要notify或notifyAll唤醒,sleep在Thread是静态方法,超时或interrupt,wait释放锁,sleep不会释放锁,wait只能在同步代码块中使用,sleep什么地方都可以,sleep需要捕获异常(InterruptedException),而wait需要
lock:ReentrantLock , ReentrantReadWriteLock.ReadLock , ReentrantReadWriteLock.WriteLock
公平锁:先来后到 非公平锁:可以插队(默认)
Synchronized与lock的区别:
Synchronized是内置的java关键字,Lock是一个类
Synchronized无法判断锁的状态,Lock可以判断是否获取到了锁,
Synchronized会自动释放锁,Lock必须要手动释放锁finally
Synchronized获得不到锁,会一直等待,Lock可以尝试获取锁,不需要一直等待
Synchronized可重入、不可中断,非公平,Lock可重入,可中断,公平/非公平
Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码
java虚假唤醒:需要将if语句修改为while
JUC8锁问题:谁先拿到锁谁先执行,锁的对象是方法的调用者
没有加synchronized的普通方法不受锁的影响
ConcurrentModificationException并发修改异常
并发下:ArrayList不安全的解决方案:
Vector<>() Collections.synchronizedList(new ArrayList<>());List list = new CopyOnWriteArrayList<>();
java COW写时复制,添加元素时,先复制一份数组,向新数组中添加元素,然后再将原容器的引用指向新容器。实现写时可读,读写分离,应用于读多写少,在写入时加lock,避免复制多份数据。适用于读多写少,缺点就是内存占用,内存压力大,引起频繁的GC,弱一致性,
HashSet set = new HashSet<>();
Set set = Collections.synchronizedSet(new HashSet<>());
Set set = new CopyOnWriteArraySet<>();
Callable 与Runnable区别:可以有返回值,可以抛出异常,方法不同run()/call,
String o = (String) task.get() 可能会阻塞,因为call方法中可能会耗时
new Thread(task).start();
new Thread(task).start();两次执行,只会执行一次call,因为futuretask在调用run方法时,会判断其线程状态,是否为new 不为new则直接返回。
CountDownLatch 是一个计数器,await方法会在计数为0的时候执行。
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
System.out.println(“chengong”);
}); 加法计数器,cyclicBarrier.await();调用7次await方法后,就可执行CyclicBarrier
Semaphore semaphore = new Semaphore(3) 信号量,同时访问资源的线程个数
当为1时,可实现互斥锁

读写锁,写锁是独占锁,读锁是共享锁
阻塞队列当队列为空或满时阻塞,非阻塞队列,当满时或空时,报错

ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
同步队列 SynchronousQueue 容量为1,put,take
线程池:池化技术 创建和销毁十分的浪费资源
优点:降低资源的消耗,提高响应速度,方便管理,线程复用,可以控制最大并发数、管理线程
三大方法、七大参数、四种拒绝策略

7大参数:
public ThreadPoolExecutor
(int corePoolSize,核心线程数
int maximumPoolSize,最大线程数
long keepAliveTime,线程空闲时最大存活时间
TimeUnit unit,最大存活时间单位
BlockingQueue workQueue,任务队列
ThreadFactory threadFactory,线程工厂用默认的即可
RejectedExecutionHandler handler)线程池和队列都满时的拒绝策略

四种拒绝策略:AbortPolicy:丢弃任务并抛出RejectedExecutionException异常 DiscardPolicy:丢弃任务,但是不抛出异常 DiscardOldestPolicy :丢弃队列最前面的任务,然后重新提交被拒绝的任务。CallerRunsPolicy:由调用线程处理该任务
Runtime.getRuntime().availableProcessors() 获取cpu核数
怎么设置线程池的最大值:
对于cpu密集型,可以设置为CPU核数,cpu利用率区域100%。
对于IO密集型,可以设置为2*io线程
函数式接口:

断定性接口:输入一个参数,返回布尔值

消费型接口:只输入不返回 供应型接口:只输出,不输入

JDK1.8新特性:lambda表达式,函数式接口,链式编程,Stream流式计算
Strea流式计算:

forkjoin:拆分任务,汇总结果。
工作窃取:双端队列,某个线程从其他队列里窃取任务来执行,提高效率,但是队列里只有一个任务时也会竞争,且该算法浪费资源
并行流计算,reduce从0开始,
long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0, Long::sum);
Volatile:是java虚拟机提供的轻量级同步机制

  1. 保证可见性 java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。MESI协议
  2. 不保证原子性 可以使用原子类解决或者lock或Synchronized
  3. 禁止指令重排 内存屏障
    JMM,java内存模型,
    8种操作每一种都是原子操作
    lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
    read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
    load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
    use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
    assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
    store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
    write(写入):作用于主内存,它把store传送值放到主内存中的变量中。unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
    规则:
    (1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
    (2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
    (3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
    (4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
    (5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
    (6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
    (7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
    (8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。
    单例模式:饿汉式,初始化就new对象,懒汉式,用的时候在new对象

暴力反射可以破坏此单例。
cas:比较并交换,ABA问题
乐观锁:cas+版本号 原子引用
lock的lock和unlock必须配对
可重入锁:可以多次获得同一锁。
悲观锁:在修改数据之前先锁定
乐观锁:乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。适用于读多写少的场景,
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
死锁排查:jps -l,查看进程,jstack 进程号查看
排查问题:日志,堆栈信息,
JVM:
内存区域:jdk1.8之前,(线程共享)堆,方法区,(线程私有)虚拟机栈、本地方法栈、程序计数器
jdk1.8之后:堆,本地内存(元空间,直接内存),虚拟机栈,本地方法栈,程序计数器
程序计数器:字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如顺序执行、选择、循环、异常处理
在多线程情况下,用于记录当前线程执行的位置,从而可以知道得到cpu资源后从哪开始执行,
程序计数器是唯一一个不会出现oom,生命周期和线程相同,虚拟机栈也是
java虚拟机栈:由一个个栈帧组成,每个栈帧表示一个函数,每个栈帧拥有:局部变量表、操作数栈、动态链接、方法出口信息。
其会出现栈溢出和oom
本地方法栈:虚拟机栈实行java字节码,本地方法栈执行native方法。其和虚拟机栈非常类似。
堆:虚拟机中最大的一块,所有线程共享,存放对象实例和数组,
堆可以分为新生代和老年代,新生代又分为eden,survivor from,和survivor to
永久代属于方法区,jdk1.8之后取消永久代,元空间代替,
新建对象会进入eden,在一次新生代垃圾回收后,如果对象还存活就进入s1或s0。且对象年龄加1,增加到15岁会进入老年代,
方法区:类信息,常量,静态变量,即时编译器编译后的代码,也被称为永久代
方法区是接口,永久代是实现类
jdk1.8方法区被移除,元空间,使用的是直接内存
为啥元空间替换永久代,1,永久代有一个JVM设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,收本机可用内存的限制,虽然原空间仍有可能溢出,但是几率小很多
2,元空间里面存放的是类的元数据,这样加载多少类的元数据,就由系统的实际可用空间决定了,可以加载更多的类
3.在jdk8,合并hotspot和jrockit的代码时,jrockit从来没有一个叫永久代的东西,合并之后就没有额外设置这么一个永久代了
运行时常量池是方法区的一部分,class文件除了有类的版本,字段,方法、接口等描述信息外,还有常量池表(用于存放编译器生成的各种字面量和符号引用)
常量池存的是引用,实例存在堆区
对象头中包括Mark word(主要用于存储运行时数据,hash,gc年龄,锁的状态)指向类的指针,数组长度(数组对象才有)
对象的创建:
1.类加载检查,new时,检查常量池中有无该类的模板,没有的话,就加载类
2.分配内存 从堆中分配内存,分配方式指针碰撞和空闲列表,由java堆是否规整,堆的规整又由所采用的GC是否带有压缩整理的功能,标记压缩和复制
3.初始化零值 保证不赋值就能直接使用
4.设置对象头 设置hash,gc和锁的信息
5.执行init方法,新的对象已经产生,
对象的内存分布:对象头,实例数据,对齐填充
对齐填充仅仅占位,因为虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,所以对象的大小必须是8字节的倍数,当实例数据不够8字节倍数时,就需要对齐填充。
对象的访问定位:使用句柄和直接指针两种
句柄:java堆中划分一块内存作为句柄池,java栈中的reference中存储对象的句柄地址,句柄中包含了对象实例数据与类型数据各自具体的信息。
直接指针:referen中存放对象的地址,堆中的实例数据包括对象类型数据的指针
句柄最大好处是句柄地址稳定,移动对象时只改变句柄中实例数据指针,而reference本身不改变,使用直接指针的好处是速度快,他节省了一次指针定位的时间开销
String s =”sasa”;从常量池中拿对象,String s=new String(“sasa”),在堆空间创建新对象
String str1 = “str”;
String str2 = “ing”;
String str3 = “str” + “ing”;//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象

String s1=new String(“abc”); 创建1或2个字符串对象,常量池中有abc就只在堆中新建一个,没有也会现在常量池中新建abc
Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character 创建了数值在[0,127]范围的缓存数据,Boolean 直接返回 True Or False。如果超出对应范围仍然会去创建新的对象。
在Integer中超出范围Integer i = 333;不会别缓存
java的堆是垃圾收集器管理的主要区域,GC堆,进一步划分的目的是更好地回收内存,更快的分配内存。
晋升老年代的阈值,遍历所有对象,按照年龄大小对器占用的大小进行累计,当累计的某个年龄大小时超过了survivor的一半时,取这个年龄和MaxTenuringThreshold中小的一个作为晋升老年代的阈值。
GC完后,Eden区和From区已经被清空,这时,From和To进行角色交换,To被填满时,会将所有对象移动到老年代
对象优先在eden区分配,存不了,会GC,
内存分配担保机制。在新生代无法分配内存的时候,把新生代的对象转移到老生代,然后把新对象放入腾空的新生代。
大对象直接存入老年代,避免由于内存担保机制带来的复制而降低效率。
长期存活的对象进入老年代,默认15或者6
主要进行GC的区域: 部分收集:新生代收集,老年代收集,混合收集(对整个新生代和部分老年代进行垃圾收集)
整堆收集:收集整个java堆和方法区

对象已经死亡:
引用计数法:每当有一个地方引用就加1,当引用失效件减1,当计数器为0时
实现简单,效率高,但很难解决相互循环引用
可达性分析:通过一系列的GC Roots的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的话,则证明此对象是不可用的。
可作为GC Roots的对象包括:虚拟机栈(栈帧中的本地变量表)中引用的对象,本地方法栈中引用的对象,方法区中常量引用的对象,所有被同步锁持有的对象。
不可达的对象并非非死不可:GC过程,可达性分析中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize方法,当对象没有覆盖finalize或者finalize方法已经被调用了,虚拟机认为这两种情况没有必要执行,
不可达的对象 会执行finalize,假如方法没有重新引用该对象,将会被回收。
如何判断一个常量是废弃常量。运行时常量池主要回收的是废弃的常量,1.7jdk后将字符串常量池移到了堆,运行时常量区还在方法区,jdk1.8,方法区转为元空间
字符串常量当没有任何对象引用时为废弃常量,
如何判断一个类是无用类,方法区主要回收无用类,
该类的所有实例已经被回收,加载该类的ClassLoader已经被回收,该类对应的java.lang.Class对象没有任何地方引用,无法在任何地方通过反射访问该类
垃圾收集算法:
标记-清除:标记不需要回收的对象,清除没有被标记的对象
特点:简单容易实现,但产生碎片
标记-复制:将内存分为两块,每次使用其中一块,当这一块使用完了,保留存活对象到另一块,将该块内存清空
特点:实现简单,运行高效且不容易产生内存碎片,但是可用内存只有原来的一半,适用于存活时间短的区域
标记-整理(压缩):首先标记存活对象,然后将存活对象向一端移动,然后清理掉边界意外的内存。
分代收集:老年代是每次垃圾只有少量对象需要回收,新生代是每次都有大量对象需要回收,
新生代采用复制算法,8:1:1将新生代分为一块较大的eden和两块较小的survivorfrom和survivorto,每次使用Eden和survivorfrom,当回收时,将eden和survivorfrom中的对象复制到survivorto中,然后清理到eden和survivorfrom
老年代每次都会很少的对象,一般使用标记整理
JDK监控和故障处理工具总结
jps 查看java进程pid jps -q 只输出pid ,jps -l输出主类的全名
jps -v 输出jvm参数
jstat :用以监视虚拟机运行状态信息
jinfo:实时查看和调整虚拟机各项参数(可以在运行期间修改jvm参数)
jmap:生成堆转储快照,
jhat:分析heapdump
jstack:生成虚拟机当前时刻的线程快照,查看死锁
jconsole工具
类文件结构:
ClassFile:
魔数:四个字节,确定这个文件是否为一个能被虚拟机接受的Class文件
Class版本号:表示java的版本
常量池:常量池的数量,常量池主要存放:字面量和符号引用
访问标志:是类还是接口,访问修饰符,public
当前类,父类,接口索引集合:全限定类名,除Object类都为1,
接口索引用来描述实现了什么接口,
字段表集合:用于描述接口或类中声明的变量,字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
方法表集合:描述有哪些方法,访问标志,名称索引,描述符索引,属性表集合
属性表集合:字段表和方法表都可以携带自己的属性表集合,
类的生命周期:1.加载2.连接(验证、准备、解析)3.初始化4.使用5.卸载
类加载过程:class文件需要加载到虚拟机中。
加载:1.通过全类名获取定义此类的二进制字节流
2.将字节流所代表的静态存储结构转换为方法区时运行时数据结构
3.在内存中生成一个代表该类的class对象,作为方法区这些数据的访问入口
验证:文件格式验证,元数据验证,字节码验证,符号引用验证
准备:正式为类变量分配内存并设置类变量的初始值,在方法区中分配,
内存分配仅包括静态变量,只与类相关的类变量,从概念上讲应在方法区中分配,jdk1.7中hotspot使用永久代来实现方法区的时候,是符合的,jdk1.7后hotspot已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这时候类变量会随着Class对象一起存放在java堆中
初始值默认零值,final有初始值,static初始值为0,初始化才赋值
解析:虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
初始化:执行初始化方法 ,为静态变量赋值,类加载的最后一步。
触发初始化的条件,只有主动去使用类才会初始化
1,当遇到new、getstatic、putstatic、invokestatic 这 4 条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
• 当 jvm 执行 new 指令时会初始化类。即当程序创建一个类的实例对象。
• 当 jvm 执行 getstatic 指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
• 当 jvm 执行 putstatic 指令时会初始化类。即程序给类的静态变量赋值。
• 当 jvm 执行 invokestatic 指令时会初始化类。即程序调用类的静态方法。
2.使用java.lang.reflect包的方法对类反射调用时
3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化
4、当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
5.MethodHandle 和 VarHandle 可以看作是轻量级的反射调用机制,而要想使用这 2 个调用, 就必须先使用 findStaticVarHandle 来初始化要调用的类。
6.「补充,来自issue745」 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
卸载:GC
卸载的3个条件:
该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
该类没有在其他任何地方被引用
该类的类加载器的实例已被 GC
由 jvm 自带的类加载器加载的类是不会被卸载的。
三个重要classloader:
BootstrapClassLoader:启动类加载器,最顶层的加载器,c++,负责加载lib目录下的jar和类或者被-Xbootclasspath参数执行的类
ExtensionClassLoader(扩展类加载器):主要加载lib/ext目录下的或被java.ext.dirs系统变量所指定路径下的jar
AppClassLoader(应用程序类加载器):面向用户加载器,负责加载当前应用classpath下的所有jar和类
双亲委派机制:每一个类都有一个对应它的类加载器,
首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
好处:双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。
操作系统
什么是操作系统:是管理计算机硬件资源和软件资源的程序,是计算机的基石,本质上是一个软件程序,操作系统屏蔽了硬件层面的复杂性,操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
什么是系统调用:根据访问资源的特点,可以将进程在系统上分为两个级别:用户态:
用户态运行的进程可以直接读取用户程序的数据
系统态:可以简单的理解系统态运行的进程或程序可以访问计算机的任何资源
我们运行的程序基本都是用户态,当需要与系统态级别的资源有关操作,都必须通过系统调用方式向操作系统提出服务请求,并有操作系统代为完成
进程间的通信方式:
管道/匿名管道:用于具有亲缘关系的父子进程间或者兄弟间的通信,
有名管道:有名管道以磁盘文件的方式存在,可以实现本机任意两个进程间通信
信号:用于接收进程某个事件已经发生,
消息队列:
信号量:信号量是一种计数器,用于进程对共享数据的访问
共享内存:使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新
套接字:此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点
线程间的同步方式:互斥量:采用互斥对象机制,只有拥有互斥对象的线程才可以访问公共资源,因为互斥量只有一个,所以可以保证公共资源不会被多个线程同时访问,synchronized和lock
信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
事件:notify 通过通知操作来保持多线程同步,
进程的调度算法:
先到先服务,短作业优先,时间片轮转,多级反馈队列,优先级调度
内存:
操作系统的内存管理主要是做什么:主要负责内存的分配与回收,还有地址转换,逻辑地址转换为物理地址,
内存管理机制:简单分为连续分配管理方式和非连续分配管理方式,
连续分配管理主要有块式管理:将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片。
非连续主要有页式管理和段式管理,页式管理就是把主存分为大小相等且固定的一页一页的形式,页较小,相对相比于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。段式管理, 页式管理虽然提高了内存利用率,但是页式管理其中的页实际并无任何实际意义。 段式管理把主存分为一段段的,每一段的空间又要比一页的空间小很多。但是,最重要的是段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段MAIN、子程序段X数据段D及栈段S等。 段式管理通过段表对应逻辑地址和物理地址。
段也是管理:段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页, 段页式管理机制 中段与段之间以及段的内部的都是离散的。
分页机制和分段机制有哪些共同点和区别呢?
共同点 :1分页机制和分段机制都是为了提高内存利用率,较少内存碎片。2页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
区别 :1页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。2分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
c语言中的指针是逻辑地址,由操作系统决定,物理地址是真实的物理内存中的地址,
计算机网络:
应用层(http,smtp),运输层(TCP、UDP),网络层(ip),数据链路层,物理层,
tcp三次握手,1,客户端向服务端发送SYN标志的数据包,2、服务端发送带有SYN/ACK的数据包 3.客户端发送带有ACK的标志数据包
第一次握手:客户端什么都不能确认,服务端确认了对方发送正常,自己接收正常,
第二次握手:客户端确认自己发送接收正常,对方发送和接收正常,服务端确认对方发送正常,自己接收正常,
第三次握手:客户端确认自己发送接收正常,对方发送接收正常,服务端也确认自己发送、接收正常,对方发送、接收正常。
为啥需要第三次握手:两次握手的问题在于服务器端不知道一个SYN是否是无效的,而三次握手机制因为客户端会给服务器回复第二次握手,也意味着服务器会等待客户端的第三次握手,如果第三次握手迟迟不来,服务器便会认为这个SYN是无效的,释放相关资源。
两次握手 服务端无法确认自己的发送和客户端的接收是否有问题,
三次握手也为了防止已经失效的连接请求突然又传到了服务器。
四次挥手:客户端发送一个FIN,用来关闭客户端到服务器的数据传输
服务器收到这个FIN,它发回一个ACK,确认序号加1
服务器关闭与客户端的连接,发送一个FIN给客户端,
客户端发回ACK确认,并将确认序号设为收到序号+1
为啥客户端要等待2MSL:保证客户端的发送的最后一个确认报文能够到达服务器,因为这个确认报文可能会丢失,当服务端接收不到确认时,会重发Fin,这两个2MSL也可以使本连接时间内所产生的的报文段从网络中消失,避免新的连接出现旧的报文
为啥建立连接三次而关闭连接确是四次挥手:建立连接时,服务器收到建立请求的SYN后,把ACK和SYN放在一个报文里发送给客户端,
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徒手写bug326

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值