首相我们要知道jvm的一个内存图:
上图就是虚拟机的内存图:
内存区域分为本地方法栈、虚拟机栈、堆、程序计数器、方法区等,方法区又被称作永久代。
栈区的底部是先执行的主线程,每个栈去多是一个线程,所以在报错的时候我们可以到如下图的一个报错信息顺序!
从上图我们可以看到主线程main方法是在最下面的,其他的相关的执行线程多是在上面的!这个只是一个栈去的一个内存理解!
下面讲下jvm怎么找到垃圾并且清理垃圾!
因为java版本的不同jvm内部有些东西也会改变,比如一些永久代报错信息的不同有兴趣了解不同版本jvm的一个内存报错信息的
可以打开这个连接看下,https://blog.csdn.net/dingpiao190/article/details/72831125
个人见解,要想理解清jvm的内存和清理机制建议是手写一个jvm虚拟机,附上个人觉得不错的书籍,
jvm的皇帝就是gc它负责回收垃圾它还有一个别称叫环卫个工人!
jvm以前的找垃圾的方法用的一个算法是:
1:引用技术法:
第一个对象被引用一次那么它的对象计数就+1,退出作用域那么就就减-1;
Object obj=new Object();//new Object();是一个对象,obj就是一个引用+1;
这个算法有一个缺点就是两个傻逼对象互相引用,但是程序中又没有使用这个2个傻逼对象,要是这种写法多了就会产生
内存泄露,内存泄露会在下面讲,
2.根搜索算法:
这个算法其实就是为了解决上面那种傻逼对象的一个存在,那么这个算法是怎么实现的呢?
1.虚拟机栈(栈帧中的本地变量表)中的引用对象。
2.方法区中的类静态属性引用的对象。
3.方法区中的常量引用的对象。
4.本地方法栈中JNI(也即一般说的Native方法)的引用的对象。
用太专业的词汇我一时描述不出!简单来讲就是上面这些去里面引用的对象,会产生一个跟路径,这个路径会把相关的
引用联起来,那么判断一个内存的资源是不是垃圾就是很简单了,想那种没有根的傻逼相互关联,就会被认定为垃圾了!
gc皇帝就会干掉它,
既然找垃圾有方法那么清楚垃圾肯定也有相关的方法!
1:标记清除法:
只要发现垃圾内存时,标记为可回收的状态,等下一起干掉!
但是有个缺点就让人很受不了,清理的内存空间多是零碎的,哪么当程序需要内存时就会遇到一个很尬尴的事情,我需要一个5M的内存,你清理的这个小地方只有3M那么放不下啊!那么的改进方法啊!
2:分段复制算法:
上图是分段复制算法的一个处理前后图!
复制算法原理 :
Survivor区,一块叫From,一块叫To,对象存在Eden和From块。当进行GC时,Eden存活的对象全移到To块,而From中,存活的对象按年龄值确定去向,当达到一定值(年龄阈值,通过-XX:MaxTenuringThreshold可设置)的对象会移到年老代中,没有达到值的复制到To区,经过GC后,Eden和From被清空。
之后,From和To交换角色,新的From即为原来的To块,新的To块即为原来的From块,且新的To块中对象年龄加1.
疑问:
只分一块Survivor区,当进行GC时,先将Survivor区中存活的对象达到年龄值的移入年老代,清除已死亡的对象,后将Eden区中存活的对象移入Survivor区,将Survivor区的对象年龄都加1.这样与分两块Survivor有什么区别呢,为什么虚拟机一定要分成两块呢?
即若只分一块Survivor,在清除Survivor区已死亡的对象时,因为此刻的Survivor区还有存活的对象,清除要比分两块Survivor麻烦,两块的情况,回收时只需将存活的对象移走,剩下的对象直接清理即可。
另外,分成两块Survivor,From和To分工明确,逻辑理解和技术实现较简单
3:标记整理算法:
于分段复制差不多!
4:分代收集法1:(方便管理)
新建的对象称为新生代,当被回收几次后便分化为老年区(经过几次回收后一直在使用),如果老年区又再过n次回收便划分为永久代
新生代扫描频率高,老年区小,永久代是频率最低(目的是提高性能)
现在的jdk基本多是使用这个!
6:内存泄漏
本该被回收的内存,遗漏了,一直占用(程序运行一段时候后很卡有可能就是内存泄漏了)
7:内存溢出
分配给进程的内存满了
我们可以设置这个程序的内存最小多少内存,最大多少内存
堆内存溢出:
7.1.堆内存溢出:
OutOfMemoryError: Java heap space
7.2.方法区(永久代)溢出:
OutOfMemoryError: Metaspace
7.3.栈内存溢出:
StackOverflowError