本文避免重复造轮子,也是从JavaGuider中提取出来方便日后查阅的手册
参考链接:
JavaGuider:https://javaguide.cn/java/basis/java-basic-questions-01/
JVM内存结构:https://blog.csdn.net/rongtaoup/article/details/89142396
文章目录
1.基础概念与常识
1.1.Java语言特点1.2字节码与编译和解释并存1.3 字符常量和字符串常量区别1.4 Java关键字1.5.泛型1.6、==和equals(),HashCode()1.7 基本数据类型1.7 基本数据类型1.8 I/O操作1.9 I/O操作1.10 Java值传递1.11 静态代理与动态代理1.12 IO模型 2.容器
2.1.Java集合2.2.==与 equals 的区别2.3 HashMap底层实现(JDK1.8前后)2.4 ConcurrentHashmap底层原理2.6 Synchronized的升降级 3.并发编程
3.1.进程与线程3.2.上下文切换3.3 Sleep与wait区别3.4 start与run3.5 Synchronized关键字3.6 其他参数3.7 AQS3.8 补充 4.JVM
4.1 JVM内存区域详解
4.1.0 JVM内存区域4.1.1对象的创建4.1.2 对象访问 4.2 JVM垃圾回收详解
4.2.1 Jvm垃圾回收相关问题4.2.2 对象是否死亡4.2.3、垃圾回收(算法与工具) 4.3 类文件结构、类加载过程(略)4.4 JVM性能调优(待续)4.5 JVM排查命令 5.新特性
5.1 Java8新特性实战、《Java8指南》、JDK9~15、小技巧(略) 1.基础概念与常识 1.1.Java语言特点
跨平台、面向对象(封装继承多态)、一次编写到处运行、多线程可靠安全、编译与解释并存
1.2字节码与编译和解释并存被编译成.class后缀的文件就是字节码。而经过解释器JIT第一次解释后,后续热点代码(频繁使用的代码。消耗大部分系统资源的只有一小部分代码,这就是热点代码)的字节码对应的机器码就会被保存下来,然后每次代码执行的时候都会优化,这样就会越来越快
1.3 字符常量和字符串常量区别字符常量是单引号,占一个字节(但java中char是2个字节),字符串常量是双引号,占若干字节
1.4 Java关键字Java泛型是伪泛型,即java运行期间所有类型擦除,数据类型被转换成一个参数。比如:
泛型类:public Class Study{} 实例化方式 Study genericInteger = new Study(123456);
泛型接口:public interface Study{} 实例化方式 class StudyImpl implements Study{}
泛型方法:public static void printArray(){E[] inputArray}
项目哪里用到了泛型?
项目接口返回Result参数的时候ExcelUtil生成excel的时候工具类Collections.sort这些地方
(注:个人觉得泛型适用性强,在和外部接口对接的时候,为防止外部参数类型经常变更,可以改成泛型。或者自己复用引用高的代码块的时候,也可以把代码块改成泛型。)
1.6、==和equals(),HashCode()==比较地址equals()比较对象,
HashCode是对堆上的对象产生独特值(不唯一,因为因hash算法也会产生不同对象相同hash值),如果重写equals不重写hashcode,那两个相同对象也会有不同的hashcode
1.7 基本数据类型这 8 种基本数据类型的默认值以及所占空间的大小如下:
引用类型Byte,Short,IntegerLong创建了[-128,127]的缓存,Character创建了[0,127]的缓存
注:频繁拆箱装箱也非常影响系统性能
1.7 基本数据类型 序列化:数据结构或对象转成二进制字节流
反序列化:序列化生成的二进制转成数据结构或对象
1.8 I/O操作 分为InputStream/Reader:字节输入流与字符输入流
OutputStream/Writer:字节输出流与字符输出流
1.9 I/O操作反射:程序在运行时分析类和执行类方法的能力。(比如@Value就能在运行时给某个对象赋值,相比正射的set,get方法更灵活)
1.10 Java值传递 1.11 静态代理与动态代理 静态代理是由代理对象和目标对象实现一样的接口
动态代理是利用反射机制在运行时创建代理类。
动态代理:JDK动态代理实现了接口的类或直接代理接口,而CGLIB可代理未实现任何接口的类。与基于Java字节码实现的Javassist
动态代理更灵活,不需要实现接口就可以代理实现类。静态代理是在编译时将接口、实现类、代理类生成.class文件
动态代理底层:反射
Proxy:生产代理实例
InvocationHandler:处理代理实例并返回结果
1.12 IO模型同步阻塞IO:BIO
同步非阻塞IO:NIO
异步IO:AIO
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2EsgvZQJ-1644739167367)(https://images.xiaozhuanlan.com/photo/2020/33b193457c928ae02217480f994814b6.png)]
2.容器 2.1.Java集合
Java集合:
1、Collection接口:list,queue,set2、Map接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YChJP6iU-1644739167369)(https://img2022.cnblogs.com/blog/1176183/202202/1176183-20220213154908189-1867285349.png)]
2.2.==与 equals 的区别对于基本类型来说,== 比较的是值是否相等;
对于引用类型来说,== 比较的是两个引用是否指向同一个对象地址(两者在内存中存放的地址(堆内存地址)是否指向同一个地方);
对于引用类型(包括包装类型)来说,equals 如果没有被重写,对比它们的地址是否相等;如果 equals()方法被重写(例如 String),则比较的是字符串值是否相等。
2.3 HashMap底层实现(JDK1.8前后)JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。
JDK1.8之后HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
(put逻辑:如果定位到的数组位置没有元素 就直接插入。
如果定位到的数组位置有元素就和要插入的 key 比较,如果 key 相同就直接覆盖,如果 key 不相同,就判断 p 是否是一个树节点,如果是就调用e = ((TreeNode
相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
2.4 ConcurrentHashmap底层原理JDK1.8之后ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))
synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍
2.6 Synchronized的升降级对象分为对象头(MarkWord)、实例变量、填充字节。
锁的4中状态:无锁状态、偏向锁状态(弄人开启)、轻量级锁状态、重量级锁状态(级别从低到高)
Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
3.并发编程 3.1.进程与线程
进程:启动一个程序(启动main函数就是启动了一个jvm进程),而main函数的的线程就是这个进程中的一部分
多个线程共享内存中的堆与方法区,但是各个线程又有自己的虚拟机栈、本地方法栈、程序计数器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8ET3RDA-1644739167370)(https://img2022.cnblogs.com/blog/1176183/202202/1176183-20220213154510291-2045033172.png)]
3.2.上下文切换 线程会有主动阻塞(Sleep,wait)让出CPU、时间片用完、IO阻塞等线程切换情况,这时需要保留现场方便后续CPU调用,同时加载下一次线程用CPU的上下文,这就叫上下文切换。
3.3 Sleep与wait区别 Sleep没有释放锁,会自动唤醒,而wait释放了锁,必须要notify/notifyall唤醒
3.4 start与run start启动线程做了线程准备工作后调用run方法并让线程进入就绪状态,CPU分配了时间片后就可以开始执行,而run方法并不会在某个线程中执行,不属于多线程。
3.5 Synchronized关键字 挂起或唤醒一个线程,都需要OS操作用户态到核心态的转换,这个过程非常耗时。
Synchronized作用域
修饰实例方法:作用于对象实例修饰静态方法,作用于类.class修饰代码块,作用域对象的锁(this,OBject)或类的锁(类.calss) 3.6 其他参数 以下内容见《JUC并发编程》
volatile(非线程安全)
ThreadLocal(线程本地变量)
线程池(三大方法、七大参数,四种拒绝策略)
3.7 AQS 简介:是juc下具体类
核心原理:多线程情况下,空闲的共享资源加锁,非空闲的共享资源使用CLH队列锁(内部是虚拟的双向队列,FIFO)进行线程队列等待与唤醒的锁分配策略
定义资源的共享方式:
1、Exclusive(独占):Reentranlock,synchronized
2、Share(共享):Countdownlatch,semaphore,cyclicBarrier,readwriteLock
3.8 补充this逃逸:构造函数返回之前其他线程就持有该对象引用,其他线程调用尚未构造完全的对象方法引发错误
4.JVM 4.1 JVM内存区域详解 4.1.0 JVM内存区域1、PC程序计数器:线程中的信号指示器,用于用来读取下一条指令功能。、2、Java虚拟机栈:包含一个个栈帧(局部变量表等等,局部变量表又包含8个基本数据类型(),对象应用)3、本地方法栈:虚拟机栈为java服务,本地方法栈为虚拟机用到的Native服务(也有本地方法的局部变量表等待)4、堆:存放分配几乎所有对象实例与数组内存、**字符串常量池、静态变量(jdk1.8之后)** 1、字符串常量池:存放"asd" + "abc"(**常量折叠**)5、方法区:存储已被虚拟机加载的类信息(类如何放入方法区,reference如何指向就是这里)、常量。**方法区和永久代的关系很像 Java 中接口和类的关系** 1、运行时常量池:类的版本、字段、方法、接口等描述信息外,还有常量池表(类的相关信息) 2、**jdk1.8之后是把永久代实现方法区方式换成了元空间实现方式**6、直接内存(堆外内存):NIO
4.1.1对象的创建 Java对象创建分如下5步:
类加载检查:遇到new 指令时,进行类的检查与加载。分配内存:在堆中分配内存,有指针碰撞与空闲列表2种分配方式。Java堆是否规整由垃圾回收算法决定。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sSK5Jnry-1644739167371)(https://img2022.cnblogs.com/blog/1176183/202202/1176183-20220213154621925-89641044.png)]
保证内存分配的线程安全:
CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
初始化零值:给每个new 对象有初始化值设置对象头:markword执行init方法:按照代码进行init,比如定义数组大小。上述可以说只是定义分配了内存位置,这里根据init定义具体大小。new ArrayList<>(3);
4.1.2 对象访问 句柄访问:Java栈的本地变量表访问堆中的句柄池中的实例对象指针(类似于操作系统中的虚拟地址表),然后访问堆或者方法区中的实例对象。缺点就是多访问了一次指针定位实例数据的时间。直接指针:Java栈的本地变量表存的就是直接访问堆中的实例对象的地址。访问方法区就是堆中存了实例对象地址。缺点就是后续垃圾回收移动对象地址的时候,需要改变栈中的本地变量表。// 上面说了new ArrayList<3>,这里说的2种方法就是一整条语句了。List
注:JVM垃圾回收在于线程共享区域:即堆、方法区(已被虚拟机加载的静态变量、常量等代码与运行时常量池)
下面介绍JVM垃圾回收具体流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9IMAN7Iv-1644739167372)(https://img2022.cnblogs.com/blog/1176183/202202/1176183-20220213155517812-903068596.png)]
4.2.2 对象是否死亡 1.如何判断对象是否死亡:引用计数器与可达性算法
2.指向对象的引用:强引用,软引用,弱引用,幻想引用(虚引用)
目前尚有一个疑问?
目前垃圾回收器普遍都是分代回收算法,但是又说JDK1.8是默认采用Parallel scavenge+Serial Old收集器,但这2个收集器并不是GC算法,是否冲突?
答:相当于所有垃圾回收器都默认使用了分代回收算法
4.2.3、垃圾回收(算法与工具)垃圾回收算法:
1、Mark-sweep(标记-清除 MS算法):标记全部堆中可回收资源进行回收2、Copying(复制 CP算法):堆内存分成2半,一半用完了复制并整理整齐到另一半上,然后把这一半清空。3、Mark-Compact(标记-整理 MC算法):MS与CP结合,将全部堆内容标记,然后整理整齐清空。4、Generation Collection(分代收集 GC算法):根据对象存活周期将内存分为新生代与老生代进行收集。垃圾收集器:1、Serial Old收集器:MC算法2、ParNew收集器:Serial的多线程版本,MC算法3、Parallel Scavenge:Cp算法4、Parallel Old:MC算法5、CMS:MS算法6、G1:MS算法
4.3 类文件结构、类加载过程(略) 4.4 JVM性能调优(待续) 能力有限,暂时用不到这些,后面用到了进一步了解。
学习链接:https://javaguide.cn/java/jvm/jvm-parameters-intro/#_3-2-gc%E8%AE%B0%E5%BD%95
4.5 JVM排查命令 参考命令:https://javaguide.cn/java/jvm/jdk-monitoring-and-troubleshooting-tools/#jps-%E6%9F%A5%E7%9C%8B%E6%89%80%E6%9C%89-java-%E8%BF%9B%E7%A8%8B
注:Jdk1.8默认采用Parallel Scavenge(新生代) + Serial Old(老年代)
5.新特性 5.1 Java8新特性实战、《Java8指南》、JDK9~15、小技巧(略) 权当手册查了。
书山有路勤为径,学海无涯苦作舟。程序员不仅要懂代码,更要懂生活,关注我,一起进步。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXuy0YTE-1644739167373)(https://img2022.cnblogs.com/blog/1176183/202202/1176183-20220213154728876-1094084242.jpg)]