共享内存:线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信
消息传递:线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify()
notify方法和notifyAll方法的区别当调用wait方法后,线程会被放到对象内部的等待池中,在等待池中的线程不会去竞争CPU,只有调用Notify或者NotifyAll才会从等待池中,放入锁池中,等待对象锁的释放从而竞争CPU以执行。
notify从等待池中随机选一个线程放入锁池
notifyAll把所有等待池全放入锁池
Java中sleep方法和wait方法的区别sleep
Thread类方法
让出CPU,不改变锁状态
任意位置执行
wait
Object类方法
让出CPU,释放当前占用的锁
只能在synchronized中的中使用
ThreadLocal作用
主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据
实现
数据结构
Map
Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal类实现的定制化的 HashMap,默认情况下这两个变量都是null
最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值
问题
内存泄露
原因
ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用
如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收
解决方案
使用完 ThreadLocal方法后 最好手动调用remove()方法
安全发布对象概念
发布对象:使一个对象能被当前范围之外的代码所使用
对象溢出:一种错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见
如何安全发布对象
在静态初始化函数中初始化一个对象引用
将对象的引用保存到volatile类型域或者AtomicReference对象中
将对象的引用保存到某个正确构造对象的final类型域中
将对象的引用保存到一个由锁保护的域中
final语义用 final 修饰的类不可以被继承
用 final 修饰的方法不可以被覆写
用 final 修饰的属性一旦初始化以后不可以被修改
如果一个对象完全初始化以后,一个线程持有该对象的引用,那么这个线程一定可以看到正确初始化的 final 属性的值(也就是不加final可能看到的是默认的0值)
final 属性的 freeze 操作发生于被调用的构造方法结束的时候
final 属性可以通过反射和其他方法来改变
JVM 如何分析jvm线程堆栈 # 查运行中的java应用 root@64b47b31317a:/# jps -ml 1 /app.jar --spring.profiles.active=pro 116 sun.tools.jps.Jps -ml # 根据第一列的PID找出 load比较高的应用PID top -p <pids> root@64b47b31317a:/# top -p 1 # dump线程堆栈信息 jstack <pid> root@64b47b31317a:/# jstack 1 "lettuce-eventExecutorLoop-1-3" #33 daemon prio=5 os_prio=0 tid=0x00007fe394367000 nid=0x24 waiting on condition [0x00007fe376bd3000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) 省略若干 # 查看java.lang.Thread.State状态 四种引用类型强引用
最普遍的引用:Object obj = new Object();
宁可抛出OOM异常也不会回收有强引用的对象
通过将对象设置为null,使其被回收(栈pop中用到)
软引用
对象处在有用但是非必须的状态
只有内存空间不足才回收
可以用来实现高速缓存
弱引用
对象处在有用但是非必须的状态,比软引用更没用一点
GC时会被回收
适用于偶尔使用且不希望影响垃圾收集的对象
虚引用
不会决定对象生命周期
任何时候会被回收
用于跟踪GC活动,起哨兵作用
必须与引用队列ReferenceQueue联合使用
常用的垃圾收集器年轻代
Serial收集器(-XX:+UseSerialGC,复制算法)
单线程,进行回收时,必须停止所有工作线程
简单高效,Client模式下默认的年轻代收集器
ParNew收集器(-XX:+UseParNewGC,复制算法)
多线程并行,其他类似Serial收集器
多核下优势
Parallel收集器(-XX:+UseParallelGC,复制算法)
多线程并行,更关注性能,吞吐量,而不是GC停顿
多核下优势,Server模式下默认的年轻代收集器
老年代
Serial Old收集器(-XX:+UseSerialOldGC,标记-整理算法)
单线程,进行回收时,必须停止所有工作线程
简单高效,Client模式下默认的老年代收集器
Parallel Old收集器(-XX:+UseParallelOldGC,标记-整理算法)
多线程并行,其他类似Serial Old收集器
多核下优势
CMS收集器(-XX:+UseConcMarkSweepGC,标记-清除算法)
多线程并发
通用
G1收集器(-XX:+UseG1GC,复制+标记-整理算法)
分代收集
空间整合
可预测的停顿
多线程并发
将Heap堆内存划分成多个大小相等的Region
年轻代和老年代不再物理隔离
CMS收集器执行步骤初始阶段:stop-the-world
并发标记:并发追溯标记,程序不停顿
并发预清理:查找并发标记阶段从新生代晋升老年代的对象
重新标记:stop-the-world,扫描CMS堆中的剩余对象
并发清理:清理垃圾对象,程序不停顿
并发重置:重置CMS收集器的数据结构,程序不停顿
JVM常用调优参数-Xss: 规定每个线程虚拟机栈的大小
-Xms: 堆的初始值
-Xmx: 堆能扩展的最大值
-XX:SurvivorRatio:Eden区和其中一个Survivor区的比值
-XX:NewRatio:老年代和新生代比值
-XX:MaxTenuringThreshold:对象从年轻代进入老年代经历过GC次数的阈值
JVM常用的垃圾回收算法标记-清除算法
缺点:容易产生碎片化
复制算法(适用与对象存活率低的场景)
优点:不会碎片化
缺点:浪费50%空间
标记-整理算法(适用于对象存活率高场景)
优点:标记清除的加强版,不会碎片化
缺点:性能差一点
分代收集算法
Minor GC:使用复制算法处理年轻代(eden区8/10,from survivor区1/10,to survivor区1/10),默认经历15次Minor GC仍然存活就进老年代,执行条件如下:
年轻代满了执行
Full GC触发时也会执行
Full GC:主要使用标记-整理算法处理老年代(老年代默认是年轻代的两倍),执行条件如下:
老年代满了执行
使用CMS垃圾收集器时候,出现promotion failed或concurrent mode failed时候也会执行
调用System.gc()后的某个时刻
使用RMI时,一般每小时执行一次GC
何时真正开始Full GC(stop-the-world)程序到达安全点,安全点是对象引用关系不会变化的点,例如方法调用,循环跳转,异常跳转等
GC如何标记垃圾对象没有被任何其他对象引用的对象被视为垃圾。
问1:JavaGC中如何判断对象是否被引用
答1:GC中主要有两种引用判断方法
引用计数法
优点:执行效率高
缺点:无法检测出循环引用情况,导致内存泄漏
可达性分析法(主流)
问2:可达性分析中,哪些对象可作为GC root
答2:
虚拟机栈中变量表引用的对象
方法区中常量引用的对象
方法区中类静态属性引用的对象
本地方法栈中JNI引用的对象
活跃线程的引用对象
String.intern()的用法
作用
直接使用双引号声明出来的String对象会直接存储在常量池中。