笔记
ArrayList
- 已经清楚或能够估计出元素容量,可以在添加元素前调用【ensureCapacity(100)】函数,或在构造函数中传入【new ArrayList<>(100)】
- 添加完元素后明确不再添加任何元素时,可调用【trimToSize()】,以释放多余空间
带资源try语句
普通try-catch方式:
1 | //open a resource |
带资源try方式:
1 | try(Recource res = ...) { |
分析堆栈轨迹元素
1 | // 可以获取所有线程的堆栈轨迹 |
Java日志
全局日志记录器:Logger.getGlobal().info("msg");
自定义日志记录器:private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
内联
消除函数调用,采用类变量直接访问,可以减少函数栈的调用,提高效率
从【c.getName()】 变更为【c.name】的形式
即时编译器可以监控经常执行哪些代码并优化这些代码以提高速度。更为复杂的优化是消除函数调用(即“ 内联”)。即时编译器知道那些类已经加载。基于当前加载的类集,如果特定的函数不会被覆盖,就可以使用内联。必要时,还可以撤销优化。
是否将某个方法设置为内联方法是Java 虚拟机的任务。即时编译器会监视调用那些简洁、经常被调用、没有被重载以及可优化的方法。
在早期的Java 中, 有些程序员为了避免动态绑定带来的系统开销而使用final 关键字。如果一个方法没有被覆盖并且很短, 编译器就能够对它进行优化处理, 这个过程为称为内联( inlining )。例如,内联调用e.getName( ) 将被替换为访问e.name 域。这是一项很有意义的改进, 这是由于CPU 在处理调用方法的指令时, 使用的分支转移会扰乱预取指令的策略, 所以,这被视为不受欢迎的。然而, 如果getName 在另外一个类中被覆盖, 那么编译器就无法知道覆盖的代码将会做什么操作, 因此也就不能对它进行内联处理了。
幸运的是, 虚拟机中的即时编译器比传统编译器的处理能力强得多。这种编译器可以准确地知道类之间的继承关系, 并能够检测出类中是否真正地存在覆盖给定的方法。如果方法很简短、被频繁调用且没有真正地被覆盖, 那么即时编译器就会将这个方法进行内联处理。如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖, 那么将会发生什么情况呢? 优化器将取消对覆盖方法的内联。这个过程很慢, 但却很少发生。
枚举
1 | public enum TestEnum { |
原理:
1 | public final class TestEnum extends Enum{ |
注意一:从上面可以看出,枚举会消耗更多的内存,类似于饿汉式的单例
注意二:Proguard以及Android的R8也会对简单的枚举(即不实现接口也没有额外成员的枚举)做优化
反射
对于final的基本变量类型,在编译字节码的时候会被认为不会再改变,会将值直接放入调用处,所以反射对它无效
如果是static final的基本变量类型,修改值会产生异常,必须要修改可以再反射修改它的
modifiers
值以达到能修改的目的,注意:目前jvm并没有对该字段有限制
反射效率低
java反射效率低的主要原因是:
- Method#invoke 方法会对参数做封装和解封操作
- 需要检查方法可见性。原因:反射时每次调用都必须检查方法的可见性(在Method.invoke里)
- 需要校验参数。反射时也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0 里或者生成的 Java 版 MethodAccessor.invoke 里)
- 反射方法难以内联。参考:https://www.iteye.com/blog/rednaxelafx-548536
- JIT 无法优化。原因:因为涉及到动态解析的类型,所以无法优化
提高反射性能的方式:
- 将反射获取到的类,构造函数,函数,对象实例缓存起来,不用每次都全新查找
- 可通过调用method.setAccessible(true)的方式来关闭Method.invoke可见性检查
- 需要更极致的提高效率可以通过字节码生成的方式来实现反射机制。参考:https://github.com/EsotericSoftware/reflectasm
Lambda高阶参考
https://baijiahao.baidu.com/s?id=1606476168883238803&wfr=spider&for=pc