赞
踩
https://zhuanlan.zhihu.com/p/439176814
java8新增了非常多的新特性,这里主要讨论如下几个:
Lambda表达式,也称闭包,是Java8最大的语言改变。
java曾经只能以匿名内部类的方式实现Lambda表达式的功能。
Lambda表达式的语法格式如下所示:
(parameters) -> expression
或
(parameters) ->{ statements; }
Lambda编程风格总结:
方法引用使用::
,通过方法的名字来指向一个方法。
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
cars.forEach( Car::collide );
cars.forEach( Car::repair );
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
函数式接口指有且仅有一个抽象方法,但可以有多个非抽象方法的接口。这样的接口可以隐式转换为Lambda表达式。
目前 Java 库中的所有相关接口都已经带有这个注解了,实践上java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子!
但是在实践中,函数式接口非常脆弱,只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface
举个例子:
@FunctionalInterface
public interface GreetingService {
void sayMessage(String message);
}
Java8中可以采用Lambda表达式进行编程
GreetingService greetService = message -> System.out.println("Hello " + message);
greetService.sayMessage("world");
默认方法是指接口的默认方法,它是java8的新特性之一。顾名思义,默认方法就是接口提供一个默认实现,且不强制实现类去覆写的方法。默认方法用default关键字来修饰。
在java8之前,修改接口功能通常会给接口添加新的方法,这时对已经实现该接口的所有实现类,都要一一添加对新方法的实现,换言之,在给接口定义新方法的同时无法不影响到已有的实现类,这时,java8的默认方法特性就可以解决这种接口修改与已有实现类不兼容的问题
public interface Vehicle {
//默认方法
default void print(){
System.out.println("我是一辆车!");
}
}
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
举个例子:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
https://zhuanlan.zhihu.com/p/79219960
之前jdk1.7的存储结构是数组+链表,到了jdk1.8变成了数组+链表+红黑树。
另外,HashMap是非线程安全的,也就是说在多个线程同时对HashMap中的某个元素进行增删改操作的时候,是不能保证数据的一致性的。
java 1.7 使用数组 + 链表的方式来存储HashMap
下图是java 1.8 后HashMap的存储方式!
只有在链表的长度不小于8,而且数组的长度不小于64的时候才会将链表转化为红黑树
我们会发现优化的部分就是把链表结构变成了红黑树。原来jdk1.7的优点是增删效率高,于是在jdk1.8的时候,不仅仅增删效率高,而且查找效率也提升了。
红黑树:自平衡的二叉查找树,查询效率非常高,能从O(n)转变为O(logn)
HashMap频繁的扩容,会造成底部红黑树不断的进行拆分和重组,这是非常耗时的。因此,也就是链表长度比较长的时候转变成红黑树才会显著提高效率。
简单地说,ConcurrentHashMap通过CAS和Synchronized实现比HashTable效果更好的线程安全!
https://blog.csdn.net/qq_41737716/article/details/90549847
非阻塞式I/O模型(Non-blocking I/O Model):进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞,但需要不断地轮询I/O操作的状态。进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。
I/O复用模型(I/O Multiplexing Model):多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select, select会监听所有注册进来的IO;
如果select没有监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可数据时,select调用就会返回;
而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。
可以看到,多个进程注册IO后,只有一个select调用进程被阻塞。
信号驱动式I/O模型(Signal-driven I/O Model):在进行I/O操作时,应用程序不会被阻塞,但需要注册信号处理函数,在I/O操作完成时,系统会向应用程序发送一个信号,通知其进行处理。
异步I/O模型(Asynchronous I/O Model):在进行I/O操作时,应用程序不会被阻塞,I/O操作的完成会通过回调函数的方式通知应用程序,从而实现异步处理。
五个IO模型的比较:
下面是ThreadPoolExecutor类的构造方法源码,其他创建线程池的方法最终都会导向这个构造方法,共有7个参数:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
package threadpool; import java.util.concurrent.*; public class MyThreadPoolDemo { public static void main(String[] args) { System.out.println("可用的系统线程数:" + Runtime.getRuntime().availableProcessors()); ExecutorService threadPool = new ThreadPoolExecutor( 5, //核心线程数 10, //最大线程数 1L, //存活时间 TimeUnit.SECONDS, //时间单位 new LinkedBlockingDeque<>(3), //工作队列 Executors.defaultThreadFactory(), //线程工厂 new ThreadPoolExecutor.AbortPolicy() //拒绝策略 ); try { for (int i = 0; i < 10; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " 办理业务"); }); } } catch (Exception E) { E.printStackTrace(); } finally { threadPool.shutdown(); } } }
https://blog.csdn.net/songguangfan/article/details/118580922
https://www.cnblogs.com/cxuanBlog/p/13494343.html
并发系统可以采用多种并发编程模型来实现。并发模型一般都指定了系统中的线程如何通过协作来完成分配给它们的作业。不同的并发模型采用不同的方式拆分作业,同时线程间的协作和交互方式也不相同。
在并行工作者模型中,委派者将传入的作业分配给不同的工作者。每个工作者完成整个任务。工作者们并行运行在不同的线程上,甚至可能在不同的CPU上运行。
并行 Worker 模型是 Java 并发模型中非常常见的一种模型。许多 java.util.concurrent 包下的并发工具都使用了这种模型。
基本的流水线并发图如下所示:
实际情况中,通常不会按照一条装配线流动,甚至各个worker中还要共同参与完成
流水线又可以分为Actor模型和Channels模型
第三种并发模型是函数式并行模型。函数式并行的基本思想是采用函数调用实现程序。函数可以看作是”代理人(agents)“或者”actor“,函数之间可以像流水线模型(AKA 反应器或者事件驱动系统)那样互相发送消息。某个函数调用另一个函数,这个过程类似于消息发送。
一旦每个函数调用都可以独立的执行,它们就可以分散在不同的CPU上执行了。这也就意味着能够在多处理器上并行的执行使用函数式实现的算法。
https://blog.csdn.net/sermonlizhi/article/details/123356877
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future源码:
public interface Future<V> {
// 取消任务的执行,参数表示是否立即中断任务执行,或者等任务结束
boolean cancel(boolean mayInterruptIfRunning);
// 任务是否已经取消,任务完成前将其取消,则返回true
boolean isCancelled();
// 任务是否已经完成
boolean isDone();
// 等待任务执行结束,返回泛型结果.中断或任务执行异常都会抛出异常
V get() throws InterruptedException, ExecutionException;
// 同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出TimeoutException
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future归根结底只是一个接口,而FutureTask实现了这个接口,同时还实现了Runnable接口,这样FutureTask就相当于是消费者和生产者的桥梁了,消费者可以通过FutureTask存储任务的执行结果,跟新任务的状态:未开始、处理中、已完成、已取消等等。而任务的生产者可以拿到FutureTask被转型为Future接口,可以阻塞式的获取处理结果,非阻塞式获取任务处理状态
总结:FutureTask既可以被当做Runnable来执行,也可以被当做Future来获取Callable的返回结果。
FutureTask的使用:构建一个FutureTask对象,其构造方法的入参为Callable的实例对象,然后将FutureTask对象作为Thread构造方法的入参。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println("通过Callable执行任务");
Thread.sleep(3000);
return "返回任务结果";
}
});
//在Thread中启动
new Thread(task).start();
//查询这个task是否完成(非阻塞)
System.out.println(task.isDone());
//获得task的结果(阻塞)
System.out.println(task.get());
Future的注意事项:
Future的局限性:
从本质上说,Future表示一个异步计算的结果。它提供了isDone()来检测计算是否已经完成,并且在计算结束后,可以通过get()方法来获取计算结果。在异步计算中,Future确实是个非常优秀的接口。但是,它的本身也确实存在着许多限制:
而这些局限性CompletionService和CompletableFuture都解决了,这边文章重点介绍CompletableFuture的使用
Future接口的功能无法满足更加复杂的异步处理流程,所以由CompletableFuture进行异步功能的改进和扩张
CompletableFuture实现了Future接口和CompletionStage接口
静态初始化代码:CompletableFuture/CompletableFutureBuildDemo.java
代码:CompletableFuture/CompletableFutureDemo.java
https://blog.csdn.net/chuhe163/article/details/124237803
@Accessors(chain = true)
注解的方法,可以使用链式调用:代码:CompletableFuture/CompletableFutureMallDemo.java
public T get() 获取结果,有抛出异常
public T get(long timeout, TimeUnit unit) 设置一个超时时间,超时就直接抛出异常
public T join() 获取结果,不抛出异常
public T getNow(T valueIfAbsent) 计算完了就回正常值,没算完就返回valueIfAbsent
public boolean complete(T value) 调用即打断get方法并立即返回value
thenApply(f->{}) 计算结果存在依赖关系,对这两个线程串行化,异常就停止在这一步
handle((f,e)->{}) 同上,但有异常的话会继续向下走
thenAccept(f->{}) 接收任务的处理结果并消费处理,无返回结果
thenRun(()->{}) 任务A执行完执行任务B,且B不需要A的结果
A.applyToEither(B, f->{}) A和B谁快,谁进f
A.thenCombine(B, (x,y)->{}) 合并A和B的结果
没有传入自定义线程池时,都使用默认线程池ForkJoinPool
如果执行第一个任务时,传入了一个自定义线程池:
调用thenRun方法执行第二个任务时,则第二个和第一个任务共用一个线程池
调用thenRunAsync执行第二个任务时,则第一个任务使用的是自己传入的线程池,第二个则会用默认线程池
https://zhuanlan.zhihu.com/p/61966479
要实现共享变量的可见性,必须保证两点:
可见性的实现方式:
synchronized可以同时实现可见性与原子性!
JMM关于synchronized的两条规定:
volatile只能实现可见性,不能实现原子性!
volatile不需要加锁,比synchronized更轻量级,不会阻塞线程
深入来说:通过加入内存屏障和禁止重排序优化来实现的
线程写volatile变量的过程:
线程读volatile变量的过程:
https://www.jianshu.com/p/d61f294ac1a6
偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。
当一个线程访问同步代码块并获取锁时,会在 Mark Word 里存储锁偏向的线程 ID。在线程进入和退出同步块时不再通过 CAS 操作来加锁和解锁,而是检测 Mark Word 里是否存储着指向当前线程的偏向锁。轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换 ThreadID 的时候依赖一次 CAS 原子指令即可。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
关于偏向锁的撤销,需要等待全局安全点,即在某个时间点上没有字节码正在执行时,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态。
偏向锁在 JDK 6 及之后版本的 JVM 里是默认启用的。可以通过 JVM 参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。
轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋(关于自旋的介绍见文末)的形式尝试获取锁,线程不会阻塞,从而提高性能。
轻量级锁的获取主要由两种情况:① 当关闭偏向锁功能时;② 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。
在代码进入同步块的时候,如果同步对象锁状态为无锁状态,虚拟机将首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,然后将对象头中的 Mark Word 复制到锁记录中。
拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record 里的 owner 指针指向对象的 Mark Word。
如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark Word 的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。
如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。
若当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁(锁膨胀)。
另外,当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁(锁膨胀)。
重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。
简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资源,导致性能低下。
乐观锁是一种乐观思想,假定当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改(所以没有上锁)。写数据时,判断当前与期望值是否相同,如果相同则进行更新(更新期间加锁,保证是原子性的)。
Java中的乐观锁: CAS,比较并替换,比较当前值(主内存中的值),与预期值(当前线程中的值,主内存中值的一份拷贝)是否一样,一样则更新,否则继续进行CAS操作
悲观锁是一种悲观思想,即认为写多读少,遇到并发写的可能性高,每次去拿数据的时候都认为其他线程会修改,所以每次读写数据都会认为其他线程会修改,所以每次读写数据时都会上锁。其他线程想要读写这个数据时,会被这个线程block,直到这个线程释放锁然后其他线程获取到锁。
Java中的悲观锁: synchronized修饰的方法和方法块、ReentrantLock。
自旋锁是一种技术: 为了让线程等待,我们只须让线程执行一个忙循环(自旋)。
自旋锁的优点: 避免了线程切换的开销。挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。
自旋锁的缺点: 占用处理器的时间,如果占用的时间很长,会白白消耗处理器资源,而不会做任何有价值的工作,带来性能的浪费。因此自旋等待的时间必须有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程。
自旋次数默认值:10次,可以使用参数-XX:PreBlockSpin来自行更改。
自适应自旋: 自适应意味着自旋的时间不再是固定的,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。有了自适应自旋,随着程序运行时间的增长及性能监控信息的不断完善,虚拟机对程序锁的状态预测就会越来越精准。
任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞。
再次获取锁:识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。获取锁后,进行计数自增,
释放锁:释放锁时,进行计数自减。
Java中的可重入锁: ReentrantLock、synchronized修饰的方法或代码段。
可重入锁的作用: 避免死锁。
读写锁是一种技术: 通过ReentrantReadWriteLock类来实现。为了提高性能, Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由 jvm 自己控制的。
读锁: 允许多个线程获取读锁,同时访问同一个资源。
写锁: 只允许一个线程获取写锁,不允许同时访问同一个资源。
Java中的读写锁:ReentrantReadWriteLock
公平锁是一种思想: 多个线程按照申请锁的顺序来获取锁。申请的多个线程按队列来获取锁。
非公平锁是一种思想: 多个线程获取锁的顺序,不是按照先到先得的顺序,有可能后申请锁的线程比先申请的线程优先获取锁。
优点: 非公平锁的性能高于公平锁。
缺点: 有可能造成线程饥饿(某个线程很长一段时间获取不到锁)
Java中的非公平锁:synchronized是非公平锁,ReentrantLock通过构造函数指定该锁是公平的还是非公平的,默认是非公平的。
分段锁是一种机制: 最好的例子来说明分段锁是ConcurrentHashMap。ConcurrentHashMap原理:它内部细分了若干个小的 HashMap,称之为段(Segment)。默认情况下一个 ConcurrentHashMap 被进一步细分为 16 个段,既就是锁的并发度。如果需要在 ConcurrentHashMap 添加一项key-value,并不是将整个 HashMap 加锁,而是首先根据 hashcode 得到该key-value应该存放在哪个段中,然后对该段加锁,并完成 put 操作。在多线程环境中,如果多个线程同时进行put操作,只要被加入的key-value不存放在同一个段中,则线程间可以做到真正的并行。
线程安全:ConcurrentHashMap 是一个 Segment 数组, Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全
锁粗化是一种优化技术: 如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作都是出现在循环体体之中,就算真的没有线程竞争,频繁地进行互斥同步操作将会导致不必要的性能损耗,所以就采取了一种方案:把加锁的范围扩展(粗化)到整个操作序列的外部,这样加锁解锁的频率就会大大降低,从而减少了性能损耗。
synchronized是Java中的关键字:用来修饰方法、对象实例,代码块。属于独占锁、悲观锁、可重入锁、非公平锁。
每个对象都有个 monitor 对象, 加锁就是在竞争 monitor 对象,代码块加锁是在代码块前后分别加上 monitorenter 和 monitorexit 指令来实现的,方法加锁是通过一个标记位来判断的。
Lock: 是Java中的接口,可重入锁、悲观锁、独占锁、互斥锁、同步锁。
Lock需要手动获取锁和释放锁。就好比自动挡和手动挡的区别
Lock 是一个接口,而 synchronized 是 Java 中的关键字, synchronized 是内置的语言实现。
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。
Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
Lock 可以通过实现读写锁提高多个线程进行读操作的效率。
synchronized的优势:
足够清晰简单,只需要基础的同步功能时,用synchronized。
Lock应该确保在finally块中释放锁。如果使用synchronized,JVM确保即使出现异常,锁也能被自动释放。
使用Lock时,Java虚拟机很难得知哪些锁对象是由特定线程锁持有的。
ReentrantLock是Java中的类 : 继承了Lock类,可重入锁、悲观锁、独占锁、互斥锁、同步锁。
划重点
相同点:
1.主要解决共享变量如何安全访问的问题
2.都是可重入锁,也叫做递归锁,同一线程可以多次获得同一个锁,
3.保证了线程安全的两大特性:可见性、原子性。
不同点:
1.ReentrantLock 就像手动汽车,需要显示的调用lock和unlock方法, synchronized 隐式获得释放锁。
2.ReentrantLock 可响应中断, synchronized 是不可以响应中断的,ReentrantLock 为处理锁的不可用性提供了更高的灵活性
3.ReentrantLock 是 API 级别的, synchronized 是 JVM 级别的
4.ReentrantLock 可以实现公平锁、非公平锁,默认非公平锁,synchronized 是非公平锁,且不可更改。
5.ReentrantLock 通过 Condition 可以绑定多个条件
https://blog.csdn.net/qq_22076345/article/details/82194482
https://blog.csdn.net/qq_22076345/article/details/82229447
Spring中使用@Async
注解标记的方法称为异步方法,会在调用方法线程之外开一个独立线程来运行这个方法。
@Async
注解使用条件:
@EnableAsync
原理:(看不太懂)
@Async和@EnableAsync注解实现方法异步调用底层是通过AOP和线程池实现的。
Spring框架提供了两种事务管理方式:声明式事务和编程式事务。
声明式事务
声明式事务管理只需要用到@Transactional 注解和@EnableTransactionManagement。它是基于 Spring AOP 实现的,并且通过注解实现,实现起来简单,对原有代码没有入侵性。
在声明式事务中,我们只需要在配置文件中指定事务的类型、传播行为、超时时间等属性,Spring框架会在运行时自动为我们创建和管理事务。这种方式的优点是简单、易用,可以避免代码中出现大量的事务管理代码,但缺点是不够灵活,只能在配置文件中进行事务的配置。
编程式事务
编程式事务是通过在代码中编写事务管理代码来实现的。在编程式事务中,我们需要在代码中显式地开启、提交或回滚事务,并且需要手动处理事务的异常。这种方式的优点是灵活、可控,可以根据具体的业务需求进行事务管理,但缺点是代码复杂度高,容易出错,并且不易维护。
通过PlatformTransactionManager控制事务
通过TransactionTemplate控制事务
总的来说,声明式事务适合简单的事务场景,而编程式事务适合复杂的事务场景。在实际应用中,我们可以根据具体的业务需求选择合适的事务管理方式。
这里有一篇讲JVM调优很不错的文章:
https://www.cnblogs.com/three-fighter/p/14644152.html
JVM调优应该是性能优化的最后一颗子弹!性能问题一般第一选择是优化应用层。
调优步骤:
//设置Serial垃圾收集器(新生代)
开启:-XX:+UseSerialGC
//设置PS+PO,新生代使用功能Parallel Scavenge 老年代将会使用Parallel Old收集器
开启 -XX:+UseParallelOldGC
//CMS垃圾收集器(老年代)
开启 -XX:+UseConcMarkSweepGC
//设置G1垃圾收集器
开启 -XX:+UseG1GC
//设置堆初始值
指令1:-Xms2g
指令2:-XX:InitialHeapSize=2048m
//设置堆区最大值
指令1:`-Xmx2g`
指令2: -XX:MaxHeapSize=2048m
//新生代内存配置
指令1:-Xmn512m
指令2:-XX:MaxNewSize=512m
//GC停顿时间,垃圾收集器会尝试用各种手段达到这个时间
-XX:MaxGCPauseMillis
//survivor区和Eden区大小比率
指令:-XX:SurvivorRatio=6 //S区和Eden区占新生代比率为1:6,两个S区2:6
//新生代和老年代的占比
-XX:NewRatio=4 //表示新生代:老年代 = 1:4 即老年代占整个堆的4/5;默认值=2
//进入老年代最小的GC年龄,年轻代对象转换为老年代对象最小年龄值,默认值7
-XX:InitialTenuringThreshol=7
//使用多少比例的老年代后开始CMS收集,默认是68%,如果频繁发生SerialOld卡顿,应该调小
-XX:CMSInitiatingOccupancyFraction
//G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%
-XX:G1MixedGCLiveThresholdPercent=65
XX:MaxDirectMemorySize
请见链接,这里不详细写了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。