大家好,我是二哥呀。
牛客上刷到一条比较有意思的帖子,名叫“八股选手进了公司就原形毕露了”,原因是不太会用 Git,checkout 和 merge 都搞错了。
截图来自牛客的仙林第一深情
不过讲良心话,这真的情有可原,好吧,因为这玩意学校又不教,面试的时候也几乎不怎么问,属于大多数初入职场的小伙伴非常欠缺的一项技能点。
反正我刚参加工作那会也不会用 Git,更没用过 Xshell 这种可以远程操作 Linux 服务器的终端,而这两项技能包又属于工作党必须具备的。
对 Git 还比较陌生的小伙伴可以去二哥的 Java进阶之路上看一下这篇教程,1.3 万字,大约 43 分钟就能读完,读完后你对 Git 会有一个全新的认知和理解。
网址:https://javabetter.cn/git/git-qiyuan.html
对 Linux 比较陌生的小伙伴,推荐大家去看看这个开源仓库 ./missing-semester
,会把 Shell、Vim、命令行、Git、持续集成等几个方面都讲清楚。
https://missing-semester-cn.github.io/2020/course-shell
你别说,国内的互联网公司,面试真的很接近古代的科举考试。所以说,面试变成八股我一点也不意外。再说,能背会八股难道不也是一种能力吗?(手动狗头)
好,接下来就分享一份二哥编程星球里一位球友刚刚分享的得物八股,大家来感受一下,这八股不背能行吗?(🤣)
JVM 的内存区域,有时叫 JVM 的内存结构,有时也叫 JVM 运行时数据区,按照 Java 的虚拟机规范,可以细分为程序计数器
、虚拟机栈
、本地方法栈
、堆
、方法区
等。
三分恶面渣逆袭:Java虚拟机运行时数据区
其中方法区
和堆
是线程共享的,虚拟机栈
、本地方法栈
和程序计数器
是线程私有的。
Java 堆被划分为新生代(Young Generation)和老年代(Old Generation)两个区域。
三分恶面渣逆袭:Java堆内存划分
新生代又被划分为 Eden 空间和两个 Survivor 空间(From 和 To)。
对象在新生代中经历多次 GC 后,如果仍然存活,会被移动到老年代。
垃圾回收(Garbage Collection,GC)就是对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
JVM 在做 GC 之前,会先搞清楚什么是垃圾,什么不是垃圾,通常会通过可达性分析算法来判断对象是否存活。
二哥的 Java 进阶之路:可达性分析
在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是进行垃圾回收,可以采用标记清除算法、复制算法、标记整理算法、分代收集算法等。
JVM 提供了多种垃圾回收器,包括 CMS GC、G1 GC、ZGC 等,不同的垃圾回收器采用的垃圾收集算法也不同,因此适用于不同的场景和需求。
比如说 CMS 是第一个关注 GC 停顿时间(STW 的时间)的垃圾收集器,JDK 1.5 时引入,JDK9 被标记弃用,JDK14 被移除。
G1(Garbage-First Garbage Collector)在 JDK 1.7 时引入,在 JDK 9 时取代 CMS 成为了默认的垃圾收集器。
有梦想的肥宅:G1 收集器
ZGC 是 JDK11 推出的一款低延迟垃圾收集器,适用于大内存低延迟服务的内存管理和回收,在 128G 的大堆下,最大停顿时间才 1.68 ms,性能远胜于 G1 和 CMS。
Full GC 是指对整个堆内存(包括新生代和老年代)进行垃圾回收操作。Full GC 频繁会导致应用程序的暂停时间增加,从而影响性能。
常见的原因有:
大厂一般都会有专门的性能监控系统,可以通过监控系统查看 GC 的频率和堆内存的使用情况。
否则可以使用 JDK 的一些自带工具,包括 jmap、jstat 等。
# 查看堆内存各区域的使用率以及GC情况
jstat -gcutil -h20 pid 1000
# 查看堆内存中的存活对象,并按空间排序
jmap -histo pid | head -n20
# dump堆内存文件
jmap -dump:format=b,file=heap pid
或者使用一些可视化的工具,比如 VisualVM、JConsole 等。
假如是因为大对象直接分配到老年代导致的 Full GC 频繁,可以通过 -XX:PretenureSizeThreshold
参数设置大对象直接进入老年代的阈值。
或者能不能将大对象拆分成小对象,减少大对象的创建。比如说分页。
假如是因为内存泄漏导致的 Full GC 频繁,可以通过分析堆内存 dump 文件找到内存泄漏的对象,再找到内存泄漏的代码位置。
假如是因为长生命周期的对象进入到了老年代,要及时释放资源,比如说 ThreadLocal、数据库连接、IO 资源等。
假如是因为 GC 参数配置不合理导致的 Full GC 频繁,可以通过调整 GC 参数来优化 GC 行为。或者直接更换更适合的 GC 收集器,如 G1、ZGC 等。
在并发量特别高的情况下,ReentrantLock 的性能可能会优于 synchronized,原因包括:
ConcurrentHashMap 在 JDK 7 时采用的是分段锁机制(Segment Locking),整个 Map 被分为若干段,每个段都可以独立地加锁。因此,不同的线程可以同时操作不同的段,从而实现并发访问。
初念初恋:JDK 7 ConcurrentHashMap
在 JDK 8 及以上版本中,ConcurrentHashMap 的实现进行了优化,不再使用分段锁,而是使用了一种更加精细化的锁——桶锁,以及 CAS 无锁算法。每个桶(Node 数组的每个元素)都可以独立地加锁,从而实现更高级别的并发访问。
初念初恋:JDK 8 ConcurrentHashMap
同时,对于读操作,通常不需要加锁,可以直接读取,因为 ConcurrentHashMap 内部使用了 volatile 变量来保证内存可见性。
对于写操作,ConcurrentHashMap 使用 CAS 操作来实现无锁的更新,这是一种乐观锁的实现,因为它假设没有冲突发生,在实际更新数据时才检查是否有其他线程在尝试修改数据,如果有,采用悲观的锁策略,如 synchronized 代码块来保证数据的一致性。
ConcurrentHashMap 在 JDK 1.7 和 JDK 1.8 中的实现机制不同,主要体现在锁的机制上。
JDK 1.7 中的 ConcurrentHashMap 使用了分段锁机制,即 Segment 锁,每个 Segment 都是一个 ReentrantLock,这样可以保证每个 Segment 都可以独立地加锁,从而实现更高级别的并发访问。
而在 JDK 1.8 中,ConcurrentHashMap 取消了 Segment 分段锁,采用了更加精细化的锁——桶锁,以及 CAS 无锁算法,每个桶(Node 数组的每个元素)都可以独立地加锁,从而实现更高级别的并发访问。
再加上 JVM 对 synchronized 做了大量优化,如锁消除、锁粗化、自旋锁和偏向锁等,在低中等的竞争情况下,synchronized 的性能并不比 ReentrantLock 差,并且使用 synchronized 可以简化代码实现。
速度快的原因主要有⼏点:
①、基于内存的数据存储,Redis 将数据存储在内存当中,使得数据的读写操作避开了磁盘 I/O。而内存的访问速度远超硬盘,这是 Redis 读写速度快的根本原因。
②、单线程模型,Redis 使用单线程模型来处理客户端的请求,这意味着在任何时刻只有一个命令在执行。这样就避免了线程切换和锁竞争带来的消耗。
③、IO 多路复⽤,基于 Linux 的 select/epoll 机制。该机制允许内核中同时存在多个监听套接字和已连接套接字,内核会一直监听这些套接字上的连接请求或者数据请求,一旦有请求到达,就会交给 Redis 处理,就实现了所谓的 Redis 单个线程处理多个 IO 读写的请求。
三分恶面渣逆袭:Redis使用IO多路复用和自身事件模型
④、高效的数据结构,Redis 提供了多种高效的数据结构,如字符串(String)、列表(List)、集合(Set)、有序集合(Sorted Set)等,这些数据结构经过了高度优化,能够支持快速的数据操作。
跳表(skiplist)是一种有序的数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。
三分恶面渣逆袭:跳表
在计算机系统中,内存可以分为两大区域:内核空间(Kernel Space)和用户空间(User Space)。这种划分主要用于保护系统稳定性和安全性。
二哥的 Java 进阶之路:用户空间和内核空间
当程序使⽤⽤户空间时,我们常说该程序在 ⽤户态 执⾏,⽽当程序使内核空间时,程序则在 内核态 执⾏。
当应用程序执行系统调用时,CPU 将从用户态切换到内核态,进入内核空间执行相应的内核代码,然后再切换回用户态。
三分恶面渣逆袭:用户态&内核态切换
系统调用是应用程序请求操作系统内核提供服务的接口,如文件操作(如 open、read、write)、进程控制(如 fork、exec)、内存管理(如 mmap)等。
进程是一个正在执行的程序的实例。每个进程有自己独立的地址空间、全局变量、堆栈、和文件描述符等资源。
线程是进程中的一个执行单元。一个进程可以包含多个线程,它们共享进程的地址空间和资源。
多线程-图片来源于网络
每个进程在独立的地址空间中运行,不会直接影响其他进程。线程共享同一个进程的内存空间、全局变量和文件描述符。
进程切换需要保存和恢复大量的上下文信息,代价较高。线程切换相对较轻量,因为线程共享进程的地址空间,只需要保存和恢复线程私有的数据。
线程的生命周期由进程控制,进程终止时,其所有线程也会终止。
特性 | 进程 | 线程 |
---|---|---|
地址空间 | 独立 | 共享 |
内存开销 | 高 | 低 |
上下文切换 | 慢,开销大 | 快,开销小 |
通信 | 需要 IPC 机制,开销较大 | 共享内存,直接通信 |
创建销毁 | 开销大,较慢 | 开销小,较快 |
并发性 | 低 | 高 |
崩溃影响 | 一个进程崩溃不会影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |