对任意单个volatile变量的读写具有原子性
对类似volatile++这种复合操作布局有原子性
i++ 实际分成两步:
复合两步一起操作不具有原子性
工作内存与主内存颜值现象导致的可见性问题
可以使用synchronized或者volatile关键解决,他们都可以使一个线程修改的变量立即对其他线程可见
对于指令重排导致的可见性问题和有序性问题
可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化
单例模式DCL机制分析
CAS(CompareAndfSwap)
比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止
CAS应用
CAS有三个操作数,内存值V,旧得预期值,要修改的更新值B。
当且仅当预期值A和主内存值V相同时,将内存值V修改为B,否则什么都不做
synchronized关键字保证一致性并发性下降,CAS没有加锁使用natice+CAS思想既保证了一致性也保证了并发性
循环时间长开销大
(比较旧的预期值和主内存最新的值是一个循环比较过程,如果长时间比较不成功,CPU开销大)
只能保证一个共享变量的原子操作
对于多个变量操作时,循环CAS无法保证操作的原子性,这个时候就可以用锁来保证原子性
引出来ABA问题
ABA问题的产生:
CAS算法实现一个重要的前提需要提取出主内存中的某个时刻的数据值并在当下时刻工作内存中的值比较替换,那么在这个时间差类会导致数据的变化。
比如:一个线程T1从主内存位置V中取出A,这时候另一个线程T2从主内存中读取A,并且线程T2进行了一些操作讲述职变成B后更显到主内存中,然后线程T2有将V位置的数据变成A,这个时候线程T1进行CAS操作发现内存中仍然是A,然后线程T1操作成功。
尽管线程T1操作成功,但并不代表这个过程就没有问题,相当于T1遗漏掉T2更新的一个中间变量值的过程
AtomicReference<User> atomicReference = new AtomicReference<User>();
//ArrayList 并发安全是示例 java.util.ConcurrentModificationException
public static void main(String[] args) {
final List<String> list = new ArrayList();
for (int i = 1; i < 100; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}
});
thread.setName("线程" + i);
thread.start();
}
}
//并发问题原因: 并发争抢修改导致,一个线程正在读,另一个进行写 导致数据不一致
//解决
// 1.List list = new Vector() 使用加锁线程安全的类
// 2.List list = Collections.synchronizedList(new ArrayList()); 包一层安全
// 3.List list = new CopyOnWriteArrayList();
HashSet 底层是 HashMap
指多个线程按照申请锁的顺序来获取锁,类似排队排序
指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,
在高并发的情况下,有可能造成优先级反转或者饥饿等待现象
synchronized:是非公平锁
指的是同一线程外层函数获取锁之后,内层递归函数仍然能获取该锁的代码
在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
即:线程可以进入任何一个已经拥有的锁所同步这的代码块
synchronized 、ReentrantLock都是可重入锁,可重入锁最大的作用就是避免死锁
多个lock.lock() 只要unlock() 匹配对应上即可
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
//自旋原理代码示例
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//自旋锁示例
public class SpinLock {
AtomicReference<Thread> atomicReference = new AtomicReference<Thread>();
public void myLock() {
Thread thread = new Thread();
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnLock() {
Thread thread = new Thread();
while (!atomicReference.compareAndSet(thread, null)) {
}
}
}
读锁的共享锁可以保证并发读是非常高效的。
读写,写读,写写的过程是互斥的
让一些线程阻塞知道另外一些线程完成一系列的操作后才被唤醒
CountDownLatch主要有两个方法,当一个或多个线程代用await方法时调用此方法的线程会被阻塞。其他线程调用countDown()方法,会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为零时,因调用waait方法被阻塞的线程被唤醒继续执行
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i <= 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "over");
countDownLatch.countDown();
}
}, String.valueOf(i));
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"over");
//等待前六个线程结束后 执行main线程
}
CyclicBarrier字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是让一组线程达到一个屏障(也可叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法
public static void main(String[] args) throws InterruptedException {
final CyclicBarrier cyclicBarrier = new CyclicBarrier(7, new Runnable() {
@Override
public void run() {
System.out.println("main 主线程执行");
}
});
for (int i = 0; i <= 7; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "执行");
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}, String.valueOf(i));
}
}
信号量主要两个目的:
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(3); //设置三个资源
for (int i = 0; i <= 6; i++) { //6个并发量
new Thread(new Runnable() {
@Override
public void run() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//占有资源
semaphore.acquire();
System.out.println("抢到");
TimeUnit.SECONDS.sleep(3);
System.out.println("停3秒离开");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放资源
semaphore.release();
}
}
});
}
}, String.valueOf(i));
}
}
ArrayBlockingQueue:数组结构组成的有界阻塞队列
LinkedBlockingQueue:链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列
每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然
LinkedTransferQueue:链表结构组成的无界阻塞队列
LinkedBlockingDeque:链表结构组成的双向阻塞队列
将if替换为while,解决线程虚假唤醒的问题
package com.yjn.test.thread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @ProjectName: com.yjn.test.thread
* @Description:
* @Author: yuanjunnan
* @CreateDate: 21/05/2019
* @Version: v1.0
*/
public class ProduceConsumeBlockingQuene {
public static void main(String[] args) {
final MyResourceBlockingQuene resourceBlockingQuene = new MyResourceBlockingQuene(new ArrayBlockingQueue<String>(10));
new Thread(new Runnable() {
@Override
public void run() {
try {
resourceBlockingQuene.addNum();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "生产者").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
resourceBlockingQuene.reduceNum();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "消费者").start();
}
}
class MyResourceBlockingQuene {
BlockingQueue<String> blockingQueue = null;
private volatile boolean FLAG = true;
private AtomicInteger atomicInteger = new AtomicInteger();
public MyResourceBlockingQuene(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void addNum() throws Exception {
String data = null;
boolean retValue;
while (FLAG) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "成功生产数据" + data);
} else {
System.out.println(Thread.currentThread().getName() + "失败生产数据" + data);
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "生产停止");
}
public void reduceNum() throws Exception {
String result = null;
while (FLAG) {
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (result == null || result.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "超时消费停止");
return;
}
System.out.println(Thread.currentThread().getName() + "消费数据成功" + result);
}
}
public void stop() {
FLAG = false;
}
}
package com.yjn.test.thread;
/**
* @ProjectName: com.yjn.test.thread
* @Description:
* @Author: yuanjunnan
* @CreateDate: 21/05/2019
* @Version: v1.0
*/
public class ProduceConsumeSynchronized {
public static void main(String[] args) {
final MyResourceSynchronized myResource = new MyResourceSynchronized(0);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 1000; i++) {
try {
myResource.addNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "生产者--").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 1000; i++) {
try {
myResource.reduceNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "消费者--").start();
}
}
class MyResourceSynchronized {
private int num;
public MyResourceSynchronized(int num) {
this.num = num;
}
public synchronized void addNum() throws InterruptedException {
while (num != 0) {
wait();
}
Thread.sleep(1000);
num++;
System.out.println(Thread.currentThread().getName() + "生产");
notify();
}
public synchronized void reduceNum() throws InterruptedException {
while (num != 1) {
wait();
}
Thread.sleep(1000);
num--;
System.out.println(Thread.currentThread().getName() + "消费");
notify();
}
}
package com.yjn.test.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @ProjectName: com.yjn.test.thread
* @Description:
* @Author: yuanjunnan
* @CreateDate: 21/05/2019
* @Version: v1.0
*/
public class ProduceConsumeLock {
public static void main(String[] args) {
final MyResourceLock myResource = new MyResourceLock(0);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 1000; i++) {
try {
myResource.addNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "生产者--").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 1000; i++) {
try {
myResource.reduceNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "消费者--").start();
}
}
class MyResourceLock {
private int num;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public MyResourceLock(int num) {
this.num = num;
}
public void addNum() throws InterruptedException {
lock.lock();
try {
while (num != 0) {
condition.await();
}
TimeUnit.SECONDS.sleep(1);
num++;
System.out.println(Thread.currentThread().getName() + "生产");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void reduceNum() throws InterruptedException {
lock.lock();
try {
while (num != 0) {
while (num != 1) {
condition.await();
}
TimeUnit.SECONDS.sleep(1);
num--;
System.out.println(Thread.currentThread().getName() + "消费");
condition.signalAll();
}
} finally {
lock.unlock();
}
}
}
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return null;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new CallableTest());
new Thread(futureTask).start();
while (futureTask.isDone()) {
Object result = futureTask.get();
}
}
}
Java中的线程池通过Executor框架实现的
该框架中用到了Executor、Executors(辅助工具类)、ExecutorService、ThreadPoolExecutor这几个类
线程池中的常驻核心线程数
线程池能够容纳同事执行的最大线程数,此值必须大于等于1
多余的空闲线程的存活时间,当前线程池数量超过corePoolSize时,
当空闲时间达到keepAliveTime值时,多余的空闲线程会被销毁知道直到只剩下corePoolSize个线程为止
默认的情况下只有当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize
keepAliveTime的时间单位
任务队列,被提交但尚未被执行的任务
表示生成线程池中工作线程的线程工厂,用于创建线程一般默认的即可
拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的Runnable的策略
以上三种创建线程池的方法都不用
推荐:
ExecutorService threadPool = new ThreadPoolExecutor(
2, //corePoolSize
5, //maximumPoolSize
1L, //keepAliveTime
TimeUnit.SECONDS, //TimeUnit
new LinkedBlockingQueue<Runnable>(3), //指定阻塞队列的大小
Executors.defaultThreadFactory(), //线程工厂方法
new ThreadPoolExecutor.AbortPolicy()); //决绝策略
线程数:CPU的核数+1个线程的线程池
系统资源不足
资源分配不当
进程运行推进的顺序不合适
class HoldLockThread implements Runnable {
private String lockA;
private String lockB;
public HoldLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "持有"
+ lockA + ",申请" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "持有"
+ lockB + ",申请" + lockA);
}
}
}
}
public class DeadlockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new HoldLockThread(lockA, lockB), "AAA").start();
new Thread(new HoldLockThread(lockB, lockA), "BBB").start();
}
}
虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(Native方法)引用的对象
//t1 可以叫 虚拟机栈中引用的对象
GCRootsDemo t1 = new GCRootsDemo();
//方法区中的类静态属性引用的对象
private static GCRootsDemo2 t2 = new GCRootsDemo2();
//方法区中常量引用的对象
private static final GCRootsDemo3 t3 = new GCRootsDemo3();
JVM常用选项
Boolean类型
使用格式:
“-XX : + 参数名称”,表示开启
“-XX : - 参数名称”,表示关闭
示例
-XX : +PrintGCDetails 开启GC打印详情 / -XX : -PrintGCDetails 关闭GC打印详情
-XX : +UseSerialGC 开启串行垃圾回收器 / -XX : -UseSerialGC 关闭串行垃圾回收器
KV设值类型
使用格式:
-XX:属性key = 属性value
示例
-XX:Metaspace=128m
-XX:MaxTenuringThreshold=15
jinfo举例,查看当前运行程序的JVM参数配置
jinfo -flag 配置项 进程编号
例: jinfo -flag PrintGCDetails 13632
jinfo -flags 进行编号 查看全部参数值
java -XX:+PrintFlagsInitial
java -XX:+PrintFlagsFinal
其中 = 显示是默认值,:= 修改值
可以让在程序运行前打印出用户手动设置或者JVM自动设置的XX选项,建议加上这个选项以辅助问题诊断。
最小堆内存 默认是物理内存的1/64,等价于 -XX:InitialHeapSize
最大堆内存 默认是物理内存的1/4,等价于 -XX:MaxHeapSize
设置单个线程栈的大小,一般默认为512k—1024k,等价于 -XX:ThreadStackSize
设置年轻代大小
设置元空间大小
元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代直接最大的区别在于:元空间并不在虚拟机中,而是在本地内存,因此默认情况下元空间的大小仅售本地内存限制
输出纤细GC收集日志
设置新生代中Eden和S0/S1空间比例
默认 -XX:SurvivorRatio=8,Eden::S0:S1=8:1:1
假如 -XX:SurvivorRatio=4,Eden::S0:S1=4:1:1 SurvivorRatio的值设置Eden的比例,S0/S1 相同
配置年轻代与老年代在堆结构的比例
默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的1/3
假如 -XX:NewRatio=4 新生代占1,老年代占4,年轻代占整个堆的1/5
NewRatio值就是设置老年代的占比,剩下的1给新生代
public static void main(String[] args) {
Object obj1 = new Object(); // 默认就是强引用
Object obj2 = obj1;
obj1 = null;
System.gc();
System.out.println(obj2); //obj2依旧不被回收
}
public static void main(String[] args) {
WeakHashMap<String, String> weakHashMap = new WeakHashMap<String, String>();
HashMap<String, String> hashMap = new HashMap<String, String>();
String weakHashMapKey = new String("weakHashMapKey");
String hashMapKey = new String("hashMapKey");
weakHashMap.put(weakHashMapKey, "weakHashMap");
hashMap.put(hashMapKey, "hashMap");
System.out.println("hashMap:"+hashMap);
System.out.println("weakHashMap:"+weakHashMap);
System.out.println("==============================================");
weakHashMapKey = null;
hashMapKey = null;
System.out.println("hashMap:key=null"+hashMap);
System.out.println("weakHashMap:key=null"+weakHashMap);
System.out.println("==============================================");
System.gc();
System.out.println("key=null,hashMap手动GC后:" + hashMap);
System.out.println("key=null,weakHashMap手动GC后:" + weakHashMap);
}
//打印
// hashMap:{hashMapKey=hashMap}
// weakHashMap:{weakHashMapKey=weakHashMap}
//==============================================
// hashMap:key=null{hashMapKey=hashMap}
// weakHashMap:key=null{weakHashMapKey=weakHashMap}
//==============================================
// key=null,hashMap手动GC后:{hashMapKey=hashMap}
// key=null,weakHashMap手动GC后:{}
public static void main(String[] args) {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
Object obj1 = new Object();
WeakReference<Object> objectWeakReference = new WeakReference<Object>(obj1, referenceQueue);
System.out.println("强引用正常对象:" + obj1);
System.out.println("弱引用正常对象:" + objectWeakReference.get());
System.out.println("referenceQueue:" + referenceQueue.poll());
obj1 = null;
System.gc(); //手动GC 虚引用被GC后会放到ReferenceQueue 中
System.out.println("强引用正常对象:" + obj1);
System.out.println("弱引用GC后对象:" + objectWeakReference.get());
System.out.println("referenceQueue:" + referenceQueue.poll());
// 强引用正常对象:java.lang.Object@4617c264
// 弱引用正常对象:java.lang.Object@4617c264
// referenceQueue:null
// 强引用正常对象:null
// 弱引用GC后对象:null
// referenceQueue:java.lang.ref.WeakReference@36baf30c
ReferenceQueue<Object> referenceQueue2 = new ReferenceQueue<Object>();
Object obj2 = new Object();
PhantomReference<Object> objectPhantomReference = new PhantomReference<Object>(obj2, referenceQueue2);
System.out.println("强引用正常对象:" + obj2);
System.out.println("虚引用正常对象:" + objectPhantomReference.get());
System.out.println("referenceQueue:" + referenceQueue2.poll());
obj2 = null;
System.gc(); //手动GC 虚引用被GC后会放到ReferenceQueue 中
System.out.println("强引用正常对象:" + obj2);
System.out.println("虚引用GC后对象:" + objectPhantomReference.get());
System.out.println("referenceQueue:" + referenceQueue2.poll());
// 强引用正常对象:java.lang.Object@7a81197d
// 虚引用正常对象:null
// referenceQueue:null
// 强引用正常对象:null
// 虚引用GC后对象:null
// referenceQueue:java.lang.ref.PhantomReference@5ca881b5
}
Java提供了4中引用类型,在垃圾回收的时候,都有各自的特点
ReferenceQueue是用来配合引用工作的,没有ReferenceQueue一样可以运行
创建引用的时候,可以指定关联的队列,当GC释放对象内存的时候,会将引用加到引用队列
如果程序当先某个虚引用已经被加入到引用队列,那就可以在所引用的队形的内训被回收之前采取必要的行动,相当于通知机制
当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的队形被回收,通过这种方式,JVM允许我们在对象被销毁后,做一些我们自己想做的事情
栈溢出错误
堆内存溢出错误
String.intern()方法解释
GC回收时间过长会抛出OutOfMemroyError----GC overhead limit exceeded
过长的定义:超过98%的时间用来做GC并且回收不到2%的堆内存,连续多次的GC都只回收不到2%的极端情况下才会抛出
假如不抛出GC overhead limit 错误会发生什么情况呢?
那就是GC清理这么点内存很快就被再次填满,迫使GC在再次执行,这样就形成了恶性循环,CPU使用率一直是100%,且GC却没有任何成果
高并发请求服务器时,经常出现 java.lang.OutOfMemoryError:nable to create new native thread
准确的讲应该native thread 异常与对应平台有关
垃圾回收器是垃圾回收算法的落地实现
目前为止没有完美的垃圾收集器出现,更没有万能的收集器,只是针对具体应用最适合的收集器,进行分代垃圾收集
为单线程环境设计且只是用一个线程进行垃圾回收
会暂停所有用户线程
不适合服务器环境
多个垃圾收集器并行工作,此时用户线程是暂停的
适用于科学计算、大数据处理等若交互场景
用户线程和垃圾收集器同事执行(不一定是并行,可能是交替执行),不需要停顿用户线程
互联网公司多用CMS垃圾收集器,适用于对相应时有要求的场景
G1垃圾回收器将堆内存分割成不同的无语然后并发对其进行垃圾回收
JVM参数:
java -XX:+PrintCommandLineFlags -version
指新生代定垃圾回收器,会自动激活老年代对应的垃圾回收器
只是标记一下GCRoots能直接关联的对象,速度很快,但仍然需要展厅所有的工作线程
进行GCRoots跟踪过程,和用户线程一起工作,不需要暂停工作线程,主要标记过程,标记全部对象
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动(可能不需要回收的对象)的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
由于并发标记时,用户线程依然运行,因此正式清理前,再做修正及确认
清楚GCRoots不可达的对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象
由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户工作线程一起并发工作
所以总体上来看CMS垃圾收集器的内存回收线程和用户线程是一起并发执行的
并发收集、地停顿
code
public class GCDemo {
public static void main(String[] args) {
System.out.println("===========GCDemo Test=======");
try {
String str = "abc";
while (true){
str +=str+new Random(777777777).nextInt()+new Random(888888888).nextInt();
str.intern();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
JVM 参数及指定垃圾回收器
Java8 不加垃圾回收器,默认就是USeParalleGC(效果同上)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC (PSYounGen+ParOldGen)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC (par new generation +concurrent)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC
(理论知道即可,实际Java8中已经被优化掉)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialOldGC (PSYounGen+ParNew)
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC (par new generation +concurrent)
同时指定新生代垃圾收集器、老生代垃圾收集器
繁琐配置仅供学习,生产上不会这样配置,因为 指定新生代、老生代垃圾收集器,对应会自动激活对应的垃圾收集器
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC -XX:+UseParallelOldGC
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC -XX:+UseParallelOldGC
最大好处是化整为零,避免全内存扫描,只需要按照区域进行扫描即可
G1不会产生内存碎片
是可以精确控制停顿、G1收集器是把整个堆(新生代,老生代)划分多个固定大小的区域
每次停顿允许停顿的时间内去收集垃圾最多的区域
mpstat -p -ALL 2
pidstat -u 1 -p 进程编号
查看硬盘剩余空间数 :df -h
查看额外
pidstat -d 采样间隔秒数 -p 进程编号
Linux和JDK命令一块分析
top命令找到CPU占比最高的进程
ps -ef 或者jps 进一步定位,得知是怎样的一个后台程序出问题
ps -mp 进程id -o THREAD,tid,time
-m :显示所有的线程
-p :进程使用cpu的时间
-o :该参数后是用户自定义格式
定位到具体线程或者代码
将需要的线程ID转换为16进制格式(英文小写格式)
printf “%x\n” 有问题的线程ID
jstack进程ID|grep tid(16进制线程ID英文小写格式) -A60
jstack 5101 |grep 13ee -A60
关键字XXX stars 通配符 :> 或者 :>= , … 区间
查找stars 数大于5000的 springboot项目 springboot stars :>=5000
查找forks 数大于5000的 springboot项目 springboot forks :>=5000
组合使用
查找fork在100到200 直接 并且stars在80 到100之间的springboot项目
springboot forks :100…200 stars :80…100
awesome系列一般是用来收集学习,工具、书籍类相关的项目
地址#L行号
例:
//单行高亮
https://github.com/JeffLi1993/springboot-learning-example/blob/master/chapter-1-spring-boot-quickstart/src/main/java/demo/springboot/QuickStartApplication.java#L12
//多行高亮
https://github.com/JeffLi1993/springboot-learning-example/blob/master/chapter-1-spring-boot-quickstart/src/main/java/demo/springboot/QuickStartApplication.java#L12-L15
英文字母 t 按键
location:beijing language:java