JVM+并发
- JVM
- 1. 方法区与常量池
- 2. 静态变量不入栈
- 3. 什么是JVM内存模型?
- 4. 栈和栈帧
- 5. JVM 中的常量池
- 6. 如何判断一个对象是否存活?
- 7. 强引用、软引用、弱引用、虚引用是什么,有什么区别?
- 8. 堆内存划分
- 9. 元空间
- 并发
- 1. 线程和进程
- 2. Runnable和Callable
- 3. 线程状态
- 4. 死锁的4个条件
- 5. 避免线程死锁
- 6. shutdown() VS shutdownNow()
- 7. start() 和 run()
- 8. Thread类中的yield方法
- 9. ReentrantLock 和 synchronized
- 10. synchronized 关键字和 volatile 关键字
JVM
1. 方法区与常量池
JDK1.8中字符串常量池和运行时常量池逻辑上属于方法区,但是实际存放在堆内存中
2. 静态变量不入栈
3. 什么是JVM内存模型?
Java 内存模型(下文简称 JMM)就是在底层处理器内存模型的基础上,定义自己的多线程语义。
它明确指定了一组排序规则,来保证线程间的可见性。
这一组规则被称为 Happens-Before。
怎么理解 happens-before 呢?
happens-before 也是为了保证可见性,比如那个解锁和加锁的动作,可以这样理解,线程1释放锁退出同步块,线程2加锁进入同步块,那么线程2就能看见线程1对共享对象修改的结果。
Java 提供了几种语言结构,包括 volatile, final 和 synchronized, 它们旨在帮助程序员向编译器描述程序的并发要求,其中:
- volatile - 保证可见性和有序性
- synchronized - 保证可见性和有序性; 通过管程(Monitor)保证一组动作的原子性
- final - 通过禁止在构造函数初始化和给 final 字段赋值这两个动作的重排序,保证可见性
编译器在遇到这些关键字时,会插入相应的内存屏障,保证语义的正确性。
总的来说,Java 内存模型描述的是多线程对共享内存修改后彼此之间的可见性。
另外,还确保正确同步的Java 代码可以在不同体系结构的处理器上正确运行。
4. 栈和栈帧
每个方法在执行的时候就会创建一个栈帧,它包含局部变量表、操作数栈、动态链接、方法出口等信息,局部变量表又包括基本数据类型和对象的引用;
5. JVM 中的常量池
JVM常量池主要分为Class文件常量池、运行时常量池,全局字符串常量池,以及基本类型包装类对象常量池。
6. 如何判断一个对象是否存活?
判断一个对象是否存活,分为两种算法
1:引用计数法;
2:可达性分析算法;
从一个被称为GC Roots的对象向下搜索,如果一个对象到GC Roots没有任何引用链相连接时,说明此对
象不可用,在java中可以作为GC Roots的对象有以下几种:
- 虚拟机栈中引用的对象
- 方法区类静态属性引用的变量
- 方法区常量池引用的对象
- 本地方法栈JNI引用的对象
7. 强引用、软引用、弱引用、虚引用是什么,有什么区别?
- 强引用,就是普通的对象引用关系,如 String s = new String(“ConstXiong”)
- 软引用,用于维护一些可有可无的对象。只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。SoftReference 实现
- 弱引用,相比软引用来说,要更加无用一些,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。WeakReference 实现
- 虚引用是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动。PhantomReference 实现
8. 堆内存划分
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old ),新生代默认占总空间的1/3,老年代默认占 2/3。
新生代有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1。
9. 元空间
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集.
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
某种程度上说,该图也是正确的:
并发
1. 线程和进程
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
2. Runnable和Callable
Call方法可以抛出异常,run方法不可以。
3. 线程状态
Java线程具有五种基本状态:
-
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
-
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于
就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行; -
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中; -
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
- 根据阻塞产生的原因不同,阻塞状态又可以分为三种:
- 1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 2.同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 3.其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
-
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
4. 死锁的4个条件
- 互斥: 同一时刻该资源只能由一个线程占用。
- 请求与保持: 一个线程请求获取资源被阻塞,另一个线程对已获得的资源保持不放。
- 不剥夺: 线程在获取资源后,在未使用完之前不能被其他线程强行剥夺,只能自己使用完自己释放。
- 循环等待: 若干线程之间形成循环等待资源关系。
5. 避免线程死锁
只要破坏产生死锁的四个条件中的其中一个就可以了
- 互斥
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问) - 请求与保持
一次性申请所有资源 - 不剥夺
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。 - 循环等待
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。 - 锁排序法:(必须回答出来的点)
- 指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作
- 多线程:指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方法。
6. shutdown() VS shutdownNow()
- shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。
- shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。
7. start() 和 run()
调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
8. Thread类中的yield方法
Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。
9. ReentrantLock 和 synchronized
- 1.两者都是可重入锁
- 2.synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API(JDK层面,需要 lock() 和 unlock() 方法配合 try/finally)
- 3.ReentrantLock 比 synchronized 增加了一些高级功能
①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)- 等待可中断.通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。
所谓的公平锁就是先等待的线程先获得锁。(先到先得)
ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。 - ReentrantLock类线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用 notify()/notifyAll() 方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”。
10. synchronized 关键字和 volatile 关键字
- volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。