From 0f2ae003aa1e435411c76af7aaa8b1a1027549d3 Mon Sep 17 00:00:00 2001 From: plumbin Date: Mon, 16 Jun 2025 15:49:21 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...45\215\201\345\244\247\345\273\272\350\256\256.md" | 11 +++++++++-- ...47\250\213\345\221\250\346\212\245\344\272\214.md" | 5 ++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git "a/\347\250\213\345\272\217\344\272\272\347\224\237&\351\235\242\350\257\225\345\273\272\350\256\256/\351\207\221\344\270\211\351\223\266\345\233\233\357\274\214\347\273\231\351\235\242\350\257\225\350\200\205\347\232\204\345\215\201\345\244\247\345\273\272\350\256\256.md" "b/\347\250\213\345\272\217\344\272\272\347\224\237&\351\235\242\350\257\225\345\273\272\350\256\256/\351\207\221\344\270\211\351\223\266\345\233\233\357\274\214\347\273\231\351\235\242\350\257\225\350\200\205\347\232\204\345\215\201\345\244\247\345\273\272\350\256\256.md" index 6909f79..79673b9 100644 --- "a/\347\250\213\345\272\217\344\272\272\347\224\237&\351\235\242\350\257\225\345\273\272\350\256\256/\351\207\221\344\270\211\351\223\266\345\233\233\357\274\214\347\273\231\351\235\242\350\257\225\350\200\205\347\232\204\345\215\201\345\244\247\345\273\272\350\256\256.md" +++ "b/\347\250\213\345\272\217\344\272\272\347\224\237&\351\235\242\350\257\225\345\273\272\350\256\256/\351\207\221\344\270\211\351\223\266\345\233\233\357\274\214\347\273\231\351\235\242\350\257\225\350\200\205\347\232\204\345\215\201\345\244\247\345\273\272\350\256\256.md" @@ -1,4 +1,5 @@ ### 一、提前复习好你的专业知识 + 专业知识是最为重要的一点,拥有了坚实的专业基础,你才能迈向成功的彼岸。 因此,面试之前,一定一定要复习好专业知识。对自己学过的知识,要做一个概括,放在脑海中。茶余饭后,复习一下,做到随便看到一道基础题目,心中都能有个答案。 @@ -9,6 +10,7 @@ ![](https://user-gold-cdn.xitu.io/2020/2/14/17043f9e65f6c1a2?w=1919&h=1080&f=png&s=1153224) ### 二、对简历上写的项目一定要足够了解,把握其中的亮点。 + 你在简历上的信息,就是面试官了解你的窗口。你写上去的项目,自己一定一定要了解清楚来龙去脉。如果把别人很厉害的项目copy上去,面试官一问你三不知,那就露馅啦~ 同时,简历上需要沉淀一些有内容的东西,需要有些亮点。当然,简历上的亮点并不一定是酝酿百年的女儿红,也可以是你自己含辛茹苦酿造出的米酒,只要有你汗水的味道体现在里面就可以啦。 @@ -19,8 +21,8 @@ [CAS乐观锁解决并发问题的一次实践](https://juejin.im/post/5d0616ade51d457756536791) - ### 三、面试一定要杜绝过度紧张,要心平气和去对待。 + 面试过程一定要杜绝过度紧张,紧张可能会导致你发挥失常,让你基础的问题都忘了怎么回答,最后与理想offer失之交臂。 有点小紧张也是可以接受的,这一点会促使你认真地准备,但是过度紧张就适得其反啦。 @@ -29,7 +31,9 @@ 平时如果觉得生活压抑,或者紧张的话,我会弹弹吉他,唱唱歌,哈哈 ![](https://user-gold-cdn.xitu.io/2020/2/14/17043f1531a9fc6e?w=1080&h=2248&f=png&s=580499) + ### 四、面试前充分了解公司以及工作岗位内容 + 面试前,多点了解公司是做什么业务的,以及工作岗位的主要工作内容。结合招聘要求,提前想一下面试官可能问的问题,换位思考以及延伸思考。比如,你面的是一间银行的开发岗,该银行用到自研的MQ通讯,那么,你需要准备好https相关的面试题,消息中间件的相关面试题等等。 如:https原理是什么?谈谈RocketMQ消息顺序和重复消费问题等。 @@ -50,7 +54,6 @@ 如果你要面一个大厂,有认识的人内推最好不过啦,其实内推过得概率大很多的。因为,大家都懂一个道理,优秀的人旁边,也是一些优秀的人,正所谓**物以类聚,人以群分**。 所以,多数HR也是这样挑人的,如果你通过内推获得面试机会,好好表现吧,骚年。 - ![](https://user-gold-cdn.xitu.io/2020/2/14/1704426243d9ab07?w=255&h=255&f=png&s=104800) ### 七、在工作时一点一字积累,在面试时,一字一词表现。 @@ -60,15 +63,19 @@ 比如,一下是我工作中代码细节的总结,有兴趣可以看看哈~ [写代码有这些想法,同事才不会认为你是复制粘贴程序员](https://juejin.im/post/5dfe2e72518825125f39a2de) + ### 八、面试尽量拿多几个offer,这样才能掌握主动权,不然HR可能会压榨你。 + 面试找工作,对待offer。需要吃着碗里的,看着锅里的。要不然,如果你只有一个offer,HR跟你谈薪的时候,很可能会压榨你的价值。拿多几个offer,可以有谈薪的资本。 ![](https://user-gold-cdn.xitu.io/2020/2/14/1704423b8e7982ac?w=800&h=450&f=png&s=613615) ### 九、多点刷专业笔试题,程序员的话,争取成为leetcode的常客。 + 阿里、腾讯、头条这些公司,面试经常要求手动写编程题,所以,作为面试者,要想过关,一定需要多点刷题,尤其是leetcode官网上面的题,不求每道题都能背下来,但是至少,每种类型的题目,你都需要知道思路,需要知道大概怎么实现吧,如:动态规划问题、链表操作等等。 ### 十、学习一门乐器,坚持一项运动。 + 学习一门乐器,是为了让生活多一份诗意,坚持一项运动,是为了身体健康。 学习一门乐器,在面试官看来会加分的,因为年会可以上去表演哈哈~ 坚持一项运动,也是加分的,因为一般公司都有运动小组,篮球小组,或者羽毛球,如果面试官也跟你一样爱好篮球,说不定,你们就可以聊聊今年湖人总冠军了,哈哈。并且,运动的人最阳光啦,哈哈,不信你看我~ diff --git "a/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\347\274\226\347\250\213\345\221\250\346\212\245\344\272\214.md" "b/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\347\274\226\347\250\213\345\221\250\346\212\245\344\272\214.md" index b5bb492..82e3b5a 100644 --- "a/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\347\274\226\347\250\213\345\221\250\346\212\245\344\272\214.md" +++ "b/\350\214\266\344\275\231\351\245\255\345\220\216\350\260\210\347\274\226\347\250\213\345\255\246\344\271\240\345\221\250\346\212\245/\350\214\266\344\275\231\351\245\255\345\220\216\347\274\226\347\250\213\345\221\250\346\212\245\344\272\214.md" @@ -4,6 +4,7 @@ 当qps在1000的时候,由于一个请求我需要分成三次去请求不同的接口,就相当于有3000个调用接口的请求。 但是请求用的是实时接口,如果该接口响应很慢比如10秒钟,那么这147个线程就全部挂起了,没有空余的线程去处理新的业务了。整个系统的响应就非常慢了。 群上回复答案: + > - 响应很慢的接口走异步,把返回的数据丢到queue里面。 > - 应该是10秒的接口得改 > - 接口慢了的话,看下能不能用redis @@ -14,14 +15,12 @@ ![](https://imgkr2.cn-bj.ufileos.com/5f138381-05f3-4aa2-a344-98e8b970a920.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=yJuf5adRW5I%252BSl3%252FWX1oyVr9%252Ffo%253D&Expires=1603008626) - 其实,如果每个请求30毫秒处理完,可能你的线程池也够用了。但是呢,如果你一个请求,接口处理10s,也就是说10秒,它没干完活,没法接别的活,肯定系统就处理不过来那么多请求啦,所以最后结果就是,一部分请求就会抛弃,因为线程池抛弃策略抛弃啦。回过头,你的系统响应慢,就是你的接口耗时问题呀,你需要优化它。 可以看下我这两篇文章。线程池和如何接口性能优化的~ - - 关于线程池和接口性能优化的,可以看下我之前这两篇文章哈 + - [面试必备:Java线程池解析](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247483728&idx=1&sn=0221dfd5eb7862c0aa749a7038b39307&chksm=9779457fa00ecc69d3bb554ccc1daa8aa204b16b15587759cf1f148bda51907f6f57418e150f&token=1319249232&lang=zh_CN#rd) - [记一次接口性能优化实践总结:优化接口性能的八个建议](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484426&idx=1&sn=7265be5a5c37e71e65a2e42c999d3f72&chksm=97794025a00ec93379ad537353dd58f9149f801e100786a2b9c8cf37257e6d863d7ce4b87a5e&token=1319249232&lang=zh_CN#rd) \ No newline at end of file From 26d835466288cfed5ae240150981f0cfd2615c5d Mon Sep 17 00:00:00 2001 From: plumbin Date: Tue, 24 Jun 2025 10:03:23 +0800 Subject: [PATCH 2/9] =?UTF-8?q?=E7=BC=96=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...VM\351\235\242\350\257\225\351\242\230.md" | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" index 5bda042..6379be6 100644 --- "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" +++ "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" @@ -1,52 +1,52 @@ -### JVM ƪ -1. ʲô»ᷢջڴʲôʱôŴģ -2. JVMôж϶ǿɻնЩ -3. JVMڴṹıEdenSurvivor -4. ֪ļռԵȱ㣬ص㽲cmsG1ԭ̣ȱ㡣 -5. ˵˵˽Դ˫ίôôơ -6. JVMڴΪʲôҪֳ־ôΪʲôҪΪEdenSurvivor -7. JVM fullGC ƵôȥŲ⣿ -8. JVMһGCģν˵˵֪ļҪJVM -9. 㷨ʵԭ -10. JVMڴģ͵֪ʶ˽٣ڴϣhappen-beforeڴ棬ڴȡ -11. ˵һJavaĴ -12. ӦõJVMЩ -13. G1cms -14. ô߳ջϢ -15. ˵һصִй -16. JVMջƣʱMinorGCȲأ -17. ZGC ռ˽ -18. ķʶλַʽ? -19. ˵һ jvm ŵĹߣ -20. ʲôʱ -21. ڴй©ڴ -22. ʲôtomcatػƣ -23. ˽ݷ -24. System.gc()ᷢʲô? -25. ̸̸Minor GCfull GC -26. Stop The World ˽ -27. ̸̸ʶOOMαOOM? -28. ˽JVMû˼·ʲô?ȷǵĴСأ -29. ԱƷϢJVMĸڴ -30. ֽı -31. JavaҪԱڴ -32. JavaʲôĿģʲôʱգ -33. System.gc()Runtime.gc()ʲô飿 -34. ڴ빤ڴ -35. ڴ佻 -36. volatile ֹڴ -37. ڴģ -38. ̸̸зԭ -39. JVM ڴ߳Ƿɼ -40. ˵һJVM òЩ -41. VM ΪʲôʹԪռ滻ô -42. JavaѵĽṹʲôӵģʲôǶеô(Perm Gen space)? -43. JVMôлᷢô -44. ʲôֽ룿ֽôʲôʲôJava -45. MinorGC Ĺ -46. CPU ռùη -47. SerialParallel GC֮IJ֮ͬ -48. WeakHashMap ôģ -49. Java ѿռ估 GC -50. ܱ֤ GC ִ -51. JVMĸ̵߳ջջС? +### JVM 篇 +1. 什么情况下会发生栈内存溢出。什么时候发生堆溢出?你是怎么排错的? +2. JVM怎么判断对象是可回收对象?有哪些方法。 +3. JVM的内存结构,新生代与老年代的比例,Eden和Survivor比例。 +4. 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。 +5. 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。 +6. JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。 +7. JVM 出现 fullGC 很频繁,怎么去线上排查问题? +8. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数。 +9. 垃圾回收算法的实现原理。 +10. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等。 +11. 说一下Java对象的创建过程 +12. 你们线上应用的JVM参数配置了哪些。 +13. G1和cms区别。 +14. 怎么打出线程栈信息。 +15. 说一下类加载的执行过程 +16. JVM垃圾回收机制,何时触发MinorGC等操作呢? +17. ZGC 垃圾收集器,了解过吗 +18. 对象的访问定位有哪两种方式? +19. 说一下 jvm 调优的工具? +20. 对象什么时候会进入老年代? +21. 内存泄漏和内存溢出区别? +22. 什么是tomcat类加载机制? +23. 了解逃逸分析技术吗 +24. 调用System.gc()会发生什么? +25. 谈谈Minor GC条件,full GC条件 +26. Stop The World 了解过吗? +27. 谈谈你认识多少种OOM?如何避免OOM? +28. 了解过JVM调优没,基本思路是什么?如何确定它们的大小呢? +29. 淘宝热门商品信息在JVM哪个内存区域 +30. 字节码的编译过程 +31. Java需要开发人员回收内存垃圾吗? +32. Java中垃圾回收有什么目的?什么时候进行垃圾回收? +33. System.gc()和Runtime.gc()会做什么事情? +34. 主内存与工作内存 +35. 内存间交互操作 +36. volatile 禁止内存重排序 +37. 内存模型三大特性 +38. 谈谈先行发生原则 +39. JVM 堆内存溢出后,其他线程是否可继续工作? +40. 说一下JVM 常用参数有哪些? +41. VM 为什么使用元空间替换了永久代? +42. Java堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space)? +43. JVM的永久代中会发生垃圾回收么? +44. 什么是字节码?采用字节码的最大好处是什么?什么Java是虚拟机? +45. MinorGC 的过程 +46. CPU 占用过高如何分析 +47. Serial与Parallel GC之间的不同之处? +48. WeakHashMap 是怎么工作的? +49. 解释 Java 堆空间及 GC? +50. 你能保证 GC 执行吗? +51. JVM中哪个参数是用来控制线程的栈堆栈小的? From 3ce15404034ff40a13ba03d1bcd4a3c879bdf3d2 Mon Sep 17 00:00:00 2001 From: plumbin Date: Tue, 24 Jun 2025 10:07:20 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E8=A1=A5=E5=85=85=E7=AD=94=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...VM\351\235\242\350\257\225\351\242\230.md" | 703 +++++++++++++++++- 1 file changed, 684 insertions(+), 19 deletions(-) diff --git "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" index 6379be6..2eef813 100644 --- "a/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" +++ "b/Java\351\235\242\350\257\225\351\242\230\351\233\206\347\273\223\345\217\267/JVM \347\257\207/JVM\351\235\242\350\257\225\351\242\230.md" @@ -1,52 +1,717 @@ ### JVM 篇 1. 什么情况下会发生栈内存溢出。什么时候发生堆溢出?你是怎么排错的? + 答:栈内存溢出通常发生在以下情况: + * 递归调用过深,没有正确终止条件,导致栈帧不断压入栈而栈空间耗尽。 + * 局部变量过多或过大,单个方法调用时栈帧超过栈容量限制。 + * 线程创建过多,每个线程都有独立的栈空间,总栈空间耗尽。 + 堆溢出通常发生在: + * 程序申请的内存总量超过了JVM堆的最大容量(-Xmx设置)。 + * 发生了内存泄漏(Memory Leak),对象不再被使用但仍然被引用,导致可用堆空间逐渐耗尽。 + 排错方法: + * **栈溢出**: + * 使用JVM参数 `-XX:+PrintStackTrace` 打印出完整的异常堆栈信息,定位到具体的代码行。 + * 检查递归逻辑,确保有终止条件。 + * 检查方法内局部变量数量和大小。 + * 调整JVM栈大小参数 `-Xss`(但通常不是首选,应先优化代码)。 + * **堆溢出**: + * 捕获 `OutOfMemoryError` 异常,在异常处理中 dump 堆转储文件(Heap Dump),常用参数 `-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof`。 + * 使用分析工具(如VisualVM, Eclipse MAT, JProfiler, YourKit)加载Heap Dump文件,分析内存占用情况,查找内存泄漏点(如哪些对象占用大量内存,哪些GC Roots引用了它们)。 + * 分析GC日志(`-Xlog:gc*=info`),观察内存增长趋势和Full GC情况,判断是内存泄漏还是堆大小设置不当。 2. JVM怎么判断对象是可回收对象?有哪些方法。 + 答:JVM主要通过以下两种方法判断对象是否可回收: + * **引用计数法(Reference Counting)**:为每个对象分配一个引用计数器。当有一个地方引用它时,计数器加1;引用失效时,计数器减1。任何时刻计数器为0的对象就是不可能再被使用的,可以被回收。缺点是难以解决对象间的循环引用问题。 + * **可达性分析算法(Reachability Analysis / Tracing Algorithm)**:这是目前主流的判断方法。通过一系列称为“GC Roots”的对象作为起点,向下搜索,如果一个对象到GC Roots没有任何引用链相连(即从GC Roots到该对象不可达),则证明该对象是不可用的,可以被回收。可以作为GC Roots的对象包括:虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(Native方法)引用的对象等。 3. JVM的内存结构,新生代与老年代的比例,Eden和Survivor比例。 + 答:JVM内存结构(以HotSpot为例)主要分为: + * **堆(Heap)**:几乎所有的对象实例都在这里分配内存,是GC的主要区域,可细分为新生代和老年代。 + * **栈(Stack)**:每个线程创建时都会分配一个私有的栈,用于存储局部变量、操作数栈、动态链接、方法出口等信息。栈内存是线程私有的。 + * **程序计数器(Program Counter Register)**:指示当前线程执行的字节码指令地址,也是线程私有的。 + * **本地方法栈(Native Method Stack)**:为JVM使用到的Native方法服务,也是线程私有的。 + * **方法区(Method Area)/元空间(Metaspace)**:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK 7及之前是永久代(PermGen),JDK 8及之后改为元空间(Metaspace),元空间使用本地内存。 + 新生代与老年代的比例:默认是 **1:2**,即新生代占堆内存的1/3,老年代占2/3。可以通过 `-XX:NewRatio` 参数调整(例如 `-XX:NewRatio=2` 表示老年代:新生代=2:1)。 + Eden和Survivor比例:新生代内部又分为一个Eden空间和两个Survivor空间(From和To)。默认比例是 **Eden : Survivor = 8 : 1 : 1**。可以通过 `-XX:SurvivorRatio` 参数调整(例如 `-XX:SurvivorRatio=8` 表示Eden:Survivor=8:1)。 4. 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。 + 答:常见的垃圾收集器有: + * **Serial GC**:单线程收集器,进行垃圾回收时必须暂停所有用户线程(Stop The World, STW)。优点是简单高效(在单核CPU上),内存开销小。缺点是STW时间长,不适合服务器环境。 + * **ParNew GC**:Serial GC的多线程版本,使用多个GC线程并行回收。是许多Server模式下的首选新生代收集器,可以与CMS配合使用。优点是并行回收,减少STW时间。缺点是仍然是独占式回收,且需要多核CPU支持。 + * **Parallel Scavenge GC**:新生代收集器,使用复制算法,追求高吞吐量(Throughput = 用户代码运行时间 / (用户代码运行时间 + GC时间))。可以通过 `-XX:MaxGCPauseMillis` 设置最大暂停时间目标,但效果有限。优点是吞吐量高。缺点是STW时间可能较长,无法精确控制暂停时间。 + * **Serial Old GC**:Serial GC的老年代版本,使用标记-整理算法。用于Client模式或Parallel Scavenge的后备方案。 + * **Parallel Old GC**:Parallel Scavenge的老年代版本,使用标记-整理算法。优点是高吞吐量,且老年代回收也能并行。缺点是STW时间可能较长。 + * **CMS(Concurrent Mark Sweep)GC**: + * **原理**:一种以获取最短回收停顿时间为目标的收集器,基于“标记-清除”算法实现。主要优点是并发标记和并发清除,大部分工作不阻塞用户线程。 + * **流程**: + 1. **初始标记(Initial Mark)**:STW,标记GC Roots能直接关联到的对象。速度很快。 + 2. **并发标记(Concurrent Mark)**:在用户线程继续运行的同时,从GC Roots开始遍历整个对象图,标记所有可达对象。耗时较长但不需要STW。 + 3. **重新标记(Remark)**:STW,修正并发标记期间因用户线程继续运行而导致标记产生变动的那部分对象的标记记录。这个阶段停顿时间比初始标记长,但远比并发标记短。 + 4. **并发清除(Concurrent Sweep)**:在用户线程继续运行的同时,清除掉未被标记(不可达)的对象。由于是“标记-清除”算法,会产生内存碎片。 + * **优缺点**: + * 优点:并发收集,低停顿。 + * 缺点:对CPU资源敏感(并发阶段虽然不暂停用户线程,但会占用CPU资源影响应用性能);无法处理“浮动垃圾”(并发标记和清除期间用户线程产生的垃圾,只能等到下一次GC);内存碎片问题(可能导致大对象分配时需要触发Full GC)。 + * **G1(Garbage-First)GC**: + * **原理**:一款面向服务器的垃圾收集器,主要针对多处理器、大内存的机器,在有限停顿时间内获得高吞吐量。它将整个堆划分为多个大小相等的独立区域(Region),每个Region都可以扮演Eden、Survivor或Old的角色。它跟踪各个Region的垃圾价值(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的停顿时间,优先回收价值最大的Region。 + * **流程**: + 1. **初始标记(Initial Mark)**:STW,与CMS的初始标记类似,标记GC Roots直接关联的对象,但可以利用Remark时收集的引用信息。 + 2. **并发标记(Concurrent Mark)**:与CMS类似,遍历对象图,标记所有可达对象,并计算各个Region的回收价值。 + 3. **最终标记(Final Mark)**:STW,处理并发标记期间对象引用关系变化的部分。 + 4. **筛选回收(Cleanup)**:STW,根据并发标记的结果,筛选出回收价值高、且预计停顿时间满足要求的Region集合,进行回收。回收时,将Region中存活的对象复制到空的Region中,并清除被回收Region的空间。这个过程会整理内存,减少碎片。 + * **优缺点**: + * 优点:可预测的停顿时间(通过 `-XX:MaxGCPauseMillis` 设置目标),高吞吐量,能充分利用多CPU优势,有效解决内存碎片问题,可扩展性好(适用于大内存)。 + * 缺点:相对复杂,内存占用稍高(需要维护数据结构),在Region数量较少或对象大小分布不均时可能不如CMS。 5. 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。 + 答:类加载器负责将类的字节码文件(.class)加载到JVM中,并生成对应的Class对象。常见的类加载器有: + * **启动类加载器(Bootstrap ClassLoader)**:负责加载 `/lib` 目录下或被 `-Xbootclasspath` 参数指定的核心类库(如rt.jar)。它是C/C++实现的,Java程序无法直接获取其引用。 + * **扩展类加载器(Extension ClassLoader)**:负责加载 `/lib/ext` 目录下或被 `java.ext.dirs` 系统变量指定的扩展类库。 + * **应用程序类加载器(Application ClassLoader / System ClassLoader)**:负责加载 `CLASSPATH` 环境变量或 `-classpath` 指定的类路径下的类。它是我们编写的大多数应用程序的默认类加载器。 + * **自定义类加载器**:继承 `java.lang.ClassLoader` 并重写 `findClass`(或 `loadClass`)方法实现。 + **双亲委派模型(Parent Delegation Model)**:工作流程是:如果一个类加载器收到了加载类的请求,它首先不会自己尝试加载,而是将请求委派给它的父类加载器。只有当父类加载器反馈自己无法完成加载任务时(如父类加载器中不存在该类),子类加载器才会尝试自己去加载。 + **打破双亲委派**:可以打破,但需要手动干预。常见的方法有: + * **重写 `loadClass` 方法**:在自定义类加载器中,不遵循双亲委派的逻辑,直接调用 `findClass` 方法进行加载。 + * **重写 `findClass` 方法**:这是更推荐的方式。在自定义类加载器的 `findClass` 方法中实现自己的加载逻辑(如从网络、数据库等非标准位置加载),并确保在找不到类时抛出 `ClassNotFoundException`,这样调用链上的其他加载器(包括父加载器)还有机会尝试加载。 + * **使用 `ClassLoader#loadClass(String, boolean)` 的 `resolve` 参数**:虽然不直接打破模型,但可以控制是否解析类。 + * **线程上下文类加载器(Thread Context ClassLoader)**:通过 `Thread.setContextClassLoader()` 设置,可以在类加载链路中插入一个不同的加载器。例如,JNDI服务使用它来加载资源,打破了SPI(Service Provider Interface)场景下默认的双亲委派限制。 6. JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。 + 答: + * **为什么分新生代和老年代**: + * **对象生命周期差异**:大多数对象在创建后很快就会变得不可达(短命对象),只有少量对象会存活很长时间(长命对象)。分代收集可以针对不同生命周期的对象采用不同的回收策略,提高效率。 + * **回收效率**:新生代对象密度高(很多是垃圾),适合使用复制算法(Copying),速度快。老年代对象密度低(大部分是活对象),适合使用标记-清除(Mark-Sweep)或标记-整理(Mark-Compact)算法,减少内存浪费。 + * **减少Full GC频率**:将长命对象移到老年代,可以减少它们被频繁扫描和回收的开销,降低Full GC的频率。 + * **为什么新生代分Eden和Survivor**: + * **复制算法的实现**:新生代主要使用复制算法。将新生代分为Eden和两块大小相同的Survivor空间,每次分配对象时都在Eden区进行。当Eden区满时触发Minor GC,将Eden和一块Survivor中存活的对象复制到另一块空的Survivor中,并清理掉Eden和原Survivor。存活的对象年龄增加,达到一定年龄(默认15,由 `-XX:MaxTenuringThreshold` 控制)的对象会被晋升到老年代。 + * **空间效率**:任何时候都保证有一块Survivor是空的,作为下次GC时的复制目标,避免了频繁的内存分配和整理。理论上只有10%的空间被“浪费”(因为Survivor区占新生代的1/10),但能高效处理大量短命对象的创建和回收。 + * **对象年龄管理**:Survivor区可以用来记录对象的存活时间,为对象晋升到老年代提供依据。 7. JVM 出现 fullGC 很频繁,怎么去线上排查问题? + 答:排查频繁Full GC的步骤: + * **收集GC日志**:确保JVM开启了GC日志功能,例如:`-Xlog:gc*=info:file=/path/to/gc.log:time,uptime,level,tags`。日志文件记录了每次GC的类型、耗时、堆内存变化等信息。 + * **分析GC日志**:使用工具(如 `jcmd GC.print_heap_at_gc`, `jstat -gcutil `, 或专门的日志分析工具如GCEasy, GCViewer)分析日志。 + * 查看Full GC的触发原因:是老年代空间不足?永久代/元空间空间不足?还是System.gc()被调用?或是CMS的Concurrent Mode Failure(并发模式失败)或Promotion Failed(晋升失败)? + * 观察老年代、新生代、永久代/元空间的使用趋势,判断是哪个区域导致的问题。 + * 计算Full GC的平均间隔时间和耗时,评估影响。 + * **生成Heap Dump**:在Full GC发生时或之后,使用 `-XX:+HeapDumpOnOutOfMemoryError` 或手动使用 `jmap -dump:live,format=b,file=heap.hprof ` 生成堆转储快照。 + * **分析Heap Dump**:使用MAT(Eclipse Memory Analyzer Tool)、VisualVM、JProfiler等工具加载Heap Dump。 + * 查找内存泄漏:通过Dominator Tree或 Leak Suspects 报告,找出占用内存最多的对象及其引用链,判断是否有对象本应被回收但被长期持有引用。 + * 检查大对象:查找占用空间特别大的对象,判断是否是正常业务需求还是异常产生。 + * 检查线程栈:有时线程长时间持有锁或处于某种状态也会间接导致内存问题。 + * **检查代码和配置**: + * 代码层面:检查是否有静态集合类长期持有对象引用、大对象创建后未释放、缓存机制设计不合理、线程泄漏等问题。 + * 配置层面:检查堆内存大小( `-Xms`, `-Xmx`)是否设置过小;老年代、新生代比例是否合理;Survivor区比例是否合适;GC算法选择是否适合当前应用场景;元空间大小( `-XX:MetaspaceSize`, `-XX:MaxMetaspaceSize`)是否足够。 + * **监控指标**:结合系统监控(如CPU、内存、线程数)和应用监控(如请求量、响应时间),看Full GC是否与特定操作或高峰期相关。 8. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的JVM参数。 + 答: + * **一次完整的GC流程**:通常指从Minor GC到可能触发Full GC的过程。 + 1. **Minor GC**:在新生代空间(主要是Eden区)满时触发。GC线程会暂停用户线程(STW),扫描Eden区和From Survivor区,将存活的对象复制到To Survivor区,并清理Eden区和From Survivor区。存活对象年龄+1。如果对象年龄达到阈值,则晋升到老年代。 + 2. **Major GC / Full GC**:通常在以下情况触发: + * 老年代空间不足(如新生代对象晋升导致老年代满)。 + * 永久代/元空间空间不足。 + * System.gc()被调用(建议,非强制)。 + * CMS GC中的Concurrent Mode Failure或Promotion Failed。 + * 统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间。 + Full GC会扫描整个堆(新生代和老年代),并可能进行整理(取决于GC算法),耗时较长,会导致较长时间的STW。 + * **对象如何晋升到老年代**: + * **年龄阈值**:在Survivor区中,对象每熬过一次Minor GC,年龄就加1。当年龄达到一定值(默认15,由 `-XX:MaxTenuringThreshold` 设置),在下次Minor GC时就会被复制到老年代。 + * **大对象直接进入老年代**:对于一些很大的对象(超过 `-XX:PretenureSizeThreshold` 设置的大小),为了避免在Survivor区之间复制带来的性能开销,会直接在新生代GC时分配到老年代。 + * **动态年龄判定**:JVM并不总是严格按照年龄阈值。它会统计Survivor区中各个年龄对象占用的总大小,如果某个年龄的所有对象的总大小大于Survivor区的一半,那么大于或等于该年龄的所有对象将直接进入老年代,即使它们的年龄还没达到阈值。 + * **主要的JVM参数**: + * **堆内存相关**: + * `-Xms`:初始堆大小。 + * `-Xmx`:最大堆大小。 + * `-Xmn`:新生代大小(通常老年代大小 = MaxHeapSize - NewSize)。 + * `-XX:NewRatio=`:老年代/新生代的比例(默认2,即老年代:新生代=2:1)。 + * `-XX:SurvivorRatio=`:Eden/Survivor的比例(默认8,即Eden:Survivor=8:1)。 + * `-XX:MaxTenuringThreshold=`:对象晋升老年代的最大年龄(默认15)。 + * `-XX:PretenureSizeThreshold=`:大于此大小的对象直接分配在老年代(仅对Serial和Parallel Scavenge GC有效)。 + * **GC相关**: + * `-XX:+UseSerialGC`:使用Serial GC。 + * `-XX:+UseParNewGC`:使用ParNew GC(新生代并行)。 + * `-XX:+UseParallelGC` / `-XX:+UseParallelOldGC`:使用Parallel Scavenge / Parallel Old GC。 + * `-XX:+UseConcMarkSweepGC`:使用CMS GC。 + * `-XX:+UseG1GC`:使用G1 GC。 + * `-XX:MaxGCPauseMillis=