目录
算法数据结构
hashmap原理 扩容 线程安全 1.7/1.8
Java
ConcurrentHashMap
Reader 与 Writer
Java对象头
new一个对象的过程
JUC
signal await AQS
死锁四大条件
线程池
锁升级
线程状态
ThreadLocal
JVM
排查cpu占用过高的情况?
oom怎么处理
jvm的参数
jmm
强引用 弱引用 软引用 虚引用
前序遍历
DLR--前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )
LDR--中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
LRD--后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)
排序算法 快排(分区)
1. 在数组中选一个基准数(通常为数组第一个);
2. 将数组中小于基准数的数据移到基准数左边,大于基准数的移到右边;
3. 对于基准数左、右两边的数组,不断重复以上两个过程,直到每个子集只有一个元素,即为全部有序。
hashmap的初始容量跟加载因子 - 星期天去哪玩o - 博客园
HashMap 内部结构:可以看作是数组和链表结合组成的复合结构,数组被分为一个个桶(bucket),每个桶存储有一个或多个Entry对象,每个Entry对象包含三部分key(键)、value(值),next(指向下一个Entry),通过哈希值决定了Entry对象在这个数组的寻址;哈希值相同的Entry对象(键值对),则以链表形式存储。如果链表大小超过树形转换的阈值(TREEIFY_THRESHOLD= 8),链表就会被改造为树形结构。(1.7/1.8区别)扩容:当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75。
HashMap只有容量达到阀值才发生扩容吗?大错特错!_全国花式撸管大赛总冠军-CSDN博客
另一个区别:JDK7版本及以前使用是:头插法(对比JDK8使用的是尾插法)
注:使用头插法在多线程扩容的时候可能会导致循环指向,从而在获取数据get()的时候陷入死循环,到是线程执行无法结束
头插法:最早取的会被最先放进并逐步变成最尾。
向集合中添加元素时,会使用(n - 1) & hash的计算方法来得出该元素在集合中的位置
只有当容量n为2的幂次方,n-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突,所以扩容必须2倍就是为了维持容量始终为2的幂次方。
红黑树
红黑树不断通过变色+旋转达成
avl和红黑树的区别:红黑树与平衡二叉树 - Eric-Lee - 博客园
红黑树(五)之 Java的实现 - 如果天空不死 - 博客园
1. 每一个结点要么是红色,要么是黑色。
2. 根结点是黑色的。
3. 所有叶子结点都是黑色的(实际上都是Null指针)。叶子结点不包含任何关键字信息,所有查询关键字都在非终结点上。
4. 每个红色结点的两个子节点必须是黑色的。换句话说:从每个叶子到根的所有路径上不能有两个连续的红色结点
5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
红黑树能够以O(log2(N))的时间复杂度进行搜索、插入、删除操作。此外,任何不平衡都会在3次旋转之内解决。这一点是AVL所不具备的
面试常问:什么是红黑树?_Mr.TS的博客-CSDN博客_红黑树
B+树
http://www.liuzk.com/410.html
B+树总结 - 简书
一个m阶的B树具有如下几个特征:
1.根结点至少有两个子女。
2.每个中间节点都至少包含
ceil(m / 2)
个孩子,最多有m个孩子。3.每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m。
4.所有的叶子结点都位于同一层。
5.每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。
B+树与B树的区别
- 有k个子结点的结点必然有k个关键码;
- 非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中。
- 树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。
①B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。
kmp
哈夫曼树了解吗?
哈夫曼树是带权路径长度最小的二叉树,用途是平均查找信息的代价最小。
普通二叉树的用途也普通,比较通用,就是信息存储和查找。
普通二叉树可能有的只有一个子节点,而哈夫曼树一定有两个。
hashmap和treemap的应用场景?
需要基于排序的统计功能:
由于TreeMap是基于红黑树的实现的排序Map,对于增删改查以及统计的时间复杂度都控制在O(logn)的级别上,相对于HashMap和LikedHashMap的统计操作的(最大的key,最小的key,大于某一个key的所有Entry等等)时间复杂度O(n)具有较高时间效率。
需要快速增删改查的存储功能:
相对于HashMap和LikedHashMap 这些 hash表的时间复杂度O(1)(不考虑冲突情况),TreeMap的增删改查的时间复杂度为O(logn)就显得效率较低。需要快速增删改查而且需要保证遍历和插入顺序一致的存储功能
相对于HashMap和LikedHashMap 这些 hash表的时间复杂度O(1)(不考虑冲突情况),TreeMap的增删改查的时间复杂度为O(logn)就显得效率较低。但是HashMap并不保证任何顺序性。LikedHashMap额外保证了Map的遍历顺序与put顺序一致的有序性。
内部类、静态内部类、匿名内部类的区别。
Object类下面有哪些方法
应该有(object构造方法、getClass、hashCode、equals、clone、toString、notify、notifyAll、wait、finalize(JDK9遗弃))
说一下object类的hashcode和equals方法:一般两个方法都需要重写,否则:
java中的hashcode和euqals的区别和联系_weixin_30546189的博客-CSDN博客
Object类的hashCode方法返回的hash值其实是对象的内存地址
没有覆盖hashCode方法,jdk使用默认Object的hashCode方法,返回内存地址转换后的整数
只要涉及到使用hash的建议都要重写hashcode
1
2
3
1
\. 重写了hashcode不重写equal,equal会走object类默认的比较方法,出现hashcode相同,而不equal;在使用Set的场景下会出现重复元素;
2
\. 重写了equal,不重写hashcode,相同的对象被哈希到不同的bucket中,还是没办法实现:【在使用Set的场景下会出现重复元素】
final 的几种使用情况 修饰变量,方法,类等
浅析Java中的final关键字 - Matrix海子 - 博客园
当用final修饰一个类时,表明这个类不能被继承。
使用final方法的原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在最近的Java版本中,不需要使用final方法进行这些优化了。类的private方法会隐式地被指定为final方法。
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。
String s = new String(“hello” );创建了几个对象?2 String s=new String("abc")创建了几个对象? - 云端飘 - 博客园
Integer的最大范围是多少? 2^ 32
堆的底层是什么?
list可以放进map集合里面吗?怎么实现
ArrayList的内存分配
CopyOnWriteArrayList
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
写操作需要加锁,防止并发写入时导致写入数据丢失。写操作结束之后需要把原始数组指向新的复制数组。
在写操作的同时允许读操作,提高了读操作的性能,适合读多写少。缺陷:
- 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右;
- 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。
java内存泄漏
Java内存泄漏分析和解决 - 简书
数据类型占多少字节
阿里面试题:一个 String 字符串占多少内存?_xmt1139057136的专栏-CSDN博客
null:4
Collection 与 Collections 的区别?
collection:它属于集合框架中的一个接口,并且是顶层接口。
Collectios 则是在集合框架中给我们提供的一个工具类,它提供了一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。sort方法
lambda+stream
[Java8] lambda表达式和stream流式操作 - Vagrant。 - 博客园
Lambda 表达式可以用在任何需要使用匿名方法,或是代理的地方。
Comparator
com = (x, y) -> { System.out.println("函数式接口"); return Integer.compare(x, y); }; 如果一个接口中,有且只有一个抽象的方法(Object类中的方法不包括在内),那这个接口就可以被看做是函数式接口。
Function:只有一个方法的接口 提供任意一种类型的参数,返回另外一个任意类型返回值。 R apply(T t);
Consumer:只有输入,没有返回值 提供任意一种类型的参数,返回空值。 void accept(T t);
Supplier: 没有参数,只有返回值 参数为空,得到任意一种类型的返回值。T get();
Predicate:有一个输入参数,返回值只能是布尔值! 提供任意一种类型的参数,返回boolean返回值。boolean test(T t);
fail-fast(快速失败)机制
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。fail-fast原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合如果在遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hasNext/next遍历下一个元素之前,都会检测modCount变量是否为exceptedmodCount值,是的话就返回遍历,否则抛出异常,中止遍历(CAS应用)。
intern
Java intern() 方法 | 菜鸟教程
几张图轻松理解String.intern()_程序老兵的博客-CSDN博客_intern()
intern() 方法返回字符串对象的规范化表示形式。
一个初始时为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),
则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。
和HashMap的区别/比较Java的ConcurrentHashMap、HashTable、HashMap/ConcurrentHashMap的底层实现,为什么线程安全?(synchronized + CAS)
ConcurrentHashMap面试题:ConcurrentHashMap面试详解_shlgyzl的博客-CSDN博客
关于ConcurrentHashMap几个面试题 - 知乎
ConcurrentHashMap底层实现原理(JDK1.7 & 1.8) - 简书
可以理解为它是由多个hashtable组合而成
JDK1.8以后,做了一些优化和改进,优化了锁的细粒度,还是一个大数组,对数组中每个元素进行CAS。
在ConcurrentHashMap中,大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。
扩容:数组默认大小为16
一旦链表中的元素个数超过了8个,那么可以执行数组扩容或者链表转为红黑树,这里依据的策略跟HashMap依据的策略是一致的.
当数组长度还未达到64个时,优先数组的扩容,否则选择链表转为红黑树。
ConcurrentHashMap源码深度剖析 – mikechen的互联网架构
synchronized锁住了node节点 + CAS
hashmap 1.7 1.8扩容?
面向对象的特性
封装的目的是增强安全性
继承提高了代码复用率
多态其实是在继承的基础上的,是指一个类实例(对象)的相同方法在不同情形有不同表现形式。消除类型之间的耦合关系。条件
- 继承
- 重写(子类继承父类后对父类方法进行重新定义)
- 父类引用指向子类对象
抽象是指从众多的事务中抽取出具有共同的、本质性的特征作为一个整体。是共同特质的集合形式。
抽象类和接口的区别,为什么要有抽象类?
接口用于规范,是对类的行为进行约束。抽象类用于共性,是代码复用
声明方法的存在而不去实现它的类被叫做抽象类
接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。
//通过抽象类定义 public abstract class Dog { public abstract void eat(); public abstract void sleep(); } //通过接口定义 public interface Dog { public abstract void eat(); public abstract void sleep(); }
检查性异常必须使用try catch或者throws等关键字进行处理,否则编译器会报错;非检查性异常一般是程序代码写的不够严谨而导致的问题,可以通过修改代码来规避。
nullpointerexception:调用了未经初始化的对象或者是不存在的对象
StackOverflowError:当一个应用递归调用的层次太深而导致堆栈溢出时抛出该错误。
OutOfMemoryError:当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
classnotfoundexception,arrayindexoutofboundsexception
arraylist默认容量和扩容方式,arraylist和linkedlist区别,
10 arraylist底层为啥线程不安全:添加一个元素:
1. 在 Items[Size] 的位置存放此元素;
2. 增大 Size 的值。 size没改变的话,两个线程会把元素插入在同一个位置
String StringBuilder StringBuffer区别
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象
StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象
StringBuffer 在方法上添加synchronized关键字 (重写 synchronized void ensureCapacity)
Java中哪些集合类
集合类型主要有3种:set(集)、list(列表)和map(映射)。
集合接口分为:Collection和Map,list、set实现了Collection接口
Java8中的新特性(我说的流处理)说一下流处理的常用函数
userList.stream() //过滤年龄大于22岁的人 .filter((user)->{return user.getAge() > 22;}) //返回大写的名字 .map((user)->{return user.getName().toUpperCase();}) .sorted(Comparator.reverseOrder())//倒序 .forEach(System.out::println);//打印
Java——Stream流计算(java.util.stream)_少歌的博客-CSDN博客
函数式接口和lambda - 搜索结果 - 知乎
自动装箱 int和integer比较
编码与解码
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
- UTF-16be 编码中,中文字符和英文字符都占 2 个字节。
Reader 与 Writer
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
- InputStreamReader 实现从字节流解码成字符流;
- OutputStreamWriter 实现字符流编码成为字节流。
NIO
通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
前32位标识的含义
JAVA对象 = 对象头 + 实例数据 + 对象填充
对象头 = Mark Word + 类型指针
对象头有什么 对象体有什么?
Java虚拟机(一):对象在JVM中的内存布局_SCUTJAY的博客-CSDN博客
java对象实例在jvm中的结构_z_ssyy的博客-CSDN博客
Java的ArrayList LinkedList,实现栈用什么比较好,为什么。
- ArrayList 随机查找能力非常强时间复杂度 O(1)
- LinkedList 随机查找能力相较 ArrayList 弱,其具体算法为不是从头遍历到尾,而是通过下标确定从哪个方向开始遍历
index < (size >> 1)
如果小于 mid 则从头部开始,否则从尾部倒序遍历。- ArrayList 的变长能力是通过创建更大的数组实现的
- LinkedList 的变长能力通过链表实现
ArrayList 和 LinkedList 都使用
transient
关键字修饰了容器数据,通过writeObject
和readObject
进行序列化和反序列ArrayList中elementData为什么被transient修饰 - 简书
Java已不推荐使用Stack,而是推荐使用更高效的ArrayDeque;
Deque有两个比较重要的类:ArrayDeque和LinkedList
建议 使用栈时,用ArrayDeque的push和pop方法;
使用队列时,使用ArrayDeque的add和remove方法。
数组的pop size-1即可
Java new一个对象的过程中发生了什么 - 知乎
加载并初始化类和创建对象
加载并初始化类:加载、验证、准备、解析、初始化
加载:加载此类的字节流到jvm的方法区,然后转换为一个对应的java.lang.class的实例
验证:语义验证。
准备:为类中的所有静态变量分配内存空间和初始值
解析:将常量池中的符号引用转为直接引用
初始化(先父后子)1,为静态变量赋值 2,执行static代码块
----执行main方法:压入栈中-----
1、new Child():在堆区分配对象需要的内存(方法区--->堆)
分配的内存包括本类和父类的所有实例变量,但不包括何静态变量
2、string name:对所有实例变量赋默认值(方法区--->堆)
将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值
3、执行实例初始化代码
初始化顺序是先初始化父类再初始化子类,初始化时先执行实例代码块然后是构造方法
4、c只是个名字,是个引用,放在栈中(栈):如果有类似于Child c = new Child()形式的c引用的话,在栈区定义Child类型引用变量c,然后将堆区对象的地址赋值给它
需要注意的是,每个子类对象持有父类对象的引用,可在内部通过super关键字来调用父类对象,但在外部不可访问
方法区:方法区 和 堆 的说明(学习笔记) - 码农教程
互斥锁、读写锁、递归锁。
sychronize修饰类名、方法的区别。
在java中如何解决死锁问题
Java 实例 – 死锁及解决方法 | 菜鸟教程
一种是用synchronized,一种是用Lock显式锁实现。
如何保证线程同步。
java线程同步的实现方式_SlagSea-CSDN博客_java线程同步
多线程的优缺点?
缺点:1.数据竞争问题 2.死锁 3.当线程过多时,会消耗大量内存。
优点:保证应用程序的响应性能,即良好的用户体验。
多线程的应用场景?
多线程的应用场景_酷酷的码农小哥的博客-CSDN博客_哪些软件用到了多线程
tomcat内部采用多线程,上百个客户端访问同一个WEB应用,tomcat接入后就是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用我们的servlet程序,比如doGet或者dpPost方法
线程越多越好吗?会有什么问题?
线程非常多而且切换频繁(处理IO密集任务),这个时间损耗是非常可观的。(上下文切换)
怎么设置线程池最优线程数?最优的线程数?
合理的设置线程池的线程数需要针对不同的任务类型而定,任务类型可以分为cpu密集型、IO密集型和混合型。
1)cpu密集型:cpu密集型指的是线程处理任务时,cpu参与计算的时间比较多,这种情况下,如果设置的线程数过多,会增加上下文的切换次数,带来额外的开销。
线程数的设定公式是:线程数=(cpu核心数+1)。
2)IO密集型:IO密集型是指在处理任务时,IO过程所占用的时间较多,在这种情况下,线程数的计算方法可以分为两种:
方法一:线程数=cpu核心数*2,cpu所占用时间不多,可让cpu在等待IO的时候去处理其他任务,充分利用cpu。
方法二:线程等待时间比例越多,需要更多的线程,而线程cpu所占时间越多,则需要更少线程数。
线程数=((线程等待时间+线程cpu时间)/线程cpu时间)*cpu核心数。intel给他的x86设计了逻辑线程=2*物理核心数,所以会x2
3)混合型:对于混合型,可以将任务划分成cpu密集型任务与IO密集型任务,分别针对这两种任务使用不同的线程池去处理。
notify 和 notifyAll 的区别
你真的懂wait、notify和notifyAll吗 - 简书
对于Entry Set:如果线程A已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,它只能进入Entry Set,并且处于线程的BLOCKED状态。
对于Wait Set:如果线程A调用了wait()方法,那么线程A会释放该对象的锁,进入到Wait Set,并且处于线程的WAITING状态。
wait notify notifyAll await signal signalAll 的理解及示例 - sanri1993 - 博客园
除了唤醒一个和唤醒一堆啊,但它两真正的区别是 notifyAll 调用后,会把所有在 Wait Set 中的线程状态变成 runnable 状态,然后这些线程再去竞争锁,获取到锁的线程为 Run 状态,没有获取到锁的线程进入 Entry Set 集合中变成 Block 状态,它们只需要等到上个线程执行完或者 wait 就可以再次竞争锁而无需 notify ; 而 notify 方法只是照规则唤醒 Wait Set 中的某一个线程,其它的线程还是在 Wait Set 中。
文章中说到的为什么 wait 要写在 for 循环中是因为 wait 是释放了锁,然后阻塞,等到下次唤醒的时候,在多个生产者多个消费者的情况下,有可能是被 “同类” 唤醒的,所以需要再去检查下状态是否正确。
就极有可能出现唤醒生产者的是另一个生产者或者唤醒消费者的是另一个消费者,这样的情况下用if就必然会现类似过度生产或者过度消费的情况了,典型如IndexOutOfBoundsException的异常。所以所有的java书籍都会建议开发者永远都要把wait()放到循环语句里面。
Java锁Synchronized之阻塞线程_悟空tiger_新浪博客
condition营造了一个单向的等待队列。(同步队列十个双向队列)
await里面是个自旋锁,直到获取lock才结束循环
signal/signalAll实现原理:调用condition的signal或者signalAll方法可以将等待队列中等待时间最长的节点移动到同步队列中,使得该节点能够有机会获得lock。按照等待队列是先进先出(FIFO)的,所以等待队列的头节点必然会是等待时间最长的节点,也就是每次调用condition的signal方法是将头节点移动到同步队列中。
wait(),notify() 与 await(), signal(), signalAll() 的区别_萧萧的专栏-CSDN博客_await()
详解Condition的await和signal等待/通知机制_你听___-CSDN博客
如何抢占?队列中release逻辑是一样的,但是头部唤醒有上下文切换,这时候会让给新进来的线程。一张图读懂非公平锁与公平锁 - 简书
AQS: 核心数据结构:双向链表 + state(锁状态) 底层操作:CAS
state 0 or 1; cas成功就加锁,不成功放入等待队列; 等到上个线程唤醒。
Java并发面试问题之谈谈你对AQS的理解?_哔哩哔哩_bilibili
ReentrantLock/Semaphore/CountDownLatch都使用了aqs
AQS的原理及应用_hellozhxy的博客-CSDN博客
synchronized lock区别
- Synchronized 内置的Java关键字, Lock 是一个Java类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
- Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
- Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以自己设置);
- Synchronized 适合少量代码的同步问题,lock适合锁大量同步的代码
volatile synchronized原理
深入理解Volatile关键字及其实现原理_泽宇的博客-CSDN博客_volatile关键字原理
Volatile是java虚拟机提供 轻量级的同步机制,volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
如果一个变量被volatile所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile在并发编程中,其值在多个缓存中是可见的。
写操作时,通过在写操作指令后加入一条store屏障指令,让本地内存中变量的值能够刷新到主内存中
读操作时,通过在读操作前加入一条load屏障指令,及时读取到变量在主内存的值
内存屏障是一种cpu的指令,用于控制特定条件下的重排序和内存可见性问题,
1、保证可见性 一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
2、不保证原子性
线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。
为了保证原子性,需要通过字节码指令monitorenter和monitorexit,但是volatile和这两个指令之间是没有任何关系的。
3、禁止指令重排序
- 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
- 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
- 循环等待条件::若干进程之间形成一种头尾相接的循环等待资源关系。
以上给出了导致死锁的四个必要条件,只要系统发生死锁则以上四个条件至少有一个成立。事实上循环等待的成立蕴含了前三个条件的成立,似乎没有必要列出然而考虑这些条件对死锁的预防是有利的,因为可以通过破坏四个条件中的任何一个来预防死锁的发生。
如何避免死锁
死锁,死锁的四个必要条件以及处理策略_wenlijunliujuan的专栏-CSDN博客_死锁的四个必要条件
银行家算法(预防死锁的方法)
Java 线程池原理和队列详解_薛瑄的博客-CSDN博客_线程池 队列
四大方法
Executors.newSingleThreadExecutor() //单个线程 newFixedThreadPool(int nThreads) //创建一个固定的线程池的大小 newCachedThreadPool() //缓存池,可伸缩的, 遇强则强,遇弱则弱 newScheduledThreadPool(int corePoolSize) //定时任务
定时线程:Executors创建可定时的线程newScheduledThreadPool_4913168的技术博客_51CTO博客程序等待10秒再执行
7大参数 理解ThreadPoolExecutor线程池的corePoolSize、maximumPoolSize和poolSize - FrankYou - 博客园
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
int maximumPoolSize,//最大线程池大小 几核写几
long keepAliveTime,//超时没人调用就会释放
TimeUnit unit,//超时单位
BlockingQueue
workQueue,//阻塞队列 (大小) ThreadFactory threadFactory,//线程工厂,创建线程的,一般不动
RejectedExecutionHandler handler//拒绝策略) java多线程-ThreadPoolExecutor的拒绝策略 - 简书
4种拒绝策略
- new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异 常
- new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!这个策略的缺点就是可能会阻塞主线程。
- new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
- new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和早的竞争,也不会 抛出异常!
ExecutorService threadPool = new ThreadPoolExecutor( 2,//核心大小,2个柜台 Runtime.getRuntime().availableProcessors(),//最多5个柜台 3,//三秒钟没人使用新开辟的,自动关闭 TimeUnit.SECONDS, new LinkedBlockingDeque<>(3),//候客区 Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试去和最早的竞争,也不会抛出异常!
可重入锁(结合了AQS谈原理)可重入锁的单位,或者说以什么为单位
aqs AQS详解(面试)_mulinsen77的博客-CSDN博客_aqs
深入剖析ReentrantLock公平锁与非公平锁源码实现_舒哥的blog-CSDN博客_公平锁 hasQueuedPredecessors()
REDIS6_分布式存储极致性能目录_所得皆惊喜-CSDN博客
Wait()和Sleep()区别?
- 来自不同的类:wait => Object 所有对象都有 sleep => Thread
- 关于锁的释放:wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
- 使用的范围是不同的:wait必须在同步在代码块中,sleep 可以再任何地方睡
- 是否需要捕获异常:wait 不需要捕获异常,sleep 必须要捕获异常
Java的锁了解哪些( juc下的和偏向锁轻量级锁重量级锁)
悲观锁、乐观锁、可重入锁、中断锁、读锁、写锁、synchronized锁升级(偏向锁、轻量级锁、重量级锁)
数组如何做到保证数据的正确性呢?
- vector替代list(并发效率低)
- 用Collections.synchronizedList(list)包装list(有synchronized修饰的方法效率低)
- 使用juc里的CopyOnWriteArrayList替代list(推荐使用)写入时复制,读写分离的思想。底层通过复制数组的方式来实现
set:Collections.synchronizedSet CopyOnWriteArraySet
map:hashtable,synchronized
ConcurrentHashMap<>();HashMap与ConcurrentHashMap的区别_XF的专栏-CSDN博客_hashmap和concurrenthashmap的区别
ConcurrentHashMap实现原理及源码分析 - dreamcatcher-cx - 博客园
CAS知道吗,介绍一下
OpenJDK系列(三):JVM对CAS的设计与实现 - 简书
Java层无法直接调用CPU指令,必须借助JNI,这里对CAS的调用在Java层就体现在sun.misc.Unsafe类上,UnSafe类中定义了很多Native方法。sun.misc.Unsafe中Native方法的调用,最终都会通过JNI调用到unsafe.cpp中,而unsafe.cpp中的实现本质都是调用CPU的cmpxchg指令
例如AtomicInteger本质就是CAS在Java层的应用.
不支持Compare-and-Swap的平台上,JVM将使用自旋锁来代替
变量valueOffset,表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
JUC(4):JMM+Volatile+单例模式+CAS+AtomicReference+锁和死锁_21秋招拒做分母的博客-CSDN博客
乐观锁,自带原子性
比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作!如果不是就一直循环!
缺点:自旋循环会消耗CPU资源,一次性只能保证一个共享变量的原子性,ABA问题(版本号)
原子类AtomicInteger类ABA问题及解决方案
1、ABA问题是怎么产生的?
当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样我们就无法正确判断这个变量是否已被修改过。
2、ABA问题的解决方案:
AtomicStampedReference:是一个带有时间戳的对象引用,在每次修改后,不仅会设置新值还会记录更改的时间。
AtomicInteger
incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。
在高并发情况下,LongAdder(累加器)比AtomicLong原子操作效率更高,LongAdder累加器是java8新加入的.
在高度并发竞争情形下,AtomicLong每次进行add都需要flush和refresh(这一块涉及到java内存模型中的工作内存和主内存的,所有变量操作只能在工作内存中进行,然后写回主内存,其它线程再次读取新值),每次add()都需要同步,在高并发时会有比较多冲突,比较耗时导致效率低;而LongAdder中每个线程会维护自己的一个计数器,在最后执行LongAdder.sum()方法时候才需要同步,把所有计数器全部加起来,不需要flush和refresh操作。
CAS操作和锁哪个效率更高,在任何情况下都是CAS更快吗?
当并发量比较大时,sychronized的性能会比CAS快,当然当查询量很少是,可以直接执行CAS对应的指令,减少了上下文的缺陷,大大提升了工作效率。
当线程进行CAS算法更新的时候,如果发现不是期望的数据,那么他会进入循环进行更新,或者时间片用完后切换线程。而在本次时间片内,存在本线程多次CAS更新后更新成功,从而减少了线程的切换。
为什么说无锁并发、CAS能减少上下文切换?_dsdj-CSDN博客
synchonized 同步属于一种悲观的并发策略。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。CAS 会快很多。
- 使用CAS在线程冲突严重的情况下,会大幅降低程序性能,CAS只适用于线程冲突较少的情况下使用
- synchronized在jdk1.6之后,依靠Lock-Free,基本思路是自旋后阻塞,在线程冲突较少的情况下,可以获得和CAS类似的性能,而线程冲突严重的情况下,性能远高于CAS。(这时 synchronized 全是偏向锁的形态,这会比 CAS 快得多?)
浅谈偏向锁、轻量级锁、重量级锁 - 简书
偏向锁、轻量级锁、重量级锁、自旋锁原理讲解 - 云+社区 - 腾讯云
偏向锁
通俗的讲,偏向锁就是在运行过程中,对象的锁偏向某个线程。即在开启偏向锁机制的情况下,某个线程获得锁,当该线程下次再想要获得锁时,不需要再获得锁(即忽略synchronized关键词),直接就可以执行同步代码,比较适合竞争较少的情况。因此直接把线程id往markword里一放就行了。在java里叫线程id,c++叫线程指针。
轻量级锁:
撤销偏向锁(或者原来没有),两个线程再竞争锁,谁能把自己的lock record的指针放在锁markword上,谁就上锁,另一个自旋,知道拿到锁的线程退出锁状态。
重量级锁
内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。
源码看monitorenter
偏向锁与hashcode能共存吗?_Saintyyu的博客-CSDN博客
无竞争:偏向锁——轻度竞争:轻量级锁,另一个自旋(大量的话消耗资源)——自旋10次:重量级锁(等待队列)
任何情况下都可以用多线程提高效率吗?什么情况下就不适合用多线程了?
是先上升,然后再下降,多线程的目的是提高资源使用率,在一个线程如果不需要用到cpu的时候,比如io操作时,可以切换给别的线程,这样就让cpu资源最大化,但是如果开过多的线程,会造成线程之间切换太过频繁,消耗cpu资源,导致效率降低。
多线程start和run方法的区别
t.start(); 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体中的代码执行完毕而直接继续执行后续的代码。
它启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里的run()方法 称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
t.run(); 该行代码相当于是使用t这个类中的run方法而已.
1. 新建(NEW):新创建了一个线程对象。
2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
线程池的五种状态:线程及线程池的五种状态_Lucifer丶晓风的博客-CSDN博客_线程池状态
线程局部变量
彻底理解ThreadLocal - 何必等明天 - 博客园!!!!!!!!!!
ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。ThreadLocal 是 Java 里一种特殊变量,它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量。
那么实现机制是如何的呢?ThreadLocal详解 - 神一样的存在 - 博客园
https://segmentfault.com/a/1190000011264294
例子java并发:线程同步机制之ThreadLocal - 时空穿越者 - 博客园
本质就是每个线程都维护了一个map(ThreadLocalMap是ThreadLocal类的静态内部类),而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。
ThreadLocal是空间换时间,而锁是时间换空间。
深挖ThreadLocal_逆风奔跑。。不要去考虑什么天赋异禀,一切都来自经历和渴望。-CSDN博客_threadlocal
ThreadLocal和线程同步机制相比有什么优势呢?
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
如果我们使用线程池,下次拿到这个thread,不remove会保留这个值。
entry:弱引用:如果当前entry只被threadlocal弱引用,而没有其他的强引用引用,那这个entry会在下个gc被清理。而强引用即使线程销毁,也不会被gc。
另外,ThreadLocal里的entry是个数组。用hashmap的话,不管是链表还是红黑树,上一个entry到下一个entry是强引用,不会被gc。
实心箭头表示强引用,空心箭头表示弱引用ThreadLocal 内存泄漏的原因
从上图中可以看出,threadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。
但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
ThreadLocal正确的使用方法
- 每次使用完ThreadLocal都调用它的remove()方法清除数据.调用remove()方法最佳时机是线程运行结束之前的finally代码块中调用,这样能完全避免操作不当导致的内存泄漏,比惰性删除有效。
- 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。
ThreadLocal的内存泄露?什么原因?如何避免? - 知乎
ThreadLocal 有什么缺陷?如果线程池的线程使用ThreadLocal 会有什么问题?_熊仙森滴Blog-CSDN博客_threadlocal 问题
超过核心线程数的那些线程什么时候回收
设置线程池线程最大空闲时间,超出这个时间,对线程进行回收
1. 当提交一个任务时,如果线程数没有达到coreSize,那么就会新建一个线程,并绑定该任务,直到数量到达coreSize前都不会重用之前的线程
2.到达后,提交的任务都会放到一个等待队列中进行等待,线程池中的线程会使用take()阻塞的从等待队列拿任务
3.当队列有界,并且线程不能及时取走队列中的任务,等待队列有可能会满,那么线程池将会创建临时线程来执行任务
4.临时线程 通过poll(keepAliveTime,timeUnit)来执行任务,如果到了keepAliveTime还取不到,那么会被回收掉,临时线程的数量不能大于
maxPoolSize - coreSize
5.当线程数到达maxSize后,将会执行拒绝策略RejectedExecutionHandler,包括抛异常,静默拒绝,抛弃最old任务,使用原远程执行等策略知道了原理你大概就知道线程被如何管理了
当一个task执行完,如果线程数小于coreSize,那么这个线程就会一直存在线程池中,
如果是临时线程,在等待keepAliveTime后,将会被回收掉
在什么情况下,Java对象不需要垃圾回收也能回收掉?
JVM逃逸分析中的栈上分配优化
Java虚拟机内存区域
JVM五大内存区域介绍_keybersan的博客-CSDN博客_jvm内存区域
栈描述的是Java方法执行的内存模型。
每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
Java虚拟机栈可能出现两种类型的异常:
- 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
- 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常(堆new对象也可能溢出,方法区和运行时常量池溢出,本机直接内存溢出)
虚拟机栈中存放了各种基本数据类型和对象引用。比如int i。这时候机会在栈中分配一个int类型的内存给i,并初始化为零值。本地方法栈和虚拟机栈所发挥的作用是非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
堆 (Java Heap)是虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例 ,几乎所有的对象实例都在这里分配内存,
方法区 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池 是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。
程序计数器和虚拟机栈都是线程“私有”的内存。
什么时候会用到堆
浅谈Java中的栈和堆 - 夏天里的Jasmine - 博客园
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。
浅谈Java中的栈和堆 - 夏天里的Jasmine - 博客园
递归函数一般在哪个区域上操作
StackOverFlowError
线程池参数
Java线程池七个参数详解_IT小跟班-CSDN博客_线程池七大参数
银行例子
类加载机制
Java类加载机制_清风博客-CSDN博客_类加载机制
加载:ClassLoader通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个class对象。
验证:目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身的安全,主要包括四种验证:文件格式的验证,元数据的验证,字节码验证,符号引用验证。
准备:为类变量(static修饰的字段变量)分配内存并且设置该类变量的初始值,(如static int i = 5 这里只是将 i 赋值为0,在初始化的阶段再把 i 赋值为5),这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。
解析:这里主要的任务是把常量池中的符号引用替换成直接引用
初始化:如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值,成员变量也将被初始化)Java源代码.java文件通过编译成字节码.class文件后,需要被加载到Java虚拟机的内存空间中使用,这个过程就是类加载。类通常是按需加载的。
四种类加载器:BootstrapClassLoader(启动类加载器)ExtClassLoader (标准扩展类加载器)AppClassLoader(系统类加载器)CustomClassLoader(用户自定义类加载器)
类什么时候被初始化?
new一个对象,对该静态变量赋值,调用类的静态方法,反射(Class.forName("com.lyj.load"))
类的初始化步骤
如果这个类还没有被加载和链接,那先进行加载和链接;假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口);
沙箱的基本组件
字节码校验器、类裝载器、安全软件包、安全管理器、存取控制器
Jprofiler
JVM的CPU资源占用过高问题的排查 - rsapaper_ing - 博客园
通过top命令查看当前CPU及内存情况,找到command为java 且cpu%过高的进程
获得pid,通过
top -H -p86786
查看有问题的线程通过jstack命令获取占用资源异常的线程栈,可暂时保存到一个log文件中查看
发现线程阻塞。
如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。
如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError。https://blog.csdn.net/qq_42914528/article/details/10567185
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
jstat命令查看jvm的GC情况 (以Linux为例) - fcyh - 博客园
jps:查看所有jvm进程,包括进程id,启动路径等等;
jstack:观察jvm中当前所有线程的运行情况和线程当前状态。
jstat:利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对进程的classloader,compiler,gc情况;特别的,可以用来监视VM内存内的各种堆和非堆的大小及其内存使用量,以及加载类的数量。
jmap:监视进程运行中的jvm物理内存的占用情况,该进程内存内,所有对象的情况,例如产生了哪些对象,对象数量;系统崩溃了?jmap 可以从core文件或进程中获得内存的具体匹配情况,包括Heap size, Perm size等等
jinfo:输出并修改运行时的java进程的运行参数。观察进程运行环境参数,包括Java System属性和JVM命令行参数。系统崩溃了?jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息。
垃圾回收器 (新生代老年代)
Eden, form, to,end
伊甸园满了就触发轻GC,经过轻GC存活下来的就到了幸存者区,幸存者区满之后意味着新生区也满了,则触发重GC,经过重GC之后存活下来的就到了养老区。
jdk1.8之后:无永久代,改为元空间。常量池在方法区里,方法区在元空间里。
元空间:逻辑上存在,物理上不存 在 (因为存储在本地磁盘内) 所以最后并不算在JVM虚拟机内存中
gc算法
GC的算法有哪些?
标记清除法,
标记整理,多了压缩
复制算法,
- 好处: 没有内存的碎片~
- 坏处: 浪费了内存空间~ :多了一半空间永远是空to。假设对象100%存活(极端情况),成本就很高了
引用计数器
(垃圾回收)
JVM 发生 OOM 的 8 种原因、及解决办法_良月柒-CSDN博客
常见OOM 原因及解决方案_Jack 架构师之路-CSDN博客_oom异常的原因和解决方法
1.无法在 Java 堆中分配对象: -Xmx 增加堆大小,修复内存泄漏
2.GC 开销超过限制:使用 -Xmx 增加堆大小,使用 -XX:-UseGCOverheadLimit 取消 GC 开销限制
3.无法新建本机线程:内存不足,为机器分配更多的内存,
4.Metaspace耗尽:-XX: MaxMetaSpaceSize 增加 metaspace 大小;取消 -XX: maxmetsspacedize;减小 Java 堆大小,为 MetaSpace 提供更多的可用空间
java heap:
原因:请求创建一个超大对象,通常是一个大数组。或者内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。
方案:
针对大部分情况,通常只需要通过
-Xmx
参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:1、如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。
2、如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
3、如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。
将启动参数 Xmx调小,同时加上 -XX:+HeapDumpOnOutOfMemoryError参数使得再 OOM 后自动生成 dump 文件,再用分析工具查看对象占用情况,快速解决问题。
有,然后围绕着CMS+Parnew 的减少老年代GC展开了一系列的回答,
追问,那你们当时线上的配置是多少,多少G内存,然后你当时多少并发量,你通过把jvm的什么参数调整到多少,然后实现了老年代gc频率从多少到多少的转变?
这个直接就没防出去,然后他降低了难度,问我怎么预估参数,我说用操作的数据结构的大小乘个参数
追问这个参数是根据什么算的?
Par New和CMS垃圾回收器,多线程工作,一般是现在生产上的标配。
Par New用于新生代,它会给自己设置与CPU核心数相同的垃圾回收线程
CMS用于老年代,标记清理算法。CMS采取的是垃圾回收线程和系统工作线程尽量同时执行的模式来处理垃圾回收的,尽可能多的缩短Stop the World的时间。
会产生碎片
垃圾收集器之 ParNew 与 CMS_dying 搁浅-CSDN博客
探索ParNew和CMS垃圾回收器_lm970585581的博客-CSDN博客
4.ParNew和CMS的工作原理 - 简书
参数设置
1.使用CMS回收器,-XX:+UseConcMarkSweepGC
2.设置老年代内存占用比例,达到就进行垃圾回收。-XX:CMSInitiatingOccupancyFraction=92%,默认92%
3.设置几个Full GC后进行碎片整理,-XX:+UseCMSCompactAtFullCollection=0,默认是0,代表每次Full GC 后都会进行碎片整理,此时会Stop The World。JVM 调优实战--垃圾收集器(串行、ParNew并行、ParallelNew并行、CMS、G1)_学亮编程手记-CSDN博客
面试官问我什么是JMM - 知乎
原子性,可见性,有序性
Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。
从jmm来理解,主存是堆加方法区,工作内存是虚拟机栈。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
- 硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。
- 内存屏障有两个作用:
- 阻止屏障两侧的指令重排序;
- 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
- 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载新数据;
- 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。
队列里面的任务怎么被执行
Java四种引用包括强引用,软引用,弱引用,虚引用 - 嘿boom - 博客园