堆和栈的区别:
1)栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
2)堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。堆和栈有什么区别?
1、申请方式的不同。栈由系统自动分配,而堆是人为申请开辟;2、申请大小的不同。栈获得的空间较小,而堆获得的空间较大;
3.申请效率的不同。栈由系统自动分配,速度较快,而堆一般速度比较慢;
4.存储内容的不同。栈在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数的各个参数进栈,其中静态变量是不入栈的。而堆一般是在头部用一个字节存放堆的大小,堆中的具体内容是人为安排;
5.底层不同。栈是连续的空间,而堆是不连续的空间。
什么时候需要用到索引?
1.为经常出现order by 、group by、distinct后的字段添加索引2、在union等集合操作的结果集字段上建立索引
3、经常做查询的字段建立索引
4、经常用在表连接上的字段建立索引
1)标记-清除:
1)标记阶段就是可达性分析的过程,清除就是直接释放内存空间,来进行标记一些将要被回收的对象,白色是正在使用的对象,灰色是已经被释放的空间(之前灰色的部分时不再继续进行使用的对象,之前是白色的,对应的对象的资源被释放之后就显示成灰色的了,我们就表示灰色的部分是已经被释放的内存),也就是说,如果我们进行直接释放内存,虽然内存是直接换给系统了,但是被释放的内存是离散的,不是连续的,是分散开的,这样就会导致内存碎片
2)虽然可以释放不用的内存空间,但是此时会出现一个严重的问题:内存碎片,此时空闲的内存和正在使用的内存,是交替出现的,虽然我们把内存还给系统了,但是被释放的内存不是是连续的,而是离散的,这就会出现内存碎片问题
3)看起来内存很多,但是可用的内存资源并不多,空闲的内存有很多,假设一块内存空间是1G,如果说我们想要申请500M的内存,也是有可能申请失败的,因为我们要进行申请的500M内存是连续内存,我们每一次进行申请,申请的都是连续的内存空间,因为这里面的1G,可能是多个内存碎片加在一起,才是1G,但是可用的并不多,非常影响程序的执行
3)如果想要分一小块(直接把讲题出现的一小块内存给你),还行;
4)但是很多时候,申请的内存,是一块连续的内存空间,但是如果想要申请一个较大的连续内存空间,new Array[10000],整个系统空间,空闲100M,但是此时如果想要申请50M的内存空间,仍然可能分配失败,每10M是一个单位,分散在各个地方,是不连续;
系统看似有好多内存,但是分配不出来,在频繁申请释放内存,是非常严重的,这个操作其实本质上是非常影响程序的效率的
2)复制算法:有效的解决内存碎片问题,内存空间只用一半
直接把不是垃圾的,拷贝到另一块区域,把原来的这个整体的空间都释放掉,这个时候我们就可以保证左侧和右侧的空间都是连续的

1)当前我们只需要使用左边这儿一大块内存,接下来1,3要被回收了,这时就会把剩下的2和4拷贝到另外一侧,这个时候我们会在右侧开辟一块内存空间来进行存放2,4区域,然后再回收1234这一整块空间,全部回收(2,4已经在右侧复制了一块内存)
2)没有进行回收之前,相当于是左侧有1234内存,右侧有2,4内存,进行回收之后,左侧的内存全部被清空(1234),右侧剩下了2,4内存,此时左侧就是一块连续的内存空间了,右侧也是一块连续的内存空间,我们此时就有效地解决了标记清除问题,我们在这个过程中,虽然地址会发生变化,但是JVM内部肯定会保证你用户端持有的引用可以找到对应的对象的(对象搬运是JVM搬运的,地址也是JVM维护的,引用也是JVM返回给你的,这是JVM内部实现的
复制算法的缺点: 1)可用内存空间只有一半 2)在另一半内存区域里面,如果需要回收的对象比较少,剩下的对象比较多,复制的开销成本就比较大了 所它适用于:对象可以被快速回收,整体内存不大的情况下3)标记整理:
优点:是为了解决复制算法引入的问题(可用内存空间是全部)也解决了标记清除的内存碎片的问题,解决内存空间利用率较低的问题;
工作过程:这是类似于顺序表删除工作的搬运,既可以解决内存碎片,向前填充空闲的内存,是可以很好的避免内存碎片,也可以解决空间利用率,比复制算法开销很大(移动元素),这个方法方案我们的空间利用率是变高了,但是我们任然没有很好的解决复制算法搬运元素开销大的问题
上面中的三种垃圾回收机制,虽然可以解决问题,都有着自己的缺陷,所以我们的垃圾回收单独使用上面的一种,都是有着自己的缺陷的,所以在JAVA的垃圾回收机制中,我们要把上面的方案结合起来使用
4)分代回收:
我们把内存中的对象根据年龄分成了不同的类别,每种情况下,要采用不同的回收算法;
我们是根据对象的年龄去进行划分的,我们还要使用空间存储年龄,就是在Object对象头里面
4.1)在JVM中进行垃圾回收扫描,也就是可达性分析,也是周期性的;每一个对象经历了一次GC扫描,就认为长了一岁,我们就根据根据这个对象的年龄,来对整个内存进行分类;
4.2)把年龄短的对象放在一起,年龄长的对象放在一起,不同年龄的内存就可以采用不同的垃圾回收算法来进行处理;
整个堆内存分成三个区域:
1)新生代,伊甸区:刚刚new出来的新鲜对象就放在这里(hr收到的简历)
2)生存区,幸存区:放一些不是很新鲜的对象,也不是一些很老的对象(通过到简历筛选,进入到笔试)
3)老年代:年龄比较大的对象
分代回收的过程:
1)刚新建了一个新的对象(new),就放在伊甸区;
2)如果是活到1岁的对象(伊甸区),对象经历了一轮GC还没有死,就把它拷贝到幸存区里面;
这里要注意一个点,生存去看起来比伊甸区要小很多,空间里可以放得下这些对象吗?
根据经验规律,伊甸区的对象绝大多数都是活不过1岁的,只有少数对象可以活到生存区
对象大部分都是朝生夕死的,大部分对象的生命周期是很短的,真正可以熬过一轮GC的对象并不多;我们把伊甸区拷贝到生存去里面就是运用了复制算法,需要用到的对象放到幸存区里面,然后我们把伊甸区的整块内存区域进行回收
3)在幸存区里面,有两个格子,左侧是A,右侧是B,在幸存区里面,对象也要经历一轮一轮的GC,每一轮GC逃过的对象,都通过复制算法拷贝到另一块生存区里面,对象来会回进行拷贝,每一轮都会淘汰掉一些对象,复制算法在这里面只是使用了一小块内存空间,利用率低这个问题算是解决了,况且对象也很少;(进入到面试环节,每一论面试就要淘汰一些同学),幸存区的对象就会在两个幸存区之间来回进行拷贝,每一轮都会淘汰掉一波幸存者
我们在这里也不要说98%的对象新对象熬不过一轮GC,只有2%的对象可以成功的进入到生存区,这个数字一点也不严谨,不知道对不对;
4)在幸存区里面,熬过一轮轮的GC之后(千万不要说经历了多少论GC,比如说经历了5轮GC,这个数据不可以拍脑门,要严谨,就说经历了若干次GC),仍然屹立不倒,JVM就认为这个对象未来还会更持久的存储下去,这样的对象就把它拷贝到老年代所以我们就针对不同种类的对象,来进行采取不同的方案
5)进入到老年代的对象,JVM都认为这是可以能够持久存在的对象,这些对象也需要通过GC来进行扫描,但是老年代的GC扫描的次数和频率会大大的进行减少;新生代会被扫描的次数更多,因为新生代容易挂,老年代这里面使用的是标记整理算法,一个对象越老,那么继续存活的可能性就越大(要死早死了)(新生代对象能够更挂得快,所以要多次进行扫描)
在老年代里面:里面的所有对象都是比较老的,都是岁数比较大的,年龄比较大,根据基本的经验规律,一个对象越老,继续存活的可能性就会越大;要死早死了
6)老年代的内存空间也是很大的,因为老年代的对象不宜回收,会一直进行累积,有的对象占据的内存比较大,会被直接放到老年区里面;因为这个对象占据的内存比较大,直接放到新生代,拷贝来拷贝去就会开销比较大(不适合使用复制算法)
标记清除算法用的比较少,新生代的对象,存活概率小,所以说复制的内容比较小,开销其实并不大,但是老年代存活概率大,要进行复制的话开销就大了,我们就不可以进行复制
谈谈Java的垃圾回收机制?
找垃圾----可达性分析回收垃圾
算法的优点和缺点
分代回收
我们对照于找工作的过程,再来进行理解一下JAVA中的垃圾回收机制
1)我们先投简历,开始进入到伊甸区,大部分的人都不投简历
2)筛选简历的过程,就相当于就进入到了伊甸区,又有大部分的人被筛选掉
3)经过简历筛选之后,接下来会有几轮笔试和面试,这是不确定的,通过简历筛选就相当于是从伊甸区进入到了幸存区
4)经历过重重筛选,相当于此时就进入到老年代了
5)那么我们进入到老年代之后,也就是说我们进入到公司之后,也不时就有那样的保险箱,而是公司也是有末位淘汰机制的,干得不好就会被淘汰,只不是公司的考核是一年或者半年,这个时候频率就会大大地降低了
6)咱们找工作,就是相当于是要经历多种笔试面试(幸存区复制算法来回拷贝),但是有些人是由绝对的优势的,他爸就是开公司的,直接就可以进入到公司;
咱们的这个垃圾回收,是在不断地向前发展,上面说的找垃圾和释放垃圾不是具体的落地实现,在我们的JVM里面,真正的实现上述算法的模块被称之为垃圾回收器,常见的垃圾回收器:
串行收集:这种方式非常不友好,也产生严重的STW
1)Seria收集器:这是新生代回收器
2)Seria Old回收器:这是老年代的回收器
上面的这两个回收器是进行串行执行的,在进行垃圾的扫描和释放的时候,咱们的业务线程要停止工作,这种方式扫描的慢,释放的也慢,况且还会产生严重的STW问题(串行化),扫描完成之后,才可以进行工作
这一组比上一组好点,多线程扫描,多线程的方式回收,速度会更快
1)ParNew收集器
Parallel Scavenge:里面调制了一些参数,控制了STW的时间本质上是不变的,只是增强了一些功能
上面两个收集器是新生代收集器
2)Parallel Old:收集器是老年代收集器
上面是比较老的垃圾回收器,下面是新生的垃圾回收器
1)CMS:它设计的比较巧妙,设计的初衷就是为了尽可能的让STW的时间比较短,就是为了等待时间短而服务的
1.1)初始标记:速度很快,但是可能会引起短暂的STW问题,只是找到GCroots
1.2)并发标记:虽然速度慢,但是可以和业务线程进行并发执行,不会产生STW问题
1.3)重新标记:这是因为业务代码可能会影响到并发标记的结果,针对第二步的结果进行微调,虽然会引起STW问题,但是只是微调,速度会很快(第二步可能说并发标记的时候标记错了本来这个对象是一个垃圾,但是业务线程并发执行的过程中业务线程处理的时候可能又会把它判定成垃圾了),虽然他会短暂的引起STW但是只是微调,速度快
1.4)回收内存:但是也是和业务线程并发执行
前三个步骤都是可达性分析,虽然会产生STW,在我们的步骤1和步骤3,但是做的事情少,速度快,影响就小
2)G1垃圾回收器(进程调度开销这样的一个量级,G1可以在当下可以优化到STW停顿时间小于1ms)
我们的这个G1把整个内存划分成了很多小的区域,每一个区域称之为Regin,我们把这些Region进行了不同的标记,有的Regin放新生代对象,有的Regin放老年代对象,然后我们再次进行扫描的时候,一次扫若干个Regin(我们不是一轮GC就扫描完,我们分多次来进行扫描,这样对于业务代码影响是最小的),可能扫完7,8轮才有可能扫描完,每一轮GC都扫描的足够快,每一轮GC影响都足够小,所以这种设计对业务代码影响最小,GC在当前可以让STW停段时间小于1ms,也就是进程进行上下文切换的这样一个速度
核心思路就是化整为0,只要我们让每一次的STW的时间足够短就可以了
第一步:Win+R 打开命令提示符,输入netstat -ano|findstr 端口号 找到占用端口的进程
第二步: 杀死使用该端口的进程,输入taskkill /t /f /im 进程号( !!!注意是进程号,不是端口号)
