栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

字节面试准备

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

字节面试准备

字节面试准备

Java 基础

锁和锁的状态

偏向锁自旋锁轻量级锁重量级锁 HashMap 扩容

JDK1.7之前JDK 1.8 之后 JVM

JVM架构图JVM内存模型GC

JVM GC时候核心参数GC 日志分析三种基本的GC算法根可达性算法垃圾回收器

Java 基础 锁和锁的状态

Java中,锁分大致可分为4种. 偏向锁,轻量级锁,重量级锁

在java内存模型中,每个对象都有一个对象头。Java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位。

偏向锁

大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。一般锁的获取都是由同一个线程来操作的。在这种情况下如果不断地进行线程的阻塞或者释放是非常浪费的。所以Java语言就引入了偏向锁的概念。在同一个线程多次尝试获取锁的时候,会直接将锁分给当前线程,不进行加锁过程。

偏向锁加锁具体过程:

在线程尝试获取锁的时候,会查看当前锁对象头的偏向锁信息。如果是偏向锁并且线程为当前线程,则会直接让当前线程获取锁权限,不进行加锁。
如果为偏向锁状态但是是别的线程,则会进行锁升级,升级为自旋锁
如果为无锁状态,则会设置为偏向锁,并且将偏向线程id记录为当前线程id,以便下一次该线程访问直接进入

偏向锁解锁

偏向锁使用了一种等到竞争出现才释放锁的机制。所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word,要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

自旋锁

自旋锁是一种锁的实现方式

线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作。同时我们可以发现,很多对象锁的锁定状态只会持续很短的一段时间,例如整数的自加操作,在很短的时间内阻塞并唤醒线程显然不值得,为此引入了自旋锁。

所谓“自旋”,就是让线程去执行一个无意义的循环,循环结束后再去重新竞争锁,如果竞争不到继续循环,循环过程中线程会一直处于running状态,但是基于JVM的线程调度,会出让时间片,所以其他线程依旧有申请锁和释放锁的机会。

自旋锁省去了阻塞锁的时间空间(队列的维护等)开销,但是长时间自旋就变成了“忙式等待”,忙式等待显然还不如阻塞锁。所以自旋的次数一般控制在一个范围内,例如10,100等,在超出这个范围后,自旋锁会升级为阻塞锁。

轻量级锁

加锁

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,则自旋获取锁,当自旋获取锁仍然失败时,表示存在其他线程竞争锁(两条或两条以上的线程竞争同一个锁),则轻量级锁会膨胀成重量级锁。

解锁

轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示同步过程已完成。如果失败,表示有其他线程尝试过获取该锁,则要在释放锁的同时唤醒被挂起的线程。

重量级锁

重量锁在JVM中又叫对象监视器(Monitor),它很像C中的Mutex,除了具备Mutex(0|1)互斥的功能,它还负责实现了Semaphore(信号量)的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责做互斥,后一个用于做线程同步。

HashMap 扩容

HashMap在插入的元素数量过多的情况下,会进行hash扩容。
这个插入的元素数量过多的定义为:数组长度系数。系数默认为0.75;
扩容会将table长度
2,并且重新计算内部元素所在table的下标。

JDK1.7之前

扩容采用头插法,新table中链表的顺序和旧列表中是相反的。链表在多线程下可能导致环状链表的问题。

JDK 1.8 之后

采用尾插法。
确定元素所在下标的方法为:hash&(length-1);由于length是二的N次幂,则length-1在二进制下有效位全为1,方便进行位运算
扩容后数组长度×2,在二进制下表示就是多了一位,length-1自然也是多一位.那么hash&(length-1)就会有两种结果,跟之前一样,或者是之前的二倍。方便定位元素所在table的下标

JVM JVM架构图

堆,栈,方法区,本地方法区->运行时常量池,程序计数器,

方法区:所有类级别数据将被存储在这里,包括静态变量。每个JVM只有一个方法区,它是一个共享的资源。

堆:存放对象的地方。所有线程共享一个堆内存。会出现并发问题

栈:对每个线程会单独创建一个运行时栈。对每个函数呼叫会在栈内存生成一个栈帧(Stack frame)。所有的局部变量将在栈内存中创建。栈区是线程安全的,因为它不是一个共享资源。栈帧被分为三个子实体:

a 局部变量数组 – 包含多少个与方法相关的局部变量并且相应的值将被存储在这里。

b 操作数栈 – 如果需要执行任何中间操作,操作数栈作为运行时工作区去执行指令。

c 帧数据 – 方法的所有符号都保存在这里。在任意异常的情况下,catch块的信息将会被保存在帧数据里面。如上是JVM三大核心区域

JVM内存模型

JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)在1.7之后将其变为了元空间。把对象根据存活概率进行分类,采用分代回收机制,从而减少扫描垃圾时间及GC频率。

年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。From和To是主要为了解决内存碎片化。

堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。

非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。

在JDK1.8版本废弃了永久代,替代的是元空间(metaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。有了元空间就不再会出现永久代OOM问题了!

元空间注意有两个参数:

metaspaceSize:初始化元空间大小,控制发生GC阈值

MaxmetaspaceSize: 限制元空间大小上限,防止异常占用过多物理内存

GC JVM GC时候核心参数

-XX:NewRatio:是年老代 新生代相对的比例,比如NewRatio=2,表明年老代是新生代的2倍。老年代占了heap的2/3,新生代占了1/3–XX:SurvivorRatio: Eden区占整个堆内存的百分比,默认为8,即伊甸区占80%;–XX:NewSize: 新生代所占用大小,可以跟-XX:NewRatio同时设置–XX:MaxNewSize : 新生代最大大小.可以跟-XX:NewRatio同时设置

当-XX:NewRatio与–XX:NewSize以及–XX:MaxNewSize同时设置时,会尽量满足比例,然后不会小于–XX:NewSize同时不会大于–XX:MaxNewSize

GC 日志分析
(2)JVM的GC日志Full GC日志每个字段彻底详解

[Full GC (Ergonomics) [PSYoungGen: 984K->425K(2048K)] [ParOldGen:7129K->7129K(7168K)] 8114K->7555K(9216K), [metaspace:2613K->2613K(1056768K)], 0.1022588 secs] [Times: user=0.56 sys=0.02,real=0.10 secs]

[Full GC (Allocation Failure) [PSYoungGen: 425K->425K(2048K)][ParOldGen: 7129K->7129K(7168K)] 7555K->7555K(9216K), [metaspace:2613K->2613K(1056768K)], 0.1003696 secs] [Times: user=0.64 sys=0.03,real=0.10 secs]

[Full GC(表明是Full GC) (Ergonomics) [PSYoungGen:FullGC会导致新生代Minor GC产生]984K->425K(2048K)][ParOldGen:(老年代GC)7129K(GC前多大)->7129K(GC后,并没有降低内存占用,因为写的程序不断循环一直有引用)(7168K) (老年代总容量)] 8114K(GC前占用整个Heap空间大小)->7555K (GC后占用整个Heap空间大小) (9216K) (整个Heap大小,JVM堆的大小), [metaspace: (java6 7是permanentspace,java8改成metaspace,类相关的一些信息) 2613K->2613K(1056768K) (GC前后基本没变,空间很大)], 0.1022588 secs(GC的耗时,秒为单位)] [Times: user=0.56 sys=0.02, real=0.10 secs](用户空间耗时,内核空间耗时,真正的耗时时间)

三种基本的GC算法
    标记清除算法复制算法标记整理算法

查找对象是否可用的方法,之前是引用计数法。但是因为会出现循环引用,所以改为了根可达性算法.

根可达性算法

在Java语言中,可以作为GCRoots的对象包括下面几种:
(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

(2). 方法区中的类静态属性引用的对象。

(3). 方法区中常量引用的对象。

(4). 本地方法栈中JNI(Native方法)引用的对象。

垃圾回收器
新生代收集器线程算法优点缺点
Parallel Scavenge多线程(并行)复制算法吞吐量优先
适用在后台运行不需要太多交互的任务
有GC自适应的调节策略开关
无法与CMS收集器配合使用
ParNew多线程(并行)复制算法响应优先
适用在多CPU环境Server模式一般采用ParNew和CMS组合
多CPU和多Core的环境中高效
Stop The World
Serial收集器单线程(串行)复制算法响应优先
适用在单CPU环境Client模式下的默认的新生代收集器
无线程交互的开销,简单而高效(与其他收集器的单线程相比)
Stop The World
老年代收集器线程算法优点缺点
Serial Old收集器单线程(串行)“标记-整理”(Mark-Compact)算法响应优先
单CPU环境下Client模式,CMS的后备预案。
无线程交互的开销,简单而高效(与其他收集器的单线程相比)
Stop The World
Parallel Old收集器多线程(并行)标记-整理响应优先
吞吐量优先
适用在后台运行不需要太多交互的任务
有GC自适应的调节策略开关
-
CMS收集器多线程(并发)标记-清除响应优先
集中在互联网站或B/S系统服务、端上的应用。
并发收集、低停顿
1、对CPU资源非常敏感:收集会占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低
2、无法处理浮动垃圾
3、清理阶段新垃圾只能下次回收
4、标记-清除算法导致的空间碎片
新/老年代收集器线程算法优点
G1多线程(并发)标记-整理+复制1、面向服务端应用的垃圾收集器
2、分代回收
3、可预测的停顿 这是G1相对CMS的一大优势
4、内存布局变化:将整个Java堆划分为多个大小相等的独立区域(Region)
5、避免全堆扫描
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/777325.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号