Java垃圾回收和JVM调优
Java为什么需要进行垃圾回收?
垃圾回收与内存息息相关,垃圾回收能够释放更多空余内存,提高程序的执行效率。
垃圾回收会造成什么问题?
会造成内存资源被gc线程暂用,程序出现卡顿,不可用的情况。
内存调优的目的
通过调节内存大小来平衡GC频率和单次GC的时长。
发现垃圾
-
引用计数
-
可达性分析(Java就是用这个算法)
确定哪些对象是不能回收的(也就是GC Root,例如string),然后从这些对象开始逐级往下搜索,不可通达的对象就表示可以回收了。
由判断是否可回收又牵扯到强引用、软引用、弱引用、虚引用的知识了。强引用GC不可回收,软引用内存不够则回收,弱引用一旦GC就回收,虚引用可以用来监听对象是否回收。
垃圾回收算法
发现垃圾以后就要进行垃圾回收了,而要进行垃圾回收,则需要一个垃圾算法:
- 标记清除算法
当我们进行垃圾回收的时候,会发现内存中的垃圾是不连续的,情况如图:
黑色部分表示是需要回收的垃圾快,这就是内存碎片。这时候如果我们想申请一块连续的较大内存就有可能造成内存溢出。
- 复制算法
为了解决内存碎片的问题,我们可以把内存分为两部分,如图:
左边2、4这部分是我们需要回收的垃圾,我们先把1、3、5复制到右边,然后把左边的全部一次性清除就好,这就是复制算法。但是这样又会造成内存被对半砍的问题,造成资源浪费。
- 标记整理算法(这就是Java采用的算法)
该算法会把内存分为新生代(伊甸园区、生存区)、老年代,如图:
在每个区域都要进行GC,存活下来的对象就往后移动,最终进入老年区。
堆栈
- 内存中的堆栈和数据结构中的栈是两码事,两者没有必然联系
- 内存中的栈是存放方法和局部变量的,先进后出,用完即释放
- 内存中的堆,是堆放杂乱无章的数据
多线程
wait、notify、notifyall
- 这三者是用来做线程间通信的,他们共同持有一个object对象,object.wait()以后会释放锁,别的线程可以继续操作object,直到有其他线程调用object.notify(),wait线程才继续往下执行。
- 这三者必须要被synchronized(object){}包裹
线程池复用伪代码,不知道理解得对不对,欢迎指教
package com.example.mytest;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
@Slf4j
public class MyThreadPool {
public static void main(String[] args) {
//整个过程只创建了一个worker线程
Worker worker = new Worker();
worker.start();
//模拟创建无数任务
for (;;){
worker.execute(new Runnable() {
@Override
public void run() {
log.info("线程复用:{}", Thread.currentThread().getName());
}
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Worker extends Thread{
private BlockingDeque<Runnable> workers = new LinkedBlockingDeque<>();
public void execute(Runnable task) {
workers.add(task);
log.info("正在复用的线程:{}", Thread.currentThread().getName());
}
@Override
public void run() {
while(true){
Runnable task = workers.poll();
if(task != null){
task.run();//并没有执行start,所以并没有创建线程
}
}
}
}
}
什么是线程安全?
多个线程操作同一个资源,能够保证数据不被污染就是线程安全,否则就不是。
保证线程安全的办法就是加锁,例如StringBuffer内部就是用了synchronize锁才保证了线程安全。
说到线程安全就不得不提起java内存模型JMM,即java内存分为主内存和工作内存,validate的作用就是实时的把线程的工作内存里的数据刷新到主存。
代理与反射
- 代理 就是在不改变原有代码的基础上新增功能,动态代理要继承自InvocationHandler
public class ProxyHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("代理成功1111");
Object invoke = method.invoke(target, args);
log.info("代理成功222222");
return invoke;
}
}
其中代理类OSSClient是继承自OSS接口(必须)
@Test
public void proxyTest(){
OSSClient ossClient = new OSSClient("11111", "11111");
ProxyHandler proxyHandler = new ProxyHandler();
proxyHandler.setTarget(ossClient);
OSS proxy = (OSS)proxyHandler.getProxy();
log.info(proxy.getConnectionPoolStats());
}
- 反射,反射可以调用其他第三方提供的SDK里的私有方法,这就很有用了
@Test
public void invokeTest(){
try {
Class<?> aClass = Class.forName("com.aliyun.oss.OSSClient");
Constructor<?> constructor = aClass.getConstructor(String.class, String.class);
Object newInstance = constructor.newInstance("11111", "11111");
Method toURI = aClass.getDeclaredMethod("toURI", String.class);
toURI.setAccessible(true);
URI invoke = (URI)toURI.invoke(newInstance, "https://www.qq.com");
log.info("反射结果:{}", invoke.getHost());
} catch (Exception e) {
e.printStackTrace();
}
}
RabbitMQ
Exchange交换机类型
- topic是路由模式,根据routingkey去分发消息,用 . 做路由分割,*表示一个单词,#表示多个单词
- fanout是广播模式,在此模式下路由失效,所有订阅的队列都能收到消息
- direct会把消息发送到routingKey和queue名称一样的队列,例如队列的名字叫queue.test1,那routingKey也必须是queue.test1,队列才能收到消息
死信队列
死信队列产生的原因:
- 消息被nack或者被reject,且requeue为false
- 消息TTL过期,可以在创建队列的时候规定超时时间,也可以发送消息的时候规定
- 队列超出长度限制
死信队列的创建方式:
哪个正常业务队列需要处理死信消息就在该队列上配置
x-dead-letter-exchange: 死信交换机(其实就是个正常交换机) x-dead-letter-routing-key: 死信队列(其实就是个队列)