JVM中哪些是线程共享区
线程共享的:
- 方法区:主要存储类的信息。
- 堆:主要存储类所产生的各个对象。
线程独有的:
- 虚拟机栈:存储某一个线程在执行过程中执行了哪些方法。一个栈有很多栈帧,每个栈帧就表示线程执行了哪些方法。
- 程序计数器:每一个线程在执行方法的时候,程序计数器会告诉线程需要执行哪一行的代码。所以必然需要每个线程独有。
- 本地方法栈:类似于虚拟机栈,区别是虚拟机栈执行Java方法服务,而本地方法栈执行Native方法服务。
项目如何排查JVM问题?
对于还在正常运行的系统
- 可以使用jmap来查看JVM中各个区域的使用情况。 可以通过jstack来查看线程的运行情况,比如哪些线程阻塞、是否发生了死锁。
- 可以通过jstat来查看垃圾回收的情况,特别是fullgc,如果发现fullgc比较频繁,那么就需要进行调优了。
- 通过各个命令的结果,或者jvisualvm等工具进行分析。
- 首先初步猜测频繁发生fullgc的原因,如果频繁的发生fullgc但一直没有出现内存溢出的情况,那表示fullgc实际上确实回收了很多对象,所以这些对象最好在younggc的过程中就直接回收掉,避免这些对象进入老年代,所以对于这种情况,需要考虑这些存活不长的对象是否比较大,导致年轻代存放不下直接进入了老年代,尝试加大年轻代的大小,如果改完后fullgc减少,则说明思路正确。
- 同时还可以找到占用CPU最多的线程,定位到具体的方法,优化方法的执行,同时尝试避免某些对象的生成,从而节省内存。
对于已经出现OOM的系统
- 一般生产系统中都会设置当系统发生OOM时,生成当时的dump文件(-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/base)。- 然后工具jvisualvm等工具来分析dump文件。
- 根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位具体的代码,然后再进行详细的分析和调试。
一个对象从加载到JVM,再到被GC清除,都经历了什么过程?
- 首先字节码文件(.class文件)内容加载到方法区。
- 然后再根据类信息在堆区创建对象。
- 对象首先会分配在堆区中年轻代的Eden区,经过一次Minor GC后,就会进入Suvivor区。在后续的每次Minor GC中,如果对象一直存活,就会在Suvivor区来回拷贝,每移动一次,年龄加1。
- 当年龄超过15后,对象依旧存活,对象就会进入老年代。
- 如果经过Full GC,被标记为垃圾对象,那么就会被GC线程清理掉。
怎么确定一个对象到底是不是垃圾?
- 引用计数算法:这种方法就是给堆内存中的每个对象记录一个引用个数。引用个数为0的就认为是垃圾。这是早期JDK中使用的方法。这种方法无法解决循环引用的问题(两个对象互相引用)。
- 可达性算法:这种方式在内存中,从根对象向下一直找引用,找到的对象就不是垃圾,没找到的对象就是垃圾。
JVM中有哪些垃圾回收算法?
标记清除算法(这种算法比较简单,但会产生大量的内存碎片):
- 标记阶段:把垃圾内存标记出来
- 清除阶段:直接将垃圾内存进行回收
复制算法:
为了解决标记清除算法的内存碎片问题,就产生了复制算法。复制算法将内存分为了大小相等的两个区域,每次只使用一半。垃圾回收时,将当前这一块的存活对象全部拷贝到另一半中,然后当前这一半内存就可以直接清除。这种算法没有内存碎片,但是过于浪费空间。而且,他的效率根存活的对象个数有关。
标记整理算法:
为了解决复制算法的缺陷,就提出了标记整理算法。这中算法在标记阶段和标记清除算法一样,但是在标记完成之后,不是直接清除垃圾内存,而是将存活对象往一端进行移动,然后将边界以外的所有内存直接清除。