The All-in-One Note (14)

共享内存:线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信

消息传递:线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在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对象会直接存储在常量池中。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpyysp.html