jvm
接https://blog.csdn.net/focuson_/article/details/81170959
垃圾回收算法
标记算法
无论哪种垃圾回收算法,都需要找到垃圾对象。有两种算法:
1、引用计数算法:堆中每个对象都有一个引用计数器,引用时加1,对象回收时,他引用的任何对象引用都减1,但是难以检测出对象之间的循环引用,增加了程序执行的开销,早期的jvm使用该算法
2、根搜索算法。从gc根对象开始,将访问到的对象标记为存活,清除不可达对象。
回收算法
1、标记-清除算法。标记清除之后会产生大量不连续的碎片,还会不得不触发另外一次垃圾收集动作。
2、标记-整理算法(老年代)它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。这样不会有碎片问题,但GC暂停的时间会增长,因为你需要将所有的对象都拷贝到一个新的地方,还得更新它们的引用地址。
3、copping算法(年轻代)
新生代gc:Minor GC/Scavenge GC
老年代gc:Major GC/Full GC:整个堆的gc,程序暂停时间长
参考https://www.jianshu.com/p/5261a62e4d29
java内存模型
JVM将内存组织为主内存和工作内存两个部分。
主内存主要包括本地方法区和堆。每个线程都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程私有的栈和对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。
1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的。
2.每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。
重排序 as-if-serial happens-before
- 重排序 在不改变处理结果的前提下,处理器和编译器会尽可能的提高并行度,提高执行效率,即处理器和编译器会进行重排序,比如单线程不是一个个命令通过单线程执行的,会通过重排序,并行执行提高效率,但不会打乱依赖性,编译器和处理器不会改变存在数据依赖性关系的两个操作。
- as-if-serial 单线程不管怎么重排序,程序执行结果是不会改变的,这个语义包裹的就是这个意思,给人一个幻觉,单线程是一条一条执行的
- happens-before 1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。
- as-if-serial VS happens-before:as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变;as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的;as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。
- happens-before规则:
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
- 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
- start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
- join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
- 程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
- 对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。
volatile关键字
volatile的作用是,线程之间的变量的可见性,只能修饰变量。主要原因是java的内存模型是每个线程从主内存copy数据到工作内存,执行完之后会把工作内存刷新到主内存,使用该关键字时:1.当线程对volatile变量进行写操作时,会将修改后的值刷新回主内存;2.当线程对volatile变量进行读操作时,会先将自己工作内存中的变量置为无效,之后再通过主内存拷贝新值到工作内存中使用。
volatile适合直接赋值的场景
synchronized保证有序性
volatile和synchronized的区别
1.volatile只能修饰变量,而synchronized可以修改变量,方法以及代码块
2.volatile在多线程中不会存在阻塞问题,synchronized会存在阻塞问题
3.volatile能保证数据的可见性,但不能完全保证数据的原子性,synchronized即保证了数据的可见性也保证了原子性
4.volatile解决的是变量在多个线程之间的可见性,而sychroized解决的是多个线程之间访问资源的同步性
java的四种引用
接https://blog.csdn.net/focuson_/article/details/82925805
JVM会优先回收长时间闲置不用的软引用对象,对那些刚刚构建的或刚刚使用过的软引用对象会尽可能保留
1、
var s = new String("123");
val reference=new WeakReference(s);
s=null;
println(reference.get())
System.gc()
println(reference.get())
返回:
123
null
2、
val s = new String("123");
val reference=new WeakReference(s);
println(reference.get())
System.gc()
println(reference.get())
返回:
123
123
3、
val reference=new WeakReference(new String("123"));
println(reference.get())
System.gc()
println(reference.get())
返回:
123
null
弱引用使用场景:
ThreadLocal:里面维护一个ThreadLocalMap,key就是当前的ThreadLocal对象的软可及引用,如果该ThreadLocal的对象全部取消引用后,jvm gc时,会回收该key,但是value值是强可及对象,为了不内存泄露,需要调用remove方法
内存泄露
应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费
1、静态变量导致的内存泄露
静态集合导致的泄露可以分析为:长生命周期的对象,持有了短生命周期对象的引用,在后者生命周期结束时未释放长周期对
象对它的引用,导致对象无法被GC回收。
以如下代码为示例:即使在循环内有设置集合对象为null,但集合中的对象还是存在,GC并不能回收它(这种在集合中不断创
建新对象的写法也是极其臭名昭著的)。
public class ExampleUnitTest {
private static Vector sVector = new Vector();
@Test
public void name() throws Exception {
for (int i = 1; i < 1000; i++) {
Object o = new Object();
sVector.add(o);
o = null;
}
}
}
2、匿名内部类引发的内存泄露
非静态内部类持有对外部类的引用。当回收外部类时,发现有内部类对他的引用,就回收不掉,会造成内存泄露,优化方法是使用静态内部类。网上还说使用弱引用,但是没有理解使用静态内部类同时使用弱引用的必要性?
3、资源未及时关闭,如io流等
防止内存泄露:
1、减少临时对象的使用
2、尽量使用StringBuffer,而不用String来累加字符串
由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
String的内容一旦声明则不可改变,如果要改变,则改变的肯定是String的引用地址,那么如果一个字符串要被经常改变,则就必须使用StringBuffer类。
3、不要在静态变量或者静态内部类中使用非静态外部成员变量,实在要用:将内部类改为静态内部类,静态内部类中使用弱引用来引用外部类的成员变量
4、尽量避免使用 static 成员变量
5、勿override finalize()。它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。
6、关闭资源
7、有些地方避免使用强引用,替换为弱引用等操作。
8、尽量使用线程池替代多线程操作,这样可以节约内存及CPU占用率。
9、尽量的优化自己的代码,减少冗余,进行编译打包等优化对齐处理,避免类加载时浪费内存。
泛型相关
https://www.cnblogs.com/drizzlewithwind/p/6101081.html
时间复杂度、空间复杂度