高阶笔记"/>
Java高阶笔记
Java语法糖
- 语法糖:Syntactic sugar
- 作用:make things easier to read or to express.
- 使得程序员更容易编写程序,更清晰准确表达逻辑业务,不仅仅是语法的转换,也包括一些小的改进。
for/for-each
- 没有分号,和C++类似for(个体:数组)
- 由5.0引入,语法更简介,避免越界错误,但是不可以删除替换元素,不知道当前具体索引,只能正向遍历不能反向遍历,不能同时遍历两个集合,性能与for接近
枚举类型
- 枚举变量:变量的取值只在一个有限的集合内。
- 可以使用enum声明枚举类,且都是Enum的子类,但不需要写extends,enum内部有多少个值就有多少个实例对象,不能直接new枚举类对象。
- 除了枚举的内容以外,还可以添加属性/构造函数/方法,构造函数只能是package-private(default)或者private,内部调用
- 所有的enum类型都是Enum的子类,也继承了相应方法。
- ordinal()返回枚举值所在的索引位置,从0开始;compareTo()比较两个枚举值的索引位置大小;toString()返回枚举值的字符串表示;valueOf()将字符串初始化为枚举对象;values()返回所有的枚举值
不定项参数
- 普通函数的形参列表是固定个数/类型/顺序
- 类型后面加…,如int…,可变参数,本质上是一个数组
- 限制条件:一个方法只能有一个不定项参数,且必须位于参数列表的最后;重载的优先级规则1:固定参数的方法,比可变参数优先级更高;重载优先级规则2:调用语句,同时与两个带可变参数的方法匹配,则报错。
静态导入
- import导入程序所需要的类
- import static导入一个类的静态方法和静态变量
- import static 导入一个类的静态方法和静态变量少使用*通配符,不滥用,最好具体到静态变量或方法;静态方法名具有明确特征,如有重名,需要补充类名
自动拆箱与装箱
- 作用:简化基本类型和对象转换的写法
- 基本类型:boolean/byte/char/int/short/long/float/double
- 对象: Boolean/Byte/Character/Integer/Short/Long/Float/Double
- 例如:Integer obj1=5;//自动装箱
- int a1=obj1;//自动拆箱
- 装箱:基本类型的值被封装为一个包装类对象
- 拆箱:一个包装类对象被拆开并获取相应的值
- 注意事项:装箱和拆箱是编译器的工作,在class中已经添加转化。虚拟机没有自动装箱和拆箱的语句。
- ==:基本类型是内容相同,对象是指针是否相同(内存同一个区域)
- 基本类型没有空值,对象有null,可能触发NullPointerException;当一个基础数据类型与封装类进行==,+,-,*,/运算时,会将封装类进行拆箱,对基础数据类型进行运算;谨慎使用多个非同类的数值类对象进行运算。
多异常并列
- 多个异常并列在一个catch中,用|隔开
- 多个异常之间不能有直接或间接的继承关系,如果有,编译器就会报错。
整数类型用二进制数赋值
- 避免二进制运算,byte/short/int/long
数字中的下划线
- 在数值字面量中使用下划线:增加数字的可读性和纠错功能,short/int/long/float/double,下划线只能出现数字之间,前后必须有数字,允许在二/八/十/十六进制的数字中使用
接口方法
- 8推出接口的默认方法/静态方法(都带实现的),为Lambda表达式提供支持
- 接口的默认方法:以default关键字标注,其他的定义和普通函数一样,有以下规则
- 默认方法不能重写Object的方法;实现类可以继承/重写父接口的默认方法;接口可以继承/重写父接口的默认方法/当父类和父接口都有同名同参数默认方法,子类继承父类的默认方法,这样可以兼容以前的代码;子类实现了两个接口(均有同名同参数的默认方法),那么编译失败,必须在子类中重写这个default方法
- 接口的静态方法:属于本接口,不属于子类/子接口,子类没有继承该静态方法,只能通过所在的接口名来调用
- 接口的私有方法,java9的特性
- 解决多个默认方法/静态方法的内容重复问题;似有方法属于本接口,只在本接口内使用,不属于子类/子接口;子类没有继承该私有方法,也无法调用/静态似有方法可以被静态或默认方法调用,非静态私有方法被默认方法调用
- 接口和抽象类对比:都是抽象的,都不可以被实例化,即不能被new,都可以有实现方法,都可以不需要继承者实现所有的方法
- 不同点:抽象类对多只能继承一个,接口可以实现多个;接口的变量默认是public static final,且必须有初值,子类不能修改;而抽象类的变量默认是default,子类可以继承修改;接口没有构造函数,抽象类有构造函数,接口没有main函数,抽象类可以有main函数,接口有public/default/private方法,抽象类有public/public/protected/不写关键字的default方法
try-with-resource(jdk7)
- try-with-resource比try-catch-finally更简便,会自动关闭资源
- 资源必须定义在try中,若已经在外面定义,则需要一个本地变量
- JDK9不再要求定义临时变量,可以直接使用外部资源变量
- try-with-resource原理,资源对象必须实现AutoCloseable接口,即实现close方法
ResourceBundle文件加载
- Java 8即以前默认以ISO-8859-1方式加载Properties文件,需要利用natice2ascii工具对文件进行转义
- ResourceBundle默认以UTF-8方式加载Properties文件,可以直接用UTF-8保存
var类型
- Java 10 推出var,局部变量推断:避免信息冗余,对齐了变量名,更容易阅读,本质上还是强类型语言,编译器,推断后不能更改
- var的限制:可以用在局部变量上,非类成员变量,可以再for/for-each循环中;声明时必须初始化/不能用在方法参数和返回类型/大面积滥用会使代码整体阅读性变差/var只在编译时起作用,没有在字节码中引入新的内容,也没有专门的JVM指令处理var
switch
- 支持的类型byte,short,int,char,String,Enumn枚举,不支持long/float/double
- switch,多分枝选择语句,多分枝可以合并,用->直接连接判定条件和动作
- switch直接在表达式赋值,代码块中break返回结果
泛型入门
- 集合,存放多个不同类型的对象,容易出现转型错误ClassCastException
- 解决方法:限定一种类型,不需要转型,没有转型错误
- 泛型的含义:编写的代码可以被很多不同类型的对象所使用
- 可以分为三种:泛型类:比如ArrayList,HashSet,HashMap;泛型方法:比如Collections,binarySearch,Arrays.sort;泛型接口List,Iterator等
- 泛型的本质:参数化类型,避免类型转换,代码可重用
- 同类:C++的模板,C#的泛型
自定义泛型设计
- 泛型类:具有泛型变量的类,在类名后用代表引入类型,多个字母表示多个引入类型,如<T,u>等,引入类型可以修饰成员变量/局部变量/参数/返回值,没有专门的template关键字
- 泛型类调用:传入具体的类即可
- 泛型方法具有泛型参数的方法,该方法可以再普通类/泛型类中,在修饰符后,返回类型前
- 泛型接口,T也可以再是一个泛型类
泛型类型限定
- 特定场合下,需要对类型进行限定,使用某些特定方法
- 比如T extends Comparable
- 规定T必须是Comparable的子类,extends固定,后面可以有多个,以&拼接
- extends限定可以有多个接口,但只能有一个类,且类必须排第一位,逗号隔开参数
- 泛型类之间的继承:Pair< S >和Pair没有任何关系,无论S和T之间是什么关系,泛型类可以拓展或实现其他的类
- 泛型通配符类型:上限界定符,Pair<? extends S>:表示Pair能接受的参数类型是S自身或子类
- 上述只能对元素进行get而不能set,编译器只能保证出来的类型,但不保证放入的对象是什么类型
- 下限界定符,Pair<? super S>,Pair能接受的类型参数,是S的自身或超类,只能set不能get,编译器保证放入的是S本身或超类,但不保证出来是什么具体类型,无法得知出来的对象类型,只能是Object
- 泛型PECS原则:要从泛型读取类型T的数据,并且不能写入,可以使用? extends通配符;如果要向泛型类中写入类型T的数据,并且不需要读取,可以使用? super通配符。
- 无限定类型的泛型:Pair,原始类型,Pair<?>,无限定通配符,表示任意类型,?getFirst()不确定出来的是什么类型,只能赋值给Object,void setFirst()无法放入任何对象,甚至是Object
泛型实现的本质
- JVM里面没有泛型对象,而是采用类型擦除技术,只有普通的类和方法
- 类型擦除:擦除泛型变量,替换为原始类型,无限定为Object;擦除泛型变量后,为了保证类型的安全性,需要自动进行类型转换
- 擦除泛型变量,为了保证类型的安全性,需要自动进行类型转换
- 重载泛型方法翻译(自动桥方法)
- 约束:不能用八种基本类型来实例化泛型;运行时类型查询只适用于原始类型;不能创建参数化类型的数组;可变参数警告;不能实例化类型变量;不能构造泛型数组;泛型类的静态上下文中类型变量无效;不能跑出或捕获泛型类的异常实例;可以消除对受查异常的检查;类型擦除后引发的方法冲突
Java类型的协变和逆变
- 面向对象语言,支持子类型
- 类型变化关系:更复杂类型中的子类型关系,与子类型之间的关系相关联
- A、B是类型,f(`)表示类型转换,≤表示继承关系,如A<B,表示A继承于B
- f(`)是协变的,如果A≤B,有f(A)≤f(B)
- f(`)是逆变的,如果A≤B,有f(B)≤f(A)
- f(`)是不变的,当上述两种都不成立,即f(B)和f(A)没有关系
- f(`)是双变的,如果A≤B,有f(A)≤f(B)和f(B)≤f(A)同时成立
- Java数组是协变的,String是Object的子类,String[]是Object[]的子类
- Java的原始的泛型是不变的,String是Object的子类,List和List没有关系,泛型可以采用通配符,支持协变和逆变pecs原则
- 符合情况:数组是协变的,泛型是不变的
反射入门
- 定义:程序可以访问、检测和修改它本身状态或行为的能力,即自描述和自控制。
- 可以下载运行时加载、探知和使用编译期间完全未知的类
- 给Java插上动态语言特性的翅膀,弥补强类型语言的不足
- 具体功能:在运行中分析类的能力;在运行中查看和操作对象,基于反射自由创建对象,反射构建出无法直接访问的类,set或者get到无法访问的成员变量,调用不可访问的方法;实现通用的数组操作代码;类似函数指针的功能
- 创建对象的方法:1是静态编译定义,2是克隆,实现Cloneable接口,克隆出来的对象与原对象内容一致,但地址不同;方法3是序列化和反序列化方法,序列化指把一个对象输出成一个文件流变成一个文件,然后我们在创建对象的时候把包含有数值的文件读进来重新new出一个对象来,序列化要求每个类都必须实现Serializable接口,有一个serialVersionUID就可以,如mybatis-plus中,反过来就是反序列化,是以文件作为中间载体来实现序列化和反序列化,但是序列化会引发安全漏洞,因为要写数据到外部文件里面去,谨慎使用;方法4和5反射,比如利用newInstance方法调用构造函数;然后是利用newInstance()方法,可以实现任意的类去实现类里面的方法。
反射关键类
- Class:类型标识,JVM为每个对象都保留其类型标识信息Runtime Type Identification,三种获取方法,getClass()方法,Class.forName()加载,通过getName()。getMethods()返回本类和所有父类所有的public方法,getDeclaredMethods()返回本类自己定义的包括private方法,但不包括父类的方法;
- Field:成员变量,getFields获取本类以及父类所有的public字段,getDeclaredFields获取本类所有包括private,只要setAccessible就可以获取private。
- Method:成员方法,类似。调用则使用Method.invoke方法。
- Constructor:构造函数,可以用getConstructors()获取构造函数,可以获取所需参数个数
- 父类/父接口,getSuperclass(),getInterfaces()等
反射应用
- 数据库连接,JDBC的Connection
- 数组扩充器,一旦创建,长度不能再修改,新建一个大的数组,然后把旧数组内容拷贝过去
- 动态执行方法,给定类名,方法名,即可执行,比如定时器,Springboot中的定时器任务
- Json和Java对象互转,Gson库即可实现。
- Tomcat的Servlet对象创建,通过反射产生所有的Servlet,基于doPost或doGet来响应前端的请求
- MyBatis的OR\M,数据库表和Java的POJO/DAO类相对应
- Spring的Bean容器,Ioc的Bean容器(HashMap)
- org.reflections,增强工具包
编译器API
- 编译器API,对.JAVA文件即时编译,对字符串及时编译,监听在编译过程中产生的警告和错误,在代码中运行编译器
- javaCompiler类是在程序中调用编译器接口,产生class文件,run方法较简单,可以编译源文件,产生class文件,但不能指定输出路径,监控错误信息,调用后就在源码所在目录生成class文件。getTask方法更强大的功能。可以编译java源文件,包括在内存中的java文件,生成class文件。
- 作用:JavaEE的JSP编译,在线编程环境,在线程序评判系统,自动化构建和测试工具
- 工具:Janino,InMemoryJavaCompile,JCI
代理模式
- 代理: 代替处理,代理服务器,代理经纪人(符合高内聚,低耦合的要求)
- 代理模式:23个经典模式的一种,又称委托模式
- 为目标对象提供(包装)了一个代理,这个代理可以控制对目标对象的访问,外界不用直接访问目标对象,而是访问代理对象,代理对象中可以添加监控和审查处理
- 名言:All problems in computer science can be solved by another level of indirection.下半句,但是这个中间层会引起新的问题
静态代理
- 定义:代理对象持有目标对象的句柄;所有调用目标对象的方法,都调用代理对象的方法,对每个方法,需要静态编码
- 比如在web项目中对请求的统一处理
动态代理
- 静态代理对象和目标对象都继承同一个接口
- 对目标对象的方法每次被调用,进行动态的拦截,比如Shiro框架,请求处理。转发给invoke()方法来使用。
- 代理处理器:持有目标对象的句柄,实现InvocationHandler接口,实现invoke方法,所有的代理对象方法调用,都会转发到invoke方法来,invoke的形参method,就是指代理对象方法的调用,在invoke内部,可以根据method,使用目标对象不同的方法来响应请求。
- 核心是invoke方法
- 代理对象:根据给定的接口,由Provy类自动生成的对象,类型com.sun.provy,通常和目标对象实现同样的接口,实现多个接口,接口的排序非常重要,当多个接口里面有方法重名,则默认以第一个接口的方法调用
AOP编程
- 面向切面编程:将通用需求功能从众多类中分离出来,使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可
- 作用:分离代码的耦合(高内聚,低耦合);业务逻辑变化不需要修改源代码/不用重启/加快编程和测试速度
- 比如热部署,NIO里的washService等
- 是OOP编程的补充,没有限定语言,主要内容包括配置文件,定义点,了解要发生的动作等
注解入门
- 例如 @SuppressWarning 压制警告
- @Override 重写方法标记
- 注解:Annotation,从JAVA5引入,位于源码中,使用其他工具进行处理的标签,注解用来修饰程序的元素,但不会对被修饰的对象有直接的影响,只有通过某种配套的工具才会对注解信息进行访问和处理
- 主要用途:提供信息给编译器/IDE工具;可用于其他工具来产生额外的代码/配置文件/有一些注解可在程序运行时访问,增加程序的动态性(通过反射)
- 可以自定义注解,关键字使用@interface
Java预定义的普通注解
- @Override,修饰方法,检查该方法是父类的方法,强制该函数代码必须符合父类中该方法的定义;避免代码错误
- @Deprecated方法,修饰类/类元素/包,标注为废除,建议不要使用
- @SuppressWarnings,可以修饰变量方法函数,类等,各种不同类型可以叠加,警告类型是编译器定的
自定义注解
- 扩展Annotation注解接口
- public @interface XXX
- 注解可以包含的类型:8种基本类型,String,Class,enum类型,注解类型,数组
- @Target可以限定位置。
Java预定义的元注解
- 元注解:修饰注解的注解
- 比如@Target,设置目标范围;@Retention,设置保持性;@Inherited,注解继承;@Repeatable,此注解可以重复修饰;@Documented 文档
- Retention(保留)这个注解用来修饰其他注解的存在范围,SOURCE仅存在于源码,不存在class文件,CLASS,存在于.class文件,但是不能被JVM加载,RUNTIME策略下,注解可以被JVM访问到,通常情况下,可以结合反射来做一些事情
- Target限定目标注解作用于上面位置@Target({ElementType.METHOD}),任何类型,即上面的的类型都可以修饰
- Inherited,让一个类和它的子类都包含某个注解,普通的注解没有继承功能
- Repeatable,表示被修饰的注解可以重复应用标注,需要定义注解和容器注解
- Documented指明可以被Javadoc工具解析,形成接口文档
注解的解析
- 注解保留方式,@Rentention注解,如果不写,默认是在CLASS层面,不加载入JVM里。
- 在JVM层面,则被JVM加载,可以用反射解析注解
- 如果是在CLASS层面,只能采用字节码工具如ASM进行处理。
- SOURCE层面,则使用的是注解处理器来使用。
RUNTIME注解的实现本质
- javap是java的反编译工具
- 注解调用路线:注解采用接口中的方法来表示变量;java为注解产生一个代理类,这个代理类保罗一个AnnotationInvocationHandler成员变量;AnnotationInvocationHandler有一个Map的成员变量,用来存储所有的注解的属性赋值;在程序中调用注解接口的方法,将会被代理类接管,然后根据方法名字,到Map里面拿对应的Value并返回。
- 传统的接口中的变量,都是public final static
- 注解需要随意赋值:注解方法表示变量;采用代理类拦截注解方法访问;所有的注解的赋值,都放在Map中,访问速度快
注解的应用
- Servlet 3.0的配置,传统式需要在web.xml配置,3.0以后支持注解配置,更简便;需要容器的支持
- JUnit测试
- Spring和Spring Boot
- Lombok,能够嵌入到IDE工具中的Java库,提供很多注解,用来消除冗长的代码
嵌套类入门
- 嵌套类:重点是嵌套,一个类定义在别的类的内部
- 嵌套类:Nested classes,静态嵌套类:Static nested classes,即类前面有static修饰符;非静态嵌套类:Non-static nested classes,又名内部类,Inner classes,分为普通内部类,局部内部类,匿名内部类
- 普通内部类:直接定义在类的里面。属于类的下面一级
- 局部内部类:定义在一个成员方法里面。
- 匿名内部类:通常使用在只需要使用一次的情境中,比如runnable在线程中
- 嵌套类产生原因:不同的访问权限要求,更细粒度的访问控制;简洁,避免过多的类定义;缺点:语言设计国语复杂,较难学习和使用
匿名内部类
- 嵌套类的语法
- 嵌套类和其他类的关系:嵌套类访问外部包围类;外部包围类访问嵌套类;第三方类访问嵌套类
- 匿名内部类:没有类名的内部类,必须继承一个父类/实现一个父接口;在实例化以后,迅速转型为父类/父接口;这种类型的对象,只能new一个对象,之后对对象名字操作;可在普通语句和成员变量赋值时使用内部类
- 普通的静态方法和静态变量都不能在匿名内部类里面定义,会屏蔽掉所有的
- 特点:没有正式类名的内部类,编译器产生内部名字:类名+$+数字编号;没有类名,没有构造函数,能用父类/父接口的构造函数(可带参数);可以继承、改写、补充、父类/父接口的方法;内部不可以新定义静态成员(变量+方法),常量除外,final static int a=5;可以访问外部包围类的成员变量和方法,包括private,如果定义在静态方法中,也只能访问外部包围类的静态成员;没有类名,外部包围类和其他类也无法访问到匿名内部类
局部内部类
- 定义在代码块中的非静态的类,如方法,for循环,if语句等;
- 定义后,即可创建对象使用;
- 只能活在这个代码块中,代码块结束后,外界无法使用该类。
- 编译后名称:外部类名+$+序号+内部类名;可以继承其他类,或者实现其它接口;非静态的类,不能包含静态成员(变量和方法),除了常量;可以访问外部包围类的成员;如果定义在静态方法中,只能访问包围类的静态成员;局部内部类不能是一个接口,即接口不能定义在代码块中。
普通内部类
- 普通内部类:非static的类,定义在某个类成员变量位置,定义后在类里面均可以使用
- 编译后名称:外部类名+$+内部类名;可以继承其他类,或者实现其它接口;可以用private/package private(不写)/protected/public控制外界访问;非静态的类,不能包含静态变量/方法,除了常量;和外部包围类的实例相关,一个普通内部类实例肯定是在一个外部包围类的实例中,且可以访问外部包围类的所有成员;在第三方类中,需要先创建外部包围类实例,才能创建普通内部类的实例,不允许单独的普通内部类对象存在
静态嵌套类
- 层级和包围类的成员变量/方法一样
- 第三方需要外部包围类才可以访问到静态嵌套类,前面加static
- 特点:需要加修饰符static,可以定义静态成员和非静态成员;不能直接访问包围类的非静态成员,可直接访问包围类的静态成员;课通过包围类的对象进行访问非静态成员;外界可以通过静态嵌套类访问其静态成员,通过对象访问其非静态成员;外界需要通过包围类才可以访问到静态嵌套类,并创建其对象,不需要外部包围类的实例。
嵌套类对比
- 匿名内部类:应用它,需要定义额外的变量和方法;局部内部类:在一个方法内,需要创建一个新的类型,并重复使用;普通内部类和局部内部类相似,在一个类中定义,可重复使用,可以访问外部类的成员,但不需要访问外部类方法的形参和内部变量;静态嵌套类:在一个类中定义,可重复使用,并需要访问外部类的静态成员
变量遮蔽
- Shadowing
- 嵌套类变量和外部包围类的变量重名:以离得近为优先原则;优先级高的变量会遮蔽优先级低的变量;外部包围类.this.变量名,可以访问到外部包围类的成员变量;静态嵌套类不能访问非静态变量;Java7以前匿名内部类和局部内部类只能访问外部包围类的final成员变量;Java 8以后,匿名内部类和局部内部类可访问外部包围类的final成员变量,事实意义上的final变量(effectively final,一个变量定值后,再也没有改过值)
嵌套类的应用
- 匿名内部类:无需类名,使用广泛,该类只要一个,且方法只有一个,代码短,比如在Android中最常用匿名内部类
- 局部内部类:定义在方法体内,只能在当前方法内使用,代码短,使用较少,介于匿名内部类和普通内部类之间,只用一次,就用匿名内部类,使用多次,那就上升到普通内部类,整个类都可以使用。
- 局部内部类:继承某一个类或接口,重新定义方法,并当作返回值在外部使用
如regex.Pattern的splitAsStream - 普通内部类:广泛使用在具有母子结构的类中,内部类与外部类有联系;如Map和Map.Entry,ZipFile等
- 静态嵌套类:和普通类一致,只是碰巧声明在一个外部类的内部;和外部类没有太多的练习,可以脱离外部类对象存在,也可以访问外部类静态成员;如果不需要访问外部类的非静态成员,尽量将普通内部类变为静态嵌套类,节省普通内部类和外围类的联系开销,使得外部类对象更容易被垃圾回收器回收。
Lambda表达式
- 面向过程程序语言:参数传递时基本类型的变量
- 面向对象语言:传递基本类型的变量,传递对象变量
- 传递方法/代码块(函数形式程序语言设计)
- Lambda表达式表示一个匿名函数,本身可以当作参数来传递
- 参数,箭头,一个表达式
- 比如(String first,String second)->first.length-second.length()
- 再比如:(first,second)->{int result=(-1)*(first.length()-second.length());return result;}
- 形参可以不写类型,可以从上下文中推断出来
- 特点:类似于匿名方法,一个没有名字的方法;参数箭头表达式语句;可以忽略写参数类型;坚决不声明返回值类型;没有任何修饰符;单句表达式,将直接返回值,不用大括号;带return语句,算多聚,必须用大括号
- 只有一个参数,可省略参数括号
- 如果有返回值,返回值会推断出来,无需声明,只在某几个分支有返回值,这样是不合法的。
函数式接口
- Lambda表达式自动成为接口方法的实现
- 函数式接口:是一个接口,符合Java接口的定义;只包含一个抽象方法的接口;可以包括其他的default方法,static方法,private方法;由于只有一个未实现的方法,所以Lambda表达式可以自动填上这个尚未实现的方法;采用Lambda表达式,可以自动创建出一个(伪)嵌套类的对象(没有实际的嵌套类class文件产生)然后使用,比真正嵌套类更加轻量
- Comparator接口有两个未实现的方法,任何实现Comparator接口的类,肯定继承了Object,也就有了equals实现。
- 系统自带的函数式接口:涵盖大部分常用的功能,可以重复使用;位于java.util.function包中
- 系统自带的函数式接口(部分常用)接口参数返回值示例
- 如Predicate< T >接收一个参数返回一个布尔值,Consumer< T >接受一个参数,无返回;Function< T,R >,接受一个参数,返回一个值;Supplier< T >没有参数,返回值T,像数据工厂
方法引用
- 方法引用:支持传递现有的类库函数
- 引用方法:Class::staticMethod,如Math::abs;Class::instanceMethod,如String::compareToIgnoreCase;object::instanceMethod,如System.out::println,两种,一种是this::instanceMethod调用,super::instanceMethod;Class::new,调用某类构造函数,支持单个对象构建;Class[]::new,调用某类构造函数,支持数组对象构建
Lambda表达式应用
- 没有存储目标类型的信息
- 重载调用:依据重载的规则和类型参数推理
- Lambda表达式变量遮蔽:Lambda表达式和匿名内部类/局部内部类一样,可以捕获变量,即可访问外部嵌套块的变量,但是变量要求是final或者是effectively final的
- Lambda表达式没有变量遮蔽问题,因为它的内容和嵌套块有着相同的作用域
- 在Lambda表达式中,不可以声明与(外部嵌套块)局部变量同名的参数或者局部变量
- Lambda表达式的this指代就是创建合格表达式方法的this参数
- 优先级比嵌套类要搞,无法创建命名实例,无法获取自身的引用
Java流的概述
- 一个流对外提供一个接口,可以访问到一串特定的数据。流不存储元素,但是可以根据计算转化。
- 聚合操作,流支持像sql操作或者其他函数式语言的操作,如filter/map/reduce/find/match/sorted等
- 流的特点:很多流操作也是返回一个流;流操作进行迭代,用户感知不到循环遍历
- Stream语法:类似SQL语句,遵循“做什么而非怎么做”原则,一条链式语句
- 流的工作流程:流的创建;流的转换,将流转换为其他流的中间操作,可包括多个步骤(惰性操作);流的计算结果,这个操作会强制之前的惰性操作。这个步骤以后,流就不用了
流的创建
- Collection接口的stream方法,如Stream as=new ArrayList().stream(); String hs=new HashSet().stream();再如LinkedList,LinkedSet,TreeSet,Vector等
- Arrays.stream可以将数组转为Stream,如Stream b1=Arrays.stream(“a,b,c,d,e”.split(","),3,5);
- 利用Stream类进行转化,of方法,直接将数组转化,如Stream c1=Stream.of(new Integer[5]); Stream c2=Stream.of(“a,b,c”.split(","));Stream c3=Stream.of(“a”,“b”,“c”);
- empty方法,可以产生一个空流,如Stream d1=Sream.empty();
- 利用generate方法,接收一个Lambda表达式,如Stream e1=Stream.generate(()->“hello”);Stream e2=Stream.generate(Math::random);
- 如iterate方法,接收一个种子和一个Lambda表达式,如Stream e3=Stream.iterate(BigInteger.ZERO,n->n.add(BigInteger.ONE));
- 基本类型流:只有三种,IntStream,LongStream,DoubleStream
- 并行流:使得所有的中间转换操作都将被合并化;Collections.parallelStream()将任何集合转为并行流;Stream.parrallel()方法,产生一个并行流;需要保证传给并行流的操作不存在竞争。
- 其他类/方法产生Stream流,如Files.line方法,又如Pattern的splitAsStream方法
流的转换
- Stream的转换,是从一种流到另外一种流,有以下操作,比如过滤,去重;排序;转化;抽取;跳过;连接;其他;
- 过滤(filter),filter(Predicate<? super T> predicate),接收一个Lambda表达式,对每个元素进行判定,符合条件的留下;如:Stream s1=Stream.of(1,2,3,4,5);Stream s2=s1.filter(n->n>2);s2.forEach(Systeam.out::println);
- 去重(distinct),distinct(),去除重复元素只留下不重复元素;如果是普通对象流,对象的判断会先调用hashCode方法,再调用equals方法。
- 排序(sorted)对流的基本类型包装类元素进行排序;自身提供Comparator,在sorted括号内传入比较器即可;对普通对象会默认调用对象的compareTo
- 转化(map),利用方法对流的每个元素进行函数计算,如Stream s1=Stream.of(-1.5,2.5,-3.5);Stream s2=s1.map(Math::abs);s2.forEach(Systeam.out::println);还可以通过Lambda表达式对流的每个元素进行函数计算
- 抽取limit,如s1.limit(3),抽取3个
- 跳过skip,如skip(8)跳过第八个
- 连接concat,Stream.concat(letters(),letters());
- 额外调试peek
Optional类型
- 空指针异常
- 如何解决:optional一个包装器对象,要么包装了类型T的对象,要么没有包装任何对象(还是null);如果T有值,那么直接返回T的对象;如果T是null,那么可以返回一个替代物
- Optional创建,of方法,empty方法,ofNullable方法,对于对象有可能为null情况下,安全创建
- 方法:get方法,获取值,不安全;orElse方法,获取值,如果为null,采用替代物的值;orElseGet方法,获取值,如果为null,采用Lambda表达式值返回;orElseThrow方法,获取值,如果为null,抛出异常;ifPresent方法,判断是否为空,不为空返回true;isPresent方法,判断是否为空,如果不为空,则进行后续Consumer操作,如果为空,则不做任何事情;map(Function),将值传递给Function函数进行计算。如果为空,则不计算
- 注意事项,直接使用get容易引发异常;使用isPresent判断值是否存在,这和判断null是一样的低效
流的计算结果
- 流的计算:简单约简(聚合函数):count/max/min/…;自定义约简:reduce;查看/遍历元素:iterator/forEach;存放到数据结构中
- 简单约简(聚合函数):count()计数;max(Comparator)最大值;min最小值;findFirst,找到第一个元素;findAny找到任意一个元素;anyMatch(Predicate);allMathch;noneMathch
- 自定义约简:reduce,传递一个二元函数BinaryOperator,对流元素进行计算,如求和求积字符串连接等
- 流的计算:查看/遍历元素,iterator()遍历元素,forEach(Consumer)应用一个函数到每个元素上
- 流的计算:存放到数据结构中,toArray(),collect(Collectors.toList());collect(Collectors.toSet());collect(Collectors.toMap());collect(Collectors.joining())将结果连接起来;
- 流的高阶计算:分组groupingBy和分区partitionBy,分组后的约简:counting,summing,maxBy,minBy
流的应用
- 优点:统一转换元素;过滤元素;利用单个操作合并元素;将元素序列存放到某一个集合中;搜索满足某些条件的元素的序列;类似SQL操作,遵循“做什么而非怎么做”原则;简化了串行/并行大批量操作
- 与循环迭代代码的比较:Stream广泛使用Lambda表达式,只能读取外围的final或者effectively final变量,循环迭代代码可以读取/修改任意的局部变量;在循环迭代代码块中,可以随意break/continue/return,或者抛出异常,而Lambda表达式无法完成这些事情;Stream流不是淘汰循环迭代代码,应该是两者相互搭配使用
- 一个流,一次只能一个用途,不能多个用途,用了不能再用
- 避免创建无限流,需要对流元素进行限制,比如limit
- 要注意操作顺序
- 谨慎使用并行流:底层使用Fork-Join Pool,处理计算密集型任务;数据量过小不用;数据结构不容易分解的时候不用,如LinkedList等;数据频繁拆箱装箱不用;涉及findFirst或者limit的时候不用
- Stream和Collection对比:两者可以互相转化;如果数据可能无限,用Stream,如果数据很大很大用Stream,如果调用者将使用查找/过滤/聚合等操作,用Stream;当调用者使用过程中,发生数据改变,而调用者需要对数据一致性有较高要求,用Collection
Java模块化概述
- JDK8即以前都是以jar为中心来开发的
- jar文件无法控制别人访问其内部的public类;无法控制不同jar包中,相同的类名;Java运行时,无法判断classpath路径上的jar中有多少个不同版本的文件。Java加载第一个符合名字的类;Java运行时,无法预判classpath路径上是否缺失了一些关键类
- 模块化必须遵循的三个原则:强封装性:一个模块必须能够对其他模块隐藏其部分代码;定义良好的接口:模块必须向其他模块公开定义良好且稳定的接口;显式依赖:明确一个模块需要哪些模块的支持才能完成工作。
- Java 9开始引入新的模块化系统:Jigsaw拼图:以模块为中心;对JDK本身进行模块化;提供一个应用程序可以使用的模块系统;优点:可靠的配置,强封装性,可扩展开发,安全性,性能优化
- 以Java11为例,一共有71个模块,最底层的是java.base,每个模块都有明确的依赖模块,不存在循环依赖;使用java --list-modules可以查看JDK的模块列表,每个类都自动引用java.base模块;使用java ==describe-module查看平台模块声明
模块创建和运行
- 命令行创建Module,新建项目主目录,创建src目录,modules目录,lib目录,在src下面建立module.hello,新建module-info.java,编译/运行/打包,链接jlink,制作自定义时的映像,舍弃无用庞大的JDK库,适合在容器中快速部署运行
- Java的模块和Maven是不一样的,Maven只是对开发的编排方式
模块信息文件
- module-info.java,模块安全控制的核心;是模块和外界沟通的总负责,名字和内容组成;模块名字:模块名称必须唯一;可以不和包名相同,使用有代表性的词语;不需要包括版本号
- requires可以添加调用其他的模块,采用requires transitive,java.se本质上是个聚合模块
- exports将当前模块输出,只有输出,别人才能使用,exports可以指定某些包输出,限定输出到特定的模块使用
- opens将当前模块开放用于反射,只有public部分可以反射
服务
- 传统对象创建方法会带来耦合问题
- 采用工厂模式来解耦对象创建
- 服务:Java模块系统引入的新功能,实现解耦;模块对外只暴露接口,隐藏实现类,provides提供接口,with实现类,uses消费接口,ServiceLoader加载接口的实现类
- ServiceLoader通过load加载接口的实现类(with语句提供)每次load,都会产生新的各自独享的实例,没有唯一的服务实例;可以调用reload进行刷新
- 两种方法创建服务实例:服务实现类有public的无参构造函数;使用单独的静态提供者方法,一个名为provider的public static无参数方法,返回服务接口或子类
模块化应用
- 传统缺点:过于简单,难以适应复杂的权限要求,程序员可以随意访问和控制代码。(反射)
- 从根源上对JDK进行模块化,降低最终程序运行时负载;在jar层上增加一个module机制;引入exports/requires/opens明确模块边界和依赖关系,程序更隐私安全;引入服务provides/uses使得程序更解耦;jlink制作运行时映像,使得运维更高效
- 制约因素:尚未成熟,存在较多变化,java0版权收费,重构困难,构建工具并未大力支持。
Java字节码概述
- Java开发过程:编写阶段,采用各种编辑工具,编写.java文件;编译阶段:采用javac.exe对.java文件编译,产生.class文件;运行阶段:采用java.exe加载.class文件运行
- .class文件:字节码文件,class文件是java一次编译到处运行的基础;class文件具有平台无关性;由JVM执行;每个class文件包含了一个类或接口或模块的定义;class文件是一个二进制文件,由JVM定义class文件的规范;任何满足这种规范的class文件都会被JVM加载运行;class文件可以由其他语言编译生成,甚至不用程序语言直接生成;JDK版本不同,所编译出.class文件略有不同
Java字节码文件构成
- class文件构成:采用类似于C语言结构体的结构来表示数据;包括两种数据类型:定长数据:无符号数,u1,u2,u4(分别代表1个字节,2个字节,4个字节的无符号数),不定长数据:由多个无符号数组成,通常在数据的前面给出其长度
- 前四个字节为魔数,十六进制表示为0xCAFEBABE,标识该文件为class文件
- 第5·6字节表示表示次版本号,7、8字节表示主版本号,主版本号与JDK版本有映射关系,右图为JVM规范中列出的映射关系
- JDK提供了javap来对class文件做反汇编。
- 接着是 常量池主要存放两大常量类:字面量,如文本字符串,final的常量值等;符号引用:类和接口的全限定名;字段的名称和描述符;方法的名称和描述符
- 常量池结束之后的两个字节,描述该Class是类还是接口,以及是否被public,abstract,final等修饰符修饰
- 接下来是类索引,父类索引,接口索引集合
- 字段表:用于描述类和接口中声明的变量,包含类级别的变量以及实例变量,但是不包括方法内部声明的局部变量;字段表也分为两部分,第一部分为两个字节,描述字段个数;第二部分是每个字段的详细信息。
- 字段表结束后为方法表,方法表也由两部分组成,第一部分为两个字节描述方法的个数;第二部分为每个方法的详细信息。方法的详细信息较为复杂,包括方法的访问标志、方法名、方法的描述符以及方法的属性。
- 方法部分属性:Code,源代码对应的JVM指令操作码。
- LineNumberTable,行号表,将Code区的操作码和源代码中的行号对应
- 字节码的最后一部分,该项存放了在该文件中类或接口所定义属性的基本信息。
- 属性信息相对灵活,编译器可自由写入属性信息,JVM会忽略不认识的属性信息
Java字节码指令分类
- class文件被JVM加载后,就执行其代码,每一个Java字节码指令,是一个byte数字,也有一个对应的助记符,目前总数200多个
- iload:从本地变量表到操作站中。
- JVM基础Heap,Stack,Frame
- 指令分类
- 加载和存储指令:用于将数据在栈帧中的局部变量表和操作数栈之间来回传输
- 将一个局部变量加载到操作栈:iload,lload,fload,dload,aload等;将一个数值从操作数栈存储到局部变量表:istore,lstore,fstore,dstore,astore等;将一个常量加载到操作数栈:bipush,sipush,ldc,lcd_w,ldc2_w,aconst_null,iconst_m1等
- 运算指令:iadd,isub,imul,idiv等
- 类型转换指令:i2b,i2l,i2s等
- 对象/数组创建与访问指令:new,newarray,getfield等
- 操作数栈管理指令:pop,dup等
- 控制转移指令:lfeq,goto等;
- 方法调用和返回指令:invokevirtual,ireturn等
- 异常处理指令:athrow
- 同步控制指令:monitorenter,monitorexit等
- JVM指令是由操作码和零至多个操作数组成,操作码OpCode,代表着某种特定操作含义的数字,操作数,Operand,操作所需要的参数。
- JVM指令集是基于栈而不是寄存器,字节码指令控制的是JVM操作数栈
Java字节码操作ASM
- ASM是生成,转换,分析class文件的工具包,体积小,轻量,快速,文档完善
- Core API,解析XML文件的SAX方式,流模型解析,非常节约内存,但是编程难度大。
- Tree API类比解析XML的树结构分析,消耗内存较大。
- 核心类:ClassReader用于读取已经编译好的.class文件;ClassWriter用于重新构建编译后的类,如修改类名,属性以及方法,也可也生成新的类的字节码文件。
- Visitor类:CoreAPI根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor,举例如下,MethodVisitor用于访问类方法;FieldVisitor访问类变量,AnnotationVisitor用于访问注解
Java字节码增强
- 字节码操作: 通常在字节码使用之前完成,源码、编译、运行。
- 字节码增强;运行时对字节码进行修改/调换,如Java ClassLoader类加载器;JavaInstrument
- Java Instrument,对程序的替换,都是通过代理程序进行,支持在main函数之前,对类的字节码进行修改/替换,agentmain,支持在程序运行过程中,对字节码进行替换。
- Java运行之前代理:在main函数运行之前,修改/替换某类的字节码;启动Java程序时,给java.exe增加一个参数;在someone.jar的清单文件(manifest)制定了Premain-Class:SomeAgent,SomeAgent类,有一个premain方法,此方法先于main运行;premain方法有一个Instrumentation的形参,可以调用;addTransformer方法,增加一个ClassTransformer转换类,自定义一个ClassTransFormer类,重写tranform方法,修改/替换字节码
- Java运行时代理:在main函数运行时,修改某类字节码;Test调用Greeting类工作,编写AttachToTest类,对Test进程中附加一个agent(jar),在jar中,利用Instrument对Greeting类进行retransformClasses,重新加载;对进程附加agent,是JVMTI的技术
- 类替换的注意事项:可以修改方法体和常量池,不可以增加修改成员变量/方法定义/不可以修改继承关系/未来版本还会增加限制条件
Java字节码保护
- .java是程序源码,需要保护;字节码文件是程序运行的主题,遵守JVM规范,且被分发使用;为了各种需要,产生出很多反编译工具,从字节码恢复源码
- 字节码保护:字节码加密,对字节码进行加密,不再遵守JVM指定的规范,JVM加载之前,对字节码解密后再加载;
- 字节码混淆:被混淆的代码依然遵循JVM指定的规范,变量命名和程序流程上进行等效替换,使得程序的可读性变差;代码难以被理解和重用,达到保护代码的效果。
- 保护工具ProGuard,可以命令行运行,也可也集成到Eclipse等IDE中使用,不仅可以处理Java代码,也可也处理Android的代码。
- ProGuard可以和Maven相结合。
- 注意事项:反射调用或者方法,可能失败;对外接口的类和方法,不要混淆,否则调用失败,嵌套类混淆,导致调用失败;natice的方法不要混淆;枚举类不要混淆
字节码的总结和展望
- 操作:生成,操作,运行时修改,Java Instrument,Class Loader,混淆和加密,运行时效率增强
- Java字节码操作是框架软件实现的重要手段,OpenJDK,MyBatis,Hibernate等
- 字节码文件的构成,字节码指令的分类,掌握一种字节码的操作工具,剖析一个框架软件的字节码操作。
Java类加载机制
- 程序是依靠多个Java类共同协作完成的,JVM依据classpath执行的类库的顺序来查找类;潜在的问题:如何找到正确的类,如classpath路径的前后,如何避免恶意的类,如一个假的String类,加载的顺序,如先加载父类,还是子类
- 类加载器ClassLoader:负责查找,加载,校验字节码的应用程序,java.lang.ClassLoader,load根据名字加载一个类,返回类的实例;defineClass(String name,byte[] b,int off, int len)将一个字节流定义为一个类;findClass(String name)查找一个类;findLoadedClass(String name)在已加载的类中,查找一个类;成员变量ClassLoader parent
- JVM四级类加载器:启动类加载器,系统类rt.jar;扩展类加载器,jre/lib/ext;应用类加载器APP,classpath;用户自定义加载器Plugin,程序自定义
- 类加载器双亲委托模型:首先判断是否已经加载,若无,找父加载器加载,若再无,由当前加载器加载。
- 每个类的Class信息,都有一个ClassLoader成员变量
Java类双亲委托加载扩展
- Java严格执行双亲委托机制,类会由最顶层的加载器来加载,如果没有,才由下级加载器加载;委托是单向的,确保上层核心的类的正确性;但是上级类加载器所加载的类,无法访问下级类加载器所加载的类。(JDBC和XML Parser等)
- 双亲委托的补充,执行java时候添加虚拟机参数,将类路径配置为Bootstrap等级。
- 使用ServiceLoader.load方法来加载(底层加载器所加载的类),以JDBC加载为例.DriverManager需要访问到Driver类
- ServiceLoader是jdk6引入的,是用于加载服务的一种工具,服务有接口定义和具体实现类(服务提供者),SPI机制
- 一个服务提供者会在jar包中有META-INF/services目录,里面放一个文件,名字同接口名字。内容的每一行都是接口的一个实现类,load方法,可以用当前线程的类加载器来获取某接口的所有实现,当然也都是转为接口类来使用
Java自定义类加载路径
- 自定义加载路径:弥补类搜索路径静态的不足;URLClassLoader从多个URL中加载类
- 自定义类加载器:继承ClassLoader类,重写findClass(String className)方法
- URLClassLoader继承于ClassLoader,程序运行时增加新的类加载路径,可从多个来源中加载类:目录,jar包,网络;addURL添加路径,close方法关闭。
- 热部署的实现原理
自定义类加载器
- 自定义类加载器:继承ClassLoader类;重写findClass方法,使用时,默认先调用loadClass来查看是否已经加载过,然后委托双亲加载,如果都没有,再通过findClass加载返回,在findClass中,首先读取字节码文件,然后再调用defineClass将类注册到虚拟机中,可以重写loadClass方法来突破双亲加载。
- 同一个类可以被不同层级的加载器加载,且作为两个类对待
Java类加载器总结和展望
- 优点:层次分明,逐级加载,确保核心类可信,兼顾静态和动态。
- 动态性:使用虚拟机参数-Xbootclasspath,将jar或者目录提高到Bootstrap等级;使用ServiceLoader和SPI机制,实现上层加载器的类,访问下层加载器的类;使用URLClassLoader,可以在运行时增加新的classpath路径;使用自定义ClassLoader,可以通过重写loadClass和findClass方法动态加载字节码,还可以在加载字节码过程中修改/校验等操作。
- JVM类装载过程,加载loading,链接,验证,准备,解析,初始化,执行类的初始化方法
- 类的加载和隔离,基于类加载器,诞生OSGi,在OSGi容器里面运行bundle,通过类加载器来控制类的可见性。
JVM概述
- 虚拟机:VM,逻辑上一台虚拟的计算机,实际上一个软件,能够执行一系列虚拟的计算指令,系统虚拟机:对物理计算机的仿真,如VMWare,Oracle VirtualBox等;软件虚拟机,专门为单个计算程序而涉及,如JVM等
JVM内存分类
- 自动内存管理机制
- 线程私有内存:程序计数器,Java虚拟机栈,本地方法栈
- 多线程共享内存:Heap堆,方法去MethodArea,运行时常量池
- 程序计数器:PC Register,一块小内存,每个线程都有;PC存储当前方法,线程正在执行的方法称为线程的当前方法;当前方法为本地方法时,pc值未定义,当前方法为非本地方法时,pc包含了当前正在执行指令的地址,当前唯一一块不会引发OutOfMemoryError异常
- JVM栈:每个线程都有自己独立的Java虚拟机栈,线程私有;-Xss设置每个线程堆栈的大小。
- Java方法的执行基于栈,每个方法从调用到完成对应的一个栈帧在栈中入栈出栈的过程:栈帧存储局部变量表,操作数栈等;局部变量表存放方法中存在栈里面的东西。
- 引入的异常:栈的深度抄国虚拟机规定深度,栈溢出异常;无法扩展内存,OutOfMemoryError异常
- 本地方法栈:存储native方法的执行信息,线程私有;VM规范没有对本低方法栈做明显规定,引发的异常,栈的深度抄国虚拟机规定深度,StackOverflowError,无法扩展也是一样。
- 最大的一块堆内存:虚拟机启动时创建,所有线程共享,占地最大;对象实例和数组都是在堆上分配内存;垃圾回收的主要区域;设置大小,xms初始堆值,-Xmx最大堆值;引入无法分配的异常。
- 方法区(Method Area):存储JVM已经加载类的结构,所有线程共享,运行时常量池,类信息,常量,静态变量等;JVM启动时创建,逻辑上属于堆的一部分;很少做垃圾回收;引发的异常,无法分配内存异常。
- 运行时的常量池:Class文件中常量池的运行时表示;属于方法区的一部分,动态性:java语言并不要求常量一定只有在编译期产生,比如String.intern方法;引发无法分配内存的异常。
JVM内存参数
- JVM默认运行参数:支持JVM运行的重要配置,根据操作系统/物理硬件不同而不同,使用-XX:+PrintFlagsFinal显式VM的参数
- 程序启动的两类参数:程序参数:程序需要,存储在main函数的形参数组中;虚拟机参数:更改默认配置,用以指导进程运行
- Heap共享,内存大户,存储所有的对象和数组,-Xms初始堆值,-Xmx最大堆值
- JVM栈,线程私有,存储类中每个方法的内容,Xss最大栈值
- 方法区:存储类信息,常量池等,永久区,元数据区
Java对象引用
- JVM内置有垃圾收集器,GC,自动清除无用的对象,回收内存;
- 垃圾收集器的工作职责:判定无用的对象,何时启动不影响程序的正常运行,如何回收,速度快,时间短,影响小
- Java对象的生命周期:对象通过构造函数创建,但是没有析构函数回收内存,对象存活在离它最近的一对大括号中。
- Java语言关于内存回收的API,Object的finalize方法,垃圾收集器在回收对象时调用,有且仅被调用一次;System的gc方法,运行垃圾收集器。
- 基于对象引用判定无用对象:零引用,互引用等;对象引用链:通过一系列的称为”GC Roots“的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用凸轮的话来说,就是从GCRoots到这个对象不可达)时,证明此对象是不可用的
- 对象可达性分析
- GC Roots对象包括:虚拟机中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象
- 强引用:例如Object obj=new Object();Object obj2=obj;只有强引用还存在,对象就不会被回收,哪怕发生OOM异常。
- 软引用:描述有用但并非必须的对象;在系统将要发生内存溢出异常之前,会把这些对象列为可回收;JDK提供了SoftReference类来实现软引用
- 弱引用:描述非必须对象,比软引用强度更弱些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前;JDK提供了WeakReference类实现弱引用;
- 虚引用:最弱的引用关系,JDK提供PhantomReference实现虚引用;为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,用于对象回收跟踪。
- 强引用:正常赋值,不回收;软引用:内存紧张时回收;弱引用:GC就回收;虚引用:随时可能被回收。
- 软引用和弱引用适合用来保存可有可无的缓存数据
垃圾收集算法
- 引用计数法:一种古老的算法,每个对象都有一个引用计数器,有引用,计数器加一,当引用失效,计数器减一,计数器为0的对象,将会被回收;优点:简单效率高;缺点:无法识别对象之间的相互循环引用
- 标记-清除算法:标记阶段,标记出所有需要回收的对象;回收阶段:统一回收所有被标记的对象;优点:简单;缺点:效率不高,内存碎片
- 复制算法:将当前可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就像还存活着的对象赋值到另外一块上面,然后再把已使用过的内存空间一次清理掉;优点:简单、高效;缺点:可用内存减少,对象存活率高时复制操作较多。
- 标记-整理算法:标记阶段:与标记清除算法一样;整理阶段:让所有存活的对象向一端移动,然后直接清理掉端边界之外的内存;优点:避免碎片产生,无需两块相同内存;缺点:计算代价大,标记清除+碎片整理,更新引用地址。
- 分代收集算法:Java对象的生命周期不同,有长有短,根据对象存货周期,将内存划分新生代和老年代;新生代:主要存放短暂生命周期的对象,新创建的对象都先放入新生代,大部分新建对象在第一次gc时被回收;老年代:一个对象经过几次gc扔存活,则放入老年代,这些对象可以活很长时间,或者伴随程序一生,需要常驻内存的,可以减少回收次数。
- 分代收集:针对各个年代特点采用合适的收集算法:新生代复制算法,老年代标记清除或标记整理
JVM堆内存参数设置和GC跟踪
- -Xms初始堆大小/-Xmx最大堆大小/-Xmn新生代大小/-XX:SurvivorRatio设置eden区/from(to)的比例;-XX:NewRatio设置老年代/新生代比例;-XX:+PrintGC/-XX:+PrintGCDetails打印GC的过程信息。
- 现有的垃圾收集器:串行收集器,并行收集器,CMS收集器,G1收集器,Z收集器
运行管理概述
- Class运行在虚拟机上,虚拟机运行在OS上
- 整体分为JVM管理和OS管理
- OS管理:进程级别的管理(黑盒),CPU/内存/IO等具体性能监控
- JVM管理:线程/程序级别的管理(白盒);查看虚拟机运行时各项信息;跟踪程序的执行过程,查看程序运行时信息;限制程序对资源的使用;将内存导出为文件进行具体分析。
OS层管理
- Linux平台管理:-top命令,查看系统中最占资源的部分;-vmstat命令,查看系统整体的CPU/内存/IO/Swap等信息;-iostat命令,查看系统详细的IO信息。
- Windows平台管理:任务管理器,perfmon性能监视器
JDK管理工具
- JDK提供很多工具:编译运行工具 javac/java;打包工具 jar;文档工具 javadoc;国际化工具:native2ascii;混合编程工具:javah;反编译工具:javap;程序运行管理工具:jps/jstat/jinfo/jstack/jsatd/jcmd
- jps:查看当前系统的java进程信息,显式main函数所在类的名字,-m可以显示进程的参数,-l显示程序的全路径;-v显示传递给Java的main函数参数
- jstat:查看堆信息,SOC,S0大小,S1C,S1大小,S0U,S0已使用,S1U,S1已使用;EC,Eden大小,EU,Eden已使用大小;OC,老年代大小,OU老年代已使用大小;YGC,新生代GC次数,YGCT,新生代GC时间;FGC,FullGC次数,FGCT,Full GC时间,GCT,GC总耗时
- jinfo:查看虚拟机参数,-jinfo -flag[+/-] 设置某些参数(布尔值);-jinfo -flag =设置某些参数值
- jstack:查看线程堆栈信息,查看线程拥有的锁,分析线程的死锁原因
- jstatd:客户机工具可以查看远程Java进程;本质上,是一个RMI服务器,默认驻守1099端口
- jcmd综合性工具,查看java进程,导出进程信息,执行GC等操作,jcmd直接查看进程,jcmd help展示命令的参数列表
可视化管理工具
- Jconsole,Visual VM,Mission Control
- JConsole:监管本地Java进程;监管远程Java进程:需要远程进程启动JMX服务
堆文件分析
- jmap:可以统计对内对象实例
- jmap:可以生成堆内存dump文件
- jhat:jdk自带的工具,自jdk9开始,被VM代替,解析hprof文件,并通过http方式进行展示
- jhat支持OQL类SQL语法,可以快速查询对象
JMX
- 是一个为应用程序植入管理功能的框架,用户可以在任何Java应用程序中使用这些代理和服务实现
- MBean代表着一个被管理的对象,类似JavaBean,对外暴露一个管理接口,即一些可读/写的属性
- Agent:一个MBean的容器,代表着一群被管理的对象;外界通过Agent可以访问到MBean,Agent的核心是MbeanServer,Mbean在MbeanServer中注册;Agent也包含一个通讯适配器或连接器,使得外界的管理者接入
- JMX的优点:不需要较大成本,即可管理应用程序;提供一套标准化方法,来管理基于Java的应用程序;被用来作为JVM的外在管理方式;提供了一个可扩展,动态的管理结构;充分利用已有的Java技术;容易和现有的管理技术进行集成
Java运行安全
- 程序来源繁杂:自定义的类,第三方jar包,网络下载
- 非法访问某些目录;打开Socket链接;退出虚拟机
- Java提供安全管理器:对程序的行为进行安全判定,如违反,报错;加载安全策略文件:一个权限集合,包含各种权限,规定哪些程序可以做哪些功能;默认情况,普通程序不加载安全管理器;启动安全管理器
- 安全策略文件:建立代码来源和访问权限的关系;代码来源:代码位置,证书集;权限:由安全管理器负责加载的安全属性
- 系统默认的安全策略文件:%JAVA_HOME%/jre/lib/security/java.policy
- 数字签名,确保类文件正确
更多推荐
Java高阶笔记
发布评论