java 半解释半编译型语言
通过解释字节码为机器码进行执行,同时jit把热点代码进行编译成机器码执行提高性能
java类加载机制/Java类加载器的加载过程是什么样的??
类加载常见错误总结,写得非常好!_类加载器验证阶段出现报错会怎么样?_码农突围的博客-CSDN博客
我们把 Java 的类加载过程分为三个主要步骤:加载、链接、初始化。
首先是加载阶段,加载字节码到jvm。
第二阶段是链接,验证规范、分配内存。
最后是初始化阶段,初始化变量。
什么是类加载?类加载器?
让应用程序能够根据完整路径获取到类的字节流,执行这个动作的代码模块叫做类加载器。
bootstrap classloader
ext classloader
app classloader
双亲委派模型
1、当需要加载类时,会优先委派当前所在的类的加载器的父加载器去加载这个类。
2、如果父加载器无法加载到这个类时,再尝试在当前所在的类的加载器中加载这个类。
描述下JVM内存模型,哪些可能发生OOM?
分为两大类,一类书线程的私有区域,另一类是线程的共享区。
线程私有区域,
线程的共享区,
堆(Heap),它是 Java 内存管理的核心区域,用来放置 Java 对象实例,分老年代新生代是垃圾回收的区域。
方法区,存储所谓的元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等。由于 Hotspot JVM 实现,方法区也称为永久代(Permanent Generation)。JDK 1.8 中将永久代移除,同时增加了元数据区(Metaspace)。
字符串常量池(StringTable),存储不重复的字符串,先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,如果不存在,初始化,并将该字符串放入字符创常量池中。拼接字符串时候,会拷贝新的备份进行拼接。
String str = “abc”;可能创建一个或者不创建对象,如果”abc”在字符串池中不存在,会在java字符串池中创建一个String对象(”abc”),然后str指向这个内存地址;
String str = new String(“abc”);至少会创建一个对象,也有可能创建两个。因为用到new关键字,肯定会在堆中创建一个String对象,如果字符池中已经存在”abc”,则不会在字符串池中创建一个String对象,如果不存在,则会在字符串常量池中也创建一个对象。
什么是内存泄漏、内存溢出及例子?
内存泄漏(memory leak),有些对象的内存无法进行回收。例如 数据库连接没有手动close,会导致没法显示回收,直到线程销毁才会被回收。
内存溢出(OOM),程序分配内存,不够用的时候就是内存溢出。
OOM排查思路和过程/如何监控和诊断JVM堆内和堆外内存使用?
jmap -dump堆栈的快照,再使用MAT(内存分析工具(Memory Analysis Tool)进行分析。
- 堆内存不足是最常见的 OOM 原因之一,抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”,原因可能千奇百怪,例如,可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定 JVM 堆大小或者指定数值偏小;或者出现 JVM 处理引用不及时,导致堆积起来,内存无法释放等。
- 而对于 Java 虚拟机栈和本地方法栈,这里要稍微复杂一点。如果我们写一段程序不断的进行递归调用,而且没有退出条件,就会导致不断地进行压栈。JVM 实际会抛出 StackOverFlowError,然后,如果 JVM 试图去扩展栈空间的的时候失败,则会抛出 OutOfMemoryError。
- xxxxxxxxxx class TryUsingAnonymousClass$1 implements MyInterface { private final TryUsingAnonymousClass this$0; private final Integer paramInteger; TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) { this.this$0 = this$0; this.paramInteger = paramInteger; } public void doSomething() { System.out.println(this.paramInteger); }}java
- 直接内存不足,也会导致 OOM
讲一下你知道的垃圾回收算法?
判别阶段的算法和清除阶段的算法
判别阶段的算法/对象什么时候可以被垃圾回收?
判断对象是否存活,可以使用引用计数器或者可达性分析两种方法。
引用计数器:
当引用计数器为零的时候,表明没用引用再指向该对象,但不能解决循环引用的情况,导致内存泄漏;
可达性分析:
通过一些GC Roots对象(方法中的参数、局部变量等)去判断可达性,判断是否有关联。
GCRoots有哪些?
虚拟机中栈中引用的对象(方法中的参数、局部变量)等
清除阶段的算法:
1.标记-清除
首先标记出所有要回收的对象,然后统一进行清除。缺点是会产生大量不连续的内存碎片。(不利于以后分配较大内存的对象,不得不再另一次垃圾回收)
2.复制算法
将内存划分为大小相等的两块,每次只使用其中一块,每当其中一块的内存用完了,就将还存活的对象复制到另一块,然后把原来的一块内存整体进行回收。(优点是没有碎片的问题,但是缺点是可利用空间只有原来的一半)
3.标记-整理
标记阶段和标记清除算法相同,然后是让所有存活的对象移动到一端,清理掉另一边的内存。
4.分代回收算法(对不通生命周期的对象采用不同的算法进行收集,效率更好)
一般java堆分为新生代和老年代。
其中,新生代中每次垃圾回收都有大量对象死去,只活下小部分,适合复制算法。
而老年代中的对象存活率高,没有额外空间可以分配。必须使用标记清除或者标记整理算法。
垃圾收集过程/对象分配过程?
https://time.geekbang.org/column/article/10513
首先:堆划分成新生代和老年代,其中新生代Eden区和Survivor区的比例是8:1:1。
第一,Java 应用不断创建对象,通常都是分配在 Eden 区域,当其空间占用达到一定阈值时,触发 minor GC。仍然被引用的对象(绿色方块)存活下来,被复制到 JVM 选择的 Survivor 区域,而没有被引用的对象(黄色方块)则被回收。
第二,经过一次 Minor GC,Eden 就会空闲下来,直到再次达到 Minor GC 触发条件,这时候,另外一个 Survivor 区域则会成为 to 区域,Eden 区域的存活对象和 From 区域对象,都会被复制到 to 区域,并且存活的年龄计数会被加 1。
第三, 类似第二步的过程会发生很多次,直到有对象年龄计数达到阈值,这时候就会发生所谓的晋升过程,如下图所示,超过阈值的对象会被晋升到老年代。这个阈值是可以通过参数指定。
后面就是老年代 GC,具体取决于选择的 GC 选项,对应不同的算法(比如对老年代进行标记清除算法)。
youngGC什么时候触发
对象优先在eden区分配,eden区没有足够区域分配时候,会进行youngGC
youngGC和老年代的gc方式
eden->s0、s1-》晋升老年代巴拉巴拉。
YGC(MinorGC)、MajorGC、FGC是什么
YGC (MinorGC):对新生代堆进行gc。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。
MajorGC :对老年代的GC
FGC :全堆范围的gc。默认堆空间使用到达80%(可调整)的时候会触发fgc。以我们生产环境为例,一般比较少会触发fgc,有时10天或一周左右会有一次。
System.gc和finalize区别
System.gc():建议jvm执行一次fullGC
finalize:finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
垃圾回收器:
Parallel GC: JDK 8 的默认 GC ,也被称作是吞吐量优先的 GC。其特点是新生代和老年代 GC 都是并行进行的。
CMS(Concurrent Mark and Sweep):GC线程和用户线程并发进行的垃圾回收器,特点是低延迟,对老年代主要使用并发 mark-sweep (标记-清除)算法,也就是说,在这些阶段并没有明显的应用线程暂停。使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。
有四个过程:
1.初始标记 (仅仅只是标记一下GCRoots能直接关联到的对象,速度很快)(STW) 开启写屏障
2.并发标记(从GC Roots的直接关联对象开始遍历整个对象图的过程) 三色标记法(写屏障,当对象新增或更新时,会将其着色为灰色)
三色标记法:对象由白->灰->黑
白色:尚未访问过。
灰色:本对象已访问过,但是本对象 引用到 的其他对象 尚未全部访问完。全部访问后,会转换为黑色。
黑色:本对象已访问过,而且本对象 引用到 的其他对象 也全部访问过了。
最后收集剩下的白色
3.重新标记(STW)修正第2步,扫描期间产生的新的垃圾,处理漏标的对象 关闭写屏障
4.并发清除 标记-清除算法
其中初始标记和重新标记会触发共2次STW,因为使用的是标记-清除算法,原有还在使用的活的对象不需要变动,所以可以并发执行,不需要STW。https://blog.csdn.net/zqz_zqz/article/details/70568819
缺点:
1.CMS收集器对CPU资源非常敏感。比如cpu不足4个的时候,会分出一半线程用来垃圾回收,压力比较大。
2.CMS收集器无法处理浮动垃圾。由于是并发执行的,在一次垃圾回收过程中,标记阶段后用户线程产生的新的垃圾无法在这次垃圾回收中被一起处理。
3.基于标记-清除算法,大量内存碎片。这个可以通过设置参数来缓解。
STW?
指的是GC事件发生过程中,会产生应用程序的停顿。
为什么 CMS两次标记时要 stop the world?
垃圾回收并不会阻塞我们程序的线程,他是与当前程序并发执行的。问题就出在这里,当GC线程标记好了一个对象的时候,此时我们程序的线程又将该对象重新加入了“关系网”中,当执行二次标记的时候,该对象也没有重写finalize()方法,因此回收的时候就会回收这个不该回收的对象,所以JVM在一些特定指令位置设置一些“安全点”,当程序运行到这些“安全点”的时候就会暂停所有当前运行的线程(Stop The World 所以叫STW),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除。
G1(Garbage-First):同样是GC线程和用户线程并发进行的垃圾回收器,但是有一个region的概念来弱化分代的概念,把内存化整为零来进行处理,类似于标记-整理,会维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region,相当于只回收部分内存,所以提高了回收效率。
年轻代的gc是Minor GC ,老年代的gc是Mixed GC。(Mixed GC 不仅同时会清理 Eden、Survivor 区域,而且还会清理部分 Old 区域。)
RSet(每个region都有的一块区域,用于记录其他region引用当前region)
CSet(本次GC需要垃圾回收的集合)
SATB(snapshot-at-the-beginning) 保证在并发标记的正确性,刚开始做一个快照,当 B 和 C 消失的时候要把这个引用推到 GC 的堆栈,保证 C 还能被 GC 扫描到,最重要的是要把这个引用推到 GC 的堆栈,是灰色对象指向白色的引用,如果一旦某一个引用消失掉了,我会把它放到栈(GC 方法运行时数据也是来自栈中),我其实还是能找到它的,我下回直接扫描他就行了,那样白色就不会漏标。
https://www.bilibili.com/video/BV13J411g7A1?from=search&seid=415269689213584436
1.初始标记(STW)仅仅只是标记一下 GC Roots 能直接关联到的对象。
2.并发标记 三色标记法 从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象。
3.最终标记(STW)用于处理并发阶段结后仍遗留下来的最后那少量的 SATB 记录(漏标对象)。
4.筛选回收(STW) 负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
CMS和G1的两者的区别?
1.回收的位置不同,CMS回收老年代,而G1回收的是年轻代和老年代
2.垃圾回收算法不同,CMS是标记-清除算法,而G1是标记-整理算法
3.CMS会产生内存碎片,G1不会产生内存碎片
4.G1可以预测STW的时长,CMS不可以。
监控命令和工具?
命令:
jps
jstack
jmap
jstat:监控gc情况,看YGC和FGC
jinfo:查看jvm参数
工具:
visualVM
MAT 分析dump文件
Arthas
参数?
-Xms
-Xmx
+PrintGCDetails:打印gc日志
你了解逃逸分析吗
作用是:Java Hotspot编译器能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上,进一步减少gc。
原理是:当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。,当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。例如作为调用参数传递到其他地方中。
优点是:
GC调优目的/为什么要调优?
1.预防和处理程序中的OOM问题
2.减少因为FGC导致的卡顿、运行慢的情况。
**GC调优步骤? **
1.通过性能监控发现问题:
2.性能分析:
3.性能调优:
调优经验?
自己维护的一个confluence项目,线上访问延迟非常严重,top命令观察到java进程的cpu利用率居高不下,jstat 进程号1000 实时监控垃圾回收异常,发现频繁发生FGC,通过调大Xms和Xmx参数后,最后重启confluence。解决了问题。最佳实践:60%~80%
CPU突然飙高了怎么排查呢?
top -Hp pid 查看找哪个线程的cpu使用率高
jstack 进程号 找到对应的线程 ---进制转换
VMThread
,则通过 jstat-gcutil<pid><period><times>
命令监控当前系统的GC状况,然后通过 jmap dump(jmapdump:format=b,file=<filepath><pid>
)导出系统当前的内存数据。导出之后将内存情况放到eclipse的mat工具中进行分析即可得出内存中主要是什么对象比较消耗内存,进而可以处理相关代码;jstack
也可以查看到死锁状态具体到哪一行。如果线上cpu占比特别低,但是服务负载特别高?
load高但是cpu占用率低的排查 - 51core - 博客园 (cnblogs.com)
load 呈现的是CPU等待处理的请求队列的压力,load太高表明有太多请求等待处理
排查方向: 线程数过多 部分线程每秒上下文切换次数过高 先排查主要原因,即部分线程上下文切换次数过高 拉一下线上该进程的堆栈信息,然后找到切换次数达到100+/每秒的线程id,把线程id转成16进制后在堆栈日志中检索
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。