// 将全部行数读取的内存中
List lines = FileUtils.readLines(new File(“temp/test.txt”), Charset.defaultCharset());
for (String line : lines) {
// pass
}
上面这段代码,一旦读取到了大文件,很可能把生产环境搞崩。
所以,把上述两个知识点深入理解了,对新手提升自己的代码质量非常非常有用。只有代码质量上去了,你才能得到更好的发展。
对于这两个知识点,我认为通过网络的文章去学习最好。如果直接看书,有两个最大的缺点:
知识积累不足导致学不懂
书中冗余知识点太多,互相交杂,精力耗费过大,性价比不高
故这里学习推荐根据知识点去搜文章读,而不是找原理性的书籍看。
对处于这个阶段的朋友,他们已经可以熟练编写健壮的代码了,经常会独立开发出一个大的功能模块,有的可能还能独立开发出一个完整的小型项目。
这时候,他们可能会面临两种情况:
1. 需要写一些工具类给全团队使用
在这种情况下,你很可能就需要 Java 中的语法糖,因为语法糖能让你写出非常灵活简单的代码。这包括泛型,自动拆装箱,可变参数还有遍历循环。
但是,使用这些语法糖的时候,如果你不熟悉他们在 JVM 中的实现原理,就非常容易栽个大跟头,
比如:
public class GenericPitfall {
public static void main(String[] args) {
List list = new ArrayList();
list.add(“123”);
List list2 = list;
System.out.println(list2.get(0).intValue());
}
}
2. 编写性能优越的代码
什么时候需要性能优越的代码?最常见的就是把以前性能不好的同步实现,转化成异步实现。
而这种要求,就需要开发对 Java 的多线程开发非常熟悉,并且一定要深入理解多线程在 JVM 中的原理实现。
不然,可以看看下面这段代码:
class IncompletedSynchronization {
int x;
public int getX() {
return x;
}
public synchronized void setX(int x) {
this.x = x;
}
}
再看看这段
Object lock = new Object();
synchronized (lock) {
lock = new Object();
}
如果把上面这些代码上了生产环境,熬通宵排查问题的命运就注定了……
这里的知识点,我推荐通过网上的文章看,又因为涉及到了并发知识,我建议就着《Java Performance》第二版的“Chapter 9. Threading and Synchronization Performance”这章一起看。
还有余力,建议再继续看周志明的那本《深入理解 JAVA 虚拟机》第三版中的 12-13 章。周志明这本书讲的十分深入,也带来个缺点:门槛高。此时,如果没看懂可以放一放。
注意,我这里说的是并发的原理,不是并发实践,读者想学并发编程,《JAVA 并发编程实践》我认为是前提条件,故不会赘述。
这时候的你,已经开始承担项目开发中很重要的职责了,有些出色的朋友都开始带团队了。那这时候,你可能会做下面的事:
1. 合理规划项目使用资源
合理规划项目使用资源,前提是对垃圾回收有非常深入的了解。
如果说在新手期,已经对 Java 对象的内存分配和内存使用有了大致的概念,那么,这个垃圾回收,则是这类知识的进一步拓展。
只有理解了各种垃圾回收的原理,再配合着 Java 内存布局的基础知识,才能更好地规划出项目用什么回收算法,才能在合适的资源利用度上得到最佳性能。
比如,新生代和老年代之间的合适比例。比如,新生代中 Eden 和 Survivor 区域间的比例。
2. 排查各种线上问题
要排查各种问题,就需要对 JVM 提供的各种故障排查工具非常了解。
这些工具又分为两类:
-
基础的命令行形式的故障处理工具,比如 jps、jstack 等等
-
第二类是可视化的故障处理工具,比如 VisualVM
但是,掌握工具的使用还不够。因为有关垃圾回收的问题,还必须得通过解析 GC 日志后,再通过工具的使用,才可能能定位到问题的根源。
所以,最好对使用故障排查工具和 GC 日志都非常熟练。
比如:
2021-05-26T14:45:37.987-0200: 151.126:
[GC (Allocation Failure) 151.126: [DefNew: 629119K->69888K(629120K), 0.0584157 secs] 1619346K->1273247K(2027264K), 0.0585007 secs]
[Times: user=0.06 sys=0.00, real=0.06 secs]
2021-05-26T14:45:59.690-0200: 172.829:
[GC (Allocation Failure) 172.829: [DefNew: 629120K->629120K(629120K), 0.0000372 secs]172.829: [Tenured: 1203359K->755802K(1398144K), 0.1855567 secs] 1832479K->755802K(2027264K), [Metaspace: 6741K->6741K(1056768K)], 0.1856954 secs]
[Times: user=0.18 sys=0.00, real=0.18 secs]
上面这条,应该一眼看出来,垃圾算法用的是 Serial 收集器,并且年轻代分配出现了问题,大小可能需要调整。
这里的知识点,强烈反对看网上的文章,网上说的很多细节有问题,疏漏很多。所以,推荐看书。
《Java Performance》第二版里,“Chapter 5. An Introduction to Garbage Collection”,“Chapter 6. Garbage Collection Algorithms”的知识已经足够。
有人去看《深入理解 JAVA 虚拟机》第三版中的第 3 章,讲垃圾收集器与内存分配策略的。这里还是老问题,讲的太细,我建议绕过 3.4 节,讲 HotSpot 算法细节的那块儿。
这里安全点这个知识点挺重要,但是现在这个阶段想理解挺难的。我觉得将来做一些底层框架,接触到崩溃恢复的 checkpoint 相关思想了,再回头来学习,那才能真正理解和掌握。
达到这个级别了,那就需要对整套 JVM 要有非常深入的了解了,因为你是解决技术问题的最后保障了。有些时候,甚至还需要因为某些问题开发出各种各样的工具。
曾经,有个项目时不时总是会报错:
java.lang.OutOfMemoryError: GC overhead limit exceeded
这个问题几个同事都没搞定,就来找我。我看了看,突然想起来,以前在官方调优指南《HotSpot Virtual Machine Garbage Collection Tuning Guide》看到过相关介绍。
JVM 本身内存不足就会运行 GC,但是如果每次 GC 回收的内存不够,那么很快就会开始下一次 GC。
JVM 有个默认的保护机制,如果发现在一个统计周期内,98% 的时间都是在运行 GC,内存回收却少于 2% 的时候,就会报这个错。
怎么引起的呢?这个问题如果去排查代码,那真的是难如登天,首先,没有任何堆栈错误去帮助定位问题。其次,项目代码量大了去了,而且是年头久远。
这时,就需要通过对 JVM 总体的深入理解,去反推问题了。我当时是这样推理的:
内存溢出,GC 无法回收问题,说明了两个问题:
-
堆内的内存不够用了
-
占用内存的对象要么就是该关闭的资源没有关闭,要么被大量的暂时放在一起了
那如果我 dump 出内存文件出来,再分析下就知道是哪些对象在占用内存了。
一查发现是大量的字符串在占用内存。
综合我前面的推测,字符串不是数据库连接,肯定没有该关闭未关闭的问题。那就剩一个可能了,就是被大量的暂时放起来了,导致 GC 回收不了。
那么新问题来了,能大量放字符串的,会是什么?
首先就去猜缓存。根据这条线索,直接去源码搜 Cache 关键词,把所有关于 Cache 的代码都看了下。一下子就找到问题了。
原来,我们有个功能是解析一个非常大的文件。文件的格式如下:
需要把这个文件的每一行内容按照列去一起存到数据库里。
由于写代码的人偷懒,想一次解析完毕后一股脑全塞到数据库里。所以,他弄了个 Map,Map 的 Key 是相同的列名,Value是每一行解析过的内容。
而这样写代码的结果就是,一行对应了一个有三个条目的 HashMap。如果文件有十几万行,就有十几万的 HashMap。然后,这些 HashMap 再存到一个列表里,再把这个列表放到一个叫做 xxxCache 的 HashMap 中。
示意代码如下:
public class ParseFile4OOM {
public static void main(String[] args) {
List<Map<String, String>> lst = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
Map<String, String> map = new HashMap<>();
map.put(“Column1”, “Content1”);
map.put(“Column2”, “Content2”);
map.put(“Column3”, “Content3”);
lst.add(map);
}
Map<String, List<Map<String, String>>> contentCache = new HashMap<>();
contentCache.put(“contents”, lst);
}
}
那对这种情况怎么办呢?代码还不能大动,只能优化。
那时,我们已经用了 JDK8 了,引入了 String 常量池。同时,Hashmap 在这个业务场景下,容积是固定的,所以,就不应该给它多分配空间,就固定死为 3。
优化后,代码如下:
public class ParseFile4OOM {
public static void main(String[] args) {
List<Map<String, String>> lst = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
Map<String, String> map = new HashMap<>(3);
map.put(“Column1”.intern(), “Content1”.intern());
map.put(“Column2”.intern(), “Content2”.intern());
map.put(“Column3”.intern(), “Content3”.intern());
lst.add(map);
}
Map<String, List<Map<String, String>>> contentCache = new HashMap<>();
contentCache.put(“contents”.intern(), lst);
}
}
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-mUuZFo7O-1715554020390)]
[外链图片转存中…(img-NtmTQvb6-1715554020390)]
[外链图片转存中…(img-Uq3EzS0p-1715554020391)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!


被折叠的 条评论
为什么被折叠?



