Java
重要语法功能
1、Java参数传递
Java方法参数的传递都是值传递,包括引用和基本类型,如果是引用就复制一份引用地址到被调用的方法入参中,注意是复制一份,而不是直接引用外层引用的地址。即如果引用参数地址不变的话修改应用参数的属性会影响外层引用对象,因为对象引用都是对应一个地址,修改的是同一堆内存区域的内容;如果引用参数地址改变的话,如重新赋值一个新的对象变量,则不会影响外层引用对象。(这和C++的指针传递不一样,C++指针传递是外城对应和参数都对应一个指针,重新赋值也会影响外层。)
2、Java闭包
Java闭包应用在内部类和匿名内部类中,可以通过闭包来隐藏一些代码和逻辑,像匿名内部类只能被使用一次。
内部类可以访问外部类的属性和方法(包括private修饰的方法和属性)。
闭包中的代码如果需要访问外部变量,可以通过final修饰变量来访问,不允许闭包中代码修改变量值,并且传入闭包中的外部变量是通过copy来赋值的。
3、Java匿名内部类
Java内部类或则匿名内部类通过命令javac编译和javap -c反编译后,看到内部类实际就是外部类的子类。
4、使用classpath和file 加载文件的区别
file:加载非编译类的文件系统,即:作为 URL 从文件系统中加载。
classpath:加载编译的class文件系统,即:从classpath中加载。
资源文件和类文件编译后都会放到
5、import static静态导入
import static静态导入是JDK1.5中的新特性。
一般我们导入一个类都用 import 包名.类名;
而静态导入是这样:import static 包名.类名.*;
如:
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@Test
public void test() {
when(Objects.toString(null)).thenReturn(null);
assertThat(new Date()).isEqualToIgnoringHours(new Date());
}
这里的多了个static,还有就是类名后面多了个 .* 。意思是导入这个类里的静态成员(静态方法、静态变量)。当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了。然后在这个类中,就可以直接用方法名调用静态方法,而不必用“类名.方法名()” 的方式来调用。
这种方法的好处就是可以简化一些操作,例如一些工具类的静态方法,如果使了静态导入,就可以像使用自己的方法一样使用这些静态方法。
不过在使用静态导入之前,我们必须了解下面几点:
- 静态导入可能会让代码更加难以阅读
- import static和static import不能替i
- 如果同时导入的两个类中又有重命名的静态成员,会出现编译器错误。例如Integer类和Long类的MAX_VALUE。
- 可以导入的静态成员包括静态对象引用、静态常量和静态方法。
6、@SuppressWarnings(“unchecked”)编译器警告忽略处理
SuppressWarnings主要用来处理忽略编译器的警告,能够在代码编译期间或则Idea中不输出或则提示警告信息。
7、Runtime
每个Java应用程序都有一个单例的Runtime对象,Runtime对象可以通过getRuntime()方法获取,这个对象可以获取应用程序运行环境中的一些参数信息。应用程序不能创建自身关于这个对象的实例。
查看运行时参数
//获取处理器个数
System.out.println(Runtime.getRuntime().availableProcessors());
//获取空闲内存 如果未扩展 在-Xms以内 否则随着内存扩展不断调整
System.out.println(Runtime.getRuntime().freeMemory()/1024/1024);
//获取当前所有可用内存 如果不超过-Xms 对应值就是-Xms 否则就是自动扩展后的总可用内存(不一定直接就对应Xmx,是根据实际情况自动递进扩展)
System.out.println(Runtime.getRuntime().totalMemory()/1024/1024);
//获取可以扩展的最大内存 对应-Xmx
System.out.println(Runtime.getRuntime().maxMemory()/1024/1024);
如设置JVM参数:
-Xms20m -Xmx4048m -XX:+HeapDumpOnOutOfMemoryError
执行下面代码前后输出:
String[] strings = new String[10000000];
说明JVM内存 存在自动扩展
手动执行gc操作
System.out.println(Runtime.getRuntime().availableProcessors());
System.out.println(Runtime.getRuntime().freeMemory()/1024/1024);
System.out.println(Runtime.getRuntime().totalMemory()/1024/1024);
System.out.println(Runtime.getRuntime().maxMemory()/1024/1024);
TimeUnit.SECONDS.sleep(3);
String[] strings = new String[10000000];
strings = null;
Runtime.getRuntime().gc();
System.out.println(Runtime.getRuntime().availableProcessors());
System.out.println(Runtime.getRuntime().freeMemory()/1024/1024);
System.out.println(Runtime.getRuntime().totalMemory()/1024/1024);
System.out.println(Runtime.getRuntime().maxMemory()/1024/1024);
输出结果:
说明执行gc Runtime.getRuntime().gc() 后可用内存增大到可用最大内存,并且垃圾回收后扩展后的内存并没有缩小到Xms水平。
System.gc()也是调用Runtime.getRuntime().gc()。
设置程序关闭hook
Runtime.getRuntime().addShutdownHook(Thread thread)可以在JVM退出前进行资源的回收或则数据等处理。实现优雅关闭程序。
Runtime.getRuntime().addShutdownHook(new Thread(()-> System.out.println("JVM shutdown hook")));
如JVM退出时主动关闭线程池,防止线程池中还存在正在执行的任务被暴力关闭:
@Bean
public ExecutorService executorService() {
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.MINUTES);
} catch (InterruptedException ignored) {
}
}));
return executorService;
}
手动终止JVM
// status 0表示正常退出,非0一般表示异常终止JVM
Runtime.getRuntime().exit(0);
public static void main(String[] args) {
System.err.println("Starting main program");
Runtime rt = Runtime.getRuntime();
System.err.println("Main: adding shutdown hook");
rt.addShutdownHook(new Thread() {
public void run() {
// In real life this might close a Connection or something.
System.err.println("Running my shutdown hook");
}
});
System.err.println("Main: calling Runtime.exit()");
rt.exit(0);
}
}
8、System
System 包含一些有用的字段和方法,如in、out字段,System不能被手动实例化。
System提供的功能有标准输入,如System.in、标准输出,如System.out、错误输出流,如System.err;可以额外的系统属性和系统环境变量;可以加载文件和类库;有一个工具方法可以快速复制数组的一部分,如arraycopy。
获取系统属性和环境变量
系统属性是Java进程范围的,环境变量是操作系统级别的。
https://qastack.cn/programming/7054972/java-system-properties-and-environment-variables
https://cloud.tencent.com/developer/article/1497677
public static void main(String[] args) {
System.getProperties();
System.getenv();
System.out.println();
}
9、MXBean 获取JVM运行时信息
这些带MXBean后缀的接口定义了获取JVM信息的方法。
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
MemoryUsage usage = memorymbean.getHeapMemoryUsage();
System.out.println("INIT HEAP: " + usage.getInit());
System.out.println("MAX HEAP: " + usage.getMax());
System.out.println("USE HEAP: " + usage.getUsed());
System.out.println("\nFull Information:");
System.out.println("Heap Memory Usage: "
+ memorymbean.getHeapMemoryUsage());
System.out.println("Non-Heap Memory Usage: "
+ memorymbean.getNonHeapMemoryUsage());
List<String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
System.out.println("===================java options=============== ");
System.out.println(inputArguments);
System.out.println("=======================通过java来获取相关系统状态============================ ");
int i = (int)Runtime.getRuntime().totalMemory()/1024;//Java 虚拟机中的内存总量,以字节为单位
System.out.println("总的内存量 i is "+i);
int j = (int)Runtime.getRuntime().freeMemory()/1024;//Java 虚拟机中的空闲内存量
System.out.println("空闲内存量 j is "+j);
System.out.println("最大内存量 is "+Runtime.getRuntime().maxMemory()/1024);
System.out.println("=======================OperatingSystemMXBean============================ ");
OperatingSystemMXBean osm = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
// System.out.println(osm.getFreeSwapSpaceSize()/1024);
// System.out.println(osm.getFreePhysicalMemorySize()/1024);
// System.out.println(osm.getTotalPhysicalMemorySize()/1024);
//获取操作系统相关信息
System.out.println("osm.getArch() "+osm.getArch());
System.out.println("osm.getAvailableProcessors() "+osm.getAvailableProcessors());
//System.out.println("osm.getCommittedVirtualMemorySize() "+osm.getCommittedVirtualMemorySize());
System.out.println("osm.getName() "+osm.getName());
//System.out.println("osm.getProcessCpuTime() "+osm.getProcessCpuTime());
System.out.println("osm.getVersion() "+osm.getVersion());
//获取整个虚拟机内存使用情况
System.out.println("=======================MemoryMXBean============================ ");
MemoryMXBean mm=(MemoryMXBean)ManagementFactory.getMemoryMXBean();
System.out.println("getHeapMemoryUsage "+mm.getHeapMemoryUsage());
System.out.println("getNonHeapMemoryUsage "+mm.getNonHeapMemoryUsage());
//获取各个线程的各种状态,CPU 占用情况,以及整个系统中的线程状况
System.out.println("=======================ThreadMXBean============================ ");
ThreadMXBean tm=(ThreadMXBean)ManagementFactory.getThreadMXBean();
System.out.println("getThreadCount "+tm.getThreadCount());
System.out.println("getPeakThreadCount "+tm.getPeakThreadCount());
System.out.println("getCurrentThreadCpuTime "+tm.getCurrentThreadCpuTime());
System.out.println("getDaemonThreadCount "+tm.getDaemonThreadCount());
System.out.println("getCurrentThreadUserTime "+tm.getCurrentThreadUserTime());
//当前编译器情况
System.out.println("=======================CompilationMXBean============================ ");
CompilationMXBean gm=(CompilationMXBean)ManagementFactory.getCompilationMXBean();
System.out.println("getName "+gm.getName());
System.out.println("getTotalCompilationTime "+gm.getTotalCompilationTime());
//获取多个内存池的使用情况
System.out.println("=======================MemoryPoolMXBean============================ ");
List<MemoryPoolMXBean> mpmList=ManagementFactory.getMemoryPoolMXBeans();
for(MemoryPoolMXBean mpm:mpmList){
System.out.println("getUsage "+mpm.getUsage());
System.out.println("getMemoryManagerNames "+mpm.getMemoryManagerNames().toString());
}
//获取GC的次数以及花费时间之类的信息
System.out.println("=======================MemoryPoolMXBean============================ ");
List<GarbageCollectorMXBean> gcmList=ManagementFactory.getGarbageCollectorMXBeans();
for(GarbageCollectorMXBean gcm:gcmList){
System.out.println("getName "+gcm.getName());
System.out.println("getMemoryPoolNames "+gcm.getMemoryPoolNames());
}
//获取运行时信息
System.out.println("=======================RuntimeMXBean============================ ");
RuntimeMXBean rmb=(RuntimeMXBean)ManagementFactory.getRuntimeMXBean();
System.out.println("getClassPath "+rmb.getClassPath());
System.out.println("getLibraryPath "+rmb.getLibraryPath());
System.out.println("getVmVersion "+rmb.getVmVersion());
10、Stream流处理和管道模型原理
java8 中常用的Stream类通过管道模型进行流处理,流处理就是流水线作业,流水线作业过程有管道模型的节点,每个节点有函数也就是算法进行处理,然后进入下一个流水线节点。
可以把Stream流处理理解成一个流水线作业,每个处理函数就是流水线上的工人。
Stream的相关类图结构:Stream
11、System.gc()
java代码给出了手动执行垃圾回收的方法,通过在jvm参数中设置-verbose:gc可以在控制台输出gc日志。
调试可以看到在执行System.gc()后有两行gc日志,分别是young gc,full gc
一般垃圾回收都是先触发young gc,然后把young gc后的对象放入full gc过程中会触发full gc。
注:执行System.gc()后也不会立即执行垃圾回收操作,具体什么时候执行垃圾回收看jvm分析。
12、System.in.read()
避免控制台程序直接退出,控制台有输入才会结束主线程。如敲个回车。
反射
Java反射是Java语言的一个非常重要的功能,是大多数Java框架现实的技术支撑点,如Spring、Mybatis等。 其中Spring是一个Java对象的容器框架,是大部分Java项目或则其他开源框架的基础支撑框架。Mybatis是一个ORM的持久层组件,提供了Java对象和关系型数据库的映射。
原理
语法
JDK中的reflect包实现了Java反射的所有功能。
实战
动态代理
原理
注:
动态代理原理
动态代理源码分析
Idea调试动态代理输出重复
JDK动态代理是通过反射来获取类实例和调用method.invoke实现的。JDK动态代理是代理模式的一种实现方式,其只能代理接口。
jdk动态代理通过Proxy来生成代理类,并且传入代理类接口所有方法的统一调用InvocationHandler,这样可以统一处理所有需要代理的方法。
语法
动态代理主要是通过Proxy类newProxyInstance方法获取代理类,newProxyInstance方法通过传入的接口和InvocationHandler实例来生成实现InvocationHandler和集成Proxy的类对象,这样
实战
Mybatis插件实现
Mybatis通过JDK反射动态代理实现了重要的插件模块。通过plugin和reflection包实现了插件模块。
集合
多线程和锁
JVM
JVM内存模型
类加载过程
GC
IO
网络通信
Property参数
Properties读取properties文件
Environment读取环境参数
XML解析
字节码
1、查看javap反编译后的字节码
.java源码经过编译器编译后生成.class字节码文件,class文件是一个字节码文件。通过javap命令可以将字节码文件翻译成人可以读的形式,但是还是字节码文件。
16进制格式显示的字节码文件:
注:以16进制的形式查看java class 二进制形式的字节码文件:
- 如果是linux系统通过vim打开class文件后,输入%!xxd 就是以16进制显示class文件了。
字节码文件中按照下图信息中,按照顺序依次填充。
javap -verbose反编译后的字节码文件:
javac Demo.java //.java文件编译后生成.class文件
javap -verbose Demo.class
C:\Users\pacoson>javap -help
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
在反编译后的字节码中我们比较关注的是版本号、访问修饰符、常量池(存放类属性引用描述名、常量值和类名、方法名等)、方法表部分。
其中方法表部分就是代码逻辑部分:
- “Code区”:源代码对应的JVM指令操作码,在进行字节码增强时重点操作的就是“Code区”这一部分。
- “LineNumberTable”:行号表,将Code区的操作码和源代码中的行号对应,Debug时会起到作用(源代码走一行,需要走多少个JVM指令操作码)。
- “LocalVariableTable”:本地变量表,包含This和局部变量,之所以可以在每一个方法内部都可以调用This,是因为JVM将This作为每一个方法的第一个参数隐式进行传入。当然,这是针对非Static方法而言。
反编译方法表部分:
这是javap -verbose反编译后整体结构:
javap -c Demo.class反编译后的结果进行了简化,去掉了版本信息、常量池、修饰符信息、行数表等信息,保留了类、方法和code主要信息。
2、java字节码指令详解
字节码操作集合
在上图中,Code区的红色编号0~17,就是.java中的方法源代码编译后让JVM真正执行的操作码。为了帮助人们理解,反编译后看到的是十六进制操作码所对应的助记符,十六进制值操作码与助记符的对应关系,以及每一个操作码的用处可以查看Oracle官方文档进行了解,在需要用到时进行查阅即可。比如上图中第一个助记符为iconst_2,对应到图2中的字节码为0x05,用处是将int值2压入操作数栈中。以此类推,对0~17的助记符理解后,就是完整的add()方法的实现。
3、操作数栈和字节码
JVM的指令集是基于栈而不是寄存器,基于栈可以具备很好的跨平台性(因为寄存器指令集往往和硬件挂钩),但缺点在于,要完成同样的操作,基于栈的实现需要更多指令才能完成(因为栈只是一个FILO结构,需要频繁压栈出栈)。另外,由于栈是在内存实现的,而寄存器是在CPU的高速缓存区,相较而言,基于栈的速度要慢很多,这也是为了跨平台性而做出的牺牲。
我们在上文所说的操作码或者操作集合,其实控制的就是这个JVM的操作数栈。为了更直观地感受操作码是如何控制操作数栈的,以及理解常量池、变量表的作用,将add()方法的对操作数栈的操作制作为GIF,如下图14所示,图中仅截取了常量池中被引用的部分,以指令iconst_2开始到ireturn结束,与图13中Code区0~17的指令一一对应:
4、查看字节码工具
如果每次查看反编译后的字节码都使用javap命令的话,好非常繁琐。这里推荐一个Idea插件:jclasslib。使用效果如图15所示,代码编译后在菜单栏”View”中选择”Show Bytecode With jclasslib”,可以很直观地看到当前字节码文件的类信息、常量池、方法区等信息。