前言:大家好,我是小威,24届毕业生,在一家满意的公司实习。本篇将记录几次面试中经常被问到的知识点以及对学习的知识点总结和面试题的复盘。
本篇文章记录的基础知识,适合在学Java的小白,也适合复习中,面试中的大佬🤩🤩。
如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
小威在此先感谢各位大佬啦~~🤞🤞
以下正文开始
文章目录
🎄JVM线程私有和共享的区域
JVM线程私有的区域有:虚拟机栈,本地方法栈,程序计数器。
虚拟机栈:主要存储方法,局部变量,运行的数据。
本地方法栈:主要存储本地方法(含有Native关键字的方法)。
程序计数器:存储程序运行位置的字节码行号指示器。
JVM线程共享的区域有:Java堆,元空间
Java堆:存储所有创建的对象,数组等。
元空间:存储虚拟机加载的字节码数据,常量,静态变量,运行时常量池等。
🎇线程上下文切换
线程上下文切换,也就是CPU不再执行当前的线程,而去执行其他的线程。那有哪些原因会导致线程的上下文切换呢?
- 线程的时间片用完
- 垃圾回收(会暂停当前工作的线程,先进行垃圾回收)
- 更高优先级的线程运行
- 线程主动调用了某些方法,如sleep,yeild,wait,join,synchronized,lock等
当发生上下文切换时,操作系统会保存当前线程的状态,恢复另一个线程的状态,此时程序计数器会记住下一条jvm指令的执行地址,同时上文记录,程序计数器是线程私有的。
🍒如何判断对象是否存活
判断对象是否存活有两种方法:引用计数算法和可达性分析算法。
🍸引用计数法
在对象被创建的时候,会在对象头中分配一个空间,即计时器,来保存这个对象被引用的次数。如果这个对象被其他的对象引用,它的引用计数器会+1,如果删除其他对象对这个对象的引用,则它的引用计数会-1,当对象的引用计数为0时,这个对象就会被当成垃圾回收。
🎍可达性分析法
Java虚拟机中的垃圾回收机制都是采用的可达性分析算法来探索存活的对象的。此种方法工作原理是会扫描java堆中的对象,沿着GC Roots对象往下寻找,看看是否能在此引用链中找到该对象,如果找不到的话,证明该对象没用了,表示该对象可以回收。
可达性分析算法最大的优点之一就是解决了对象之间的相互循环依赖的问题,目前和引用计数法比起来没有缺点。
🍖JVM中的垃圾回收算法
对于新生代和老年代的对象,在JVM中会采取不同的垃圾回收算法。年轻代的对象一般都是朝生暮死的,创建之后很快就会被回收,而老年代的对象是需要长期存活的,因此用到的算法大不相同。新生代对应的收集方法为“Minor GC”,老年代对应的收集方法称为“Major GC”,而对于整个堆空间和方法区的回收被称为“Full GC”
🧃标记清除算法
标记清除算法为最基础的垃圾收集算法,即为每个对象都分配一个标记为,这个标记位会记录对象的状态。标记着所要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以标记存活的对象,清理掉未标记的对象。标记清除算法用于老年代的垃圾回收中。
🥫复制算法
复制算法被用于新生代的垃圾回收机制中,新生代有三部分,Eden(80%),和两个survivor区(From Survivor 和 To Survivor)。两个Survivor区为容量大小相等的两块内存,每次只使用其中的一块内存,当使用的那块内存用完后,就会将内存中还存活着的对象复制到另一块内存上,然后把使用过的那块内存空间清空。
扩展:
90%以上的对象都是朝生暮死的,所以在新生代中,每次为对象分配内存时会使用Eden区和其中的一块Survivor区,当发生垃圾回收时,JVM会将Eden和Survivor中存活的对象都复制到另一块Survivor区域内,之后清理掉Eden区和Survivor区域中的空间。综上所述,建立新对象时,新生代可用内存空间为整个儿新生代容量的90%(80%的Eden区和10%的Survivor区),如果发生了极少部分情况,即多于10%的对象存活下来了,没有被垃圾回收器回收掉,此时JVM会触发空间担保机制,即当Survivor空间不足以容纳一次Minor GC后的存活对象时,就需要依赖老年代进行分配担保。
🥓标记整理算法
标记整理法是对标记清除算法的一个改进。第一个阶段和标记清除算法一样,都是将对象标记为存活和死亡状态,然而在第二阶段,标记清除算法只是将被标记的对象进行清除,标记整理算法会将存活的对象进行整理并且放到另一个端,然后再把所有的对象清除掉。
标记整理算法用于老年代的回收机制中。
🍨如何判断变量是否线程安全
对于成员变量和静态变量
- 如果它们没有被共享,则它们是线程安全的;
- 如果它们被共享了,根据它们的状态是否能够改变,又会分两种情况:如果只有读操作,则它们是线程安全的;如果有读写操作,则这段代码是临界区,是需要考虑线程安全的。
对于局部变量是否线程安全
- 局部变量是线程安全的
- 但局部变量引用的对象则未必线程安全。如果该对象没有逃离方法的作用访问,它是线程安全的;如果该对象逃离方法的作用范围,则是需要考虑线程安全的。
🍻最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
思路:该题让求出最长的递增子序列,因此至少需要一次遍历,考虑到代码进行到每一步的状态才可以,所以动态规划法解决此题比较容易。
代码+详解:
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length==0){
return 0; //长度为0直接返回0
}
int [] dp=new int[nums.length];
dp[0]=1; //初始化数组,也可以调用库函数Arrays.fill(dp,1),但是效率会慢些
int result=1;//初始化结果
for(int i=1;i<nums.length;i++){
dp[i]=1;//初始化数组
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=Math.max(dp[i],dp[j]+1);//循环更新dp[i]的最大值
}
}
result=Math.max(result,dp[i]);
}
return result;
}
}