洋蔥

贪婪,找不到比这更好的词了,是件好事。

SQL 窗口

PL/SQL Developer 工具的 SQL 窗口只能执行 SQL 和 PLSQL,执行命令使用命令窗口。

分页查询

https://www.youtube.com/watch?v=BHzgsKi3dI4&list=PLD3Xyx6ef38yPjePP9e0yfIwX4lGOcb3w&index=28

复合索引

建立时的字段顺序和条件中的字段顺序相同才会走索引。

反向建索引

当某字段的值连续增长时,构建标准索引会形成歪脖子树,查询性能不理想。反向建索引,可以让索引的值变得不规则,从而使索引树均匀分布。

1
create index 索引名称 on 表名(列名) reverse;

image-20220309194826259

同义词

1
create [public] SYNONYM 同义词名称 for object

public 是可选的,不加就是私有同义词,只能被当前用户使用。object 可以是表、试图、序列等要创建同义词的对象名称。

为表 T_OWNERS 创建(私有)同义词,名称为 OWNERS,语句:

1
create synonym OWNERS for T_OWNERS;

使用同义词

1
select * from OWNERS;

视图

主要用来简化数据操作。

视图提供了一个简单而有效的安全机制,可以定制不同用户对数据的访问权限。比如不希望一张表的敏感信息被他人查看,可以只赋予视图权限,而且只读。

提供向后兼容性:视图使用户能够在表的架构更改时为表创建向后兼容接口。比如改表字段导致查询无法使用,可以使用视图查询。

1
2
3
4
5
CREATE [OR REPLACE] [FORCE|NOFORCE] VIEW view_name
[(alias[, alias]...)]
AS subquery
[WITH CHECK OPTION [CONSTRAINT constraint]]
[WITH READ ONLY]

alias:为视图产生的列定义的别名;

subquery :一条完整的SELECT语句,可以在该语句中定义别名;

WITH CHECK OPTION :插入或修改的数据行必须满足视图定义的约束;

WITH READ ONLY :默认可以通过视图对基表执行增删改操作,但是有很多在基表上的限制(比如:基表中某列不能为空,但是该列没有出现在视图中,则不能通过视图执行insert操作),WITH READ ONLY说明视图是只读视图,不能通过该视图进行增删改操作。现实开发中,基本上不通过视图对表中的数据进行增删改操作。

键保留表:把主键保留下来的那个表。通过视图查询的数据中,主键来自哪个表,哪个表就是键保留表,只能修改键保留表中的数据,而不能修改其他表的数据。

聚合统计的视图是没有键保留表的,这种视图都是只读的。

物化视图

https://www.modb.pro/db/52367

https://www.youtube.com/watch?v=mo61cAiV0HE&list=PLD3Xyx6ef38yPjePP9e0yfIwX4lGOcb3w&index=45

1
2
3
4
5
6
7
8
CREATE METERIALIZED VIEW view_name
[BUILD IMMEDIATE | BUILD DEFERRED]
REFRESH [FAST|COMPLETE|FORCE]
[
ON [COMMIT | DEMAND] | START WITH (START_TIME) NEXT (NEXT_TIME)
]
AS
subquery

会生成一个表。

参数详解

  • build immediate 在创建物化视图时就生成数据,此为默认值。
  • build deferred 创建时不生成数据。当配合 commit 使用时,第一次必须手动刷新,后面才会自动刷新。

刷新模式:

  • commit 自动刷新,当基表有事务提交时刷新物化视图。
  • demand 手动刷新。

刷新方法:

  • fast 采用增量刷新,只刷新自上次刷新以后进行的修改。前提是视图中用到的表必须都有物化视图日志,且建立的物化视图必须有基表的ROWID或主键字段(可以使用别名),物化视图会根据它确定需要更新的数据行
  • complete 对整个物化视图进行完全刷新
  • force 自动选择。在刷新时会去判断是否可以进行快速刷新,如果可以则采用 FAST 方式,否则采用 COMPLETE 的方式。

刷新时间(可选):

  • start_time 指定起始时间。
  • next_time 下次开始时间,起始到下次的间隔时间即改表的循环时间。

手动刷新

物化视图生成的表数据

1
2
3
begin
DBMS_MVIEW.refresh('VIEW_NAME', 'C'); -- 这是oracle封装的存储过程,有两个参数。
end;

也可以使用命令刷新,打开命令窗执行:

1
EXEC DBMS_MVIEW.refresh('VIEW_NAME', 'C');

物化视图日志

此日志记录了基表发生的变化(增、删、改),物化视图用记录来刷新数据行,刷新完成后会自动删除日志记录,所以若要看到效果可以将物化视图定义为手动刷新(demand),当基表变化是查看日志表,手动刷新后,日志表数据被删除。

1
create materialized view log on 物化视图使用的表名 with rowid(或者主键); -- 根据rowid更新物化视图

会生成一张表,默认物化视图日志的表名格式:MLOG$_表名

备份和恢复

使用imp命令和exp命令对oracle数据库进行导入导出操作

整库导入导出

https://www.youtube.com/watch?v=_zOp8S4uQQ4&list=PLD3Xyx6ef38yPjePP9e0yfIwX4lGOcb3w&index=15

按用户和表导入导出

https://www.youtube.com/watch?v=2BoHiT_ymC4&list=PLD3Xyx6ef38yPjePP9e0yfIwX4lGOcb3w&index=16

问题

在java中运行代码: System.out.println(2.00-1.10);
输出结果:0.8999999999999999
很奇怪,并不是我们想要的值0.9

再运行代码: System.out.println(2.00f-1.10f);
输出结果:0.9
又正确了,为什么会导致这种问题?程序中为什么要尽量避免浮点数比较?

阅读全文 »

推荐链接

Java中的回调

所谓回调

所谓回调,就是A类调用B类中的某个方法C,然后B类反过来调用A类中的某个方法D,这个D方法就叫回调方法。

异步+回调

阅读全文 »

反射的历史由来

反射这一概念最早由编程开发人员Smith在1982年提出,主要指应用程序访问、检测、修改自身状态与行为的能力。这一概念的提出立刻吸引了编程界的极大关注,各种研究工作随之展开,随之而来引发编程革命,出现了多种支持反射机制的面向对象语言。

什么是反射?

Java 反射(Reflection)机制就是在运行状态中,对于 任意 一个类,都能够获取到这个类的所有属性和方法。对于 任意 一个对象,都能够调用它的**任意一个方法和属性(包括私有的方法和属性)。这种动态获取的信息以及动态调用对象的方法的功能**就称为 Java 语言的反射机制。

通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。

Java 程序中对象的类型都是在编译期就确定下来的。但通过 Java 反射机制动态创建的对象,这个对象的类型在编译期是未知的。

反射的核心是 JVM 在 运行时 才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。反射机制就是通过 java.lang.Class 类来实现的,在 Java 中,Object 类是所有类的根类,而 Class 类就是描述 Java 类的类。

Class 本身就是一个类,Class 就是这个类的名称,所以 Object 也包括 Class 类。

反射的主要功能

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用 private 方法);
  • 在运行时调用任意一个对象的方法;
  • 修改构造函数、方法、属性的可见性。

反射的主要用途

反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Action 之类的),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类(运行时动态加载),调用不同的方法,这个时候就必须用到反射。对与框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。

反射的优势

  • 提高灵活性和扩展性,降低耦合性,提高自适应能力
  • 允许程序创建和控制任何类的对象,无需硬编码目标类

反射的使用

反射机制中会用到一些类,在了解反射是如何使用之前,先介绍一下这些类。

说明
Object Java 中所有类的超类
Class 在反射中表示内存中的一个 Java 类,Class 可以代表的实例类型包括,类和接口、基本数据类型、数组
Constructor 封装了类的构造函数的属性信息,包括访问权限和动态调用信息
Field 提供类或接口的成员变量属性信息,包括访问权限和动态修改
Method 提供类或接口的方法属性信息,包括访问权限和动态调用信息
Modifier 封装了修饰属性,public、protected、static、final、synchronized、abstract 等

声明一个接口和一个简单的具体类作为示例代码:

声明一个接口:

1
2
3
public interface IAddress {
void address();
}

具体的类,请关注各个方法的修饰属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Person implements IAddress {
private int age;
public String name;

public Person() {}

public Person(int age, String name) {
this.age = age;
this.name = name;
}

public int getAge() {return age;}

public void setAge(int age) {this.age = age;}

public String getName() {return name;}

public void setName(String name) {this.name = name;}

public Person setData(int age, String name) {
this.age = age;
this.name = name;
return this;
}

@Override
public void address() {System.out.println("I am from China.");}

@Override
public String toString() {return "Person [age=" + age + ", name=" + name + "]";}
}

获取Class对象

想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class 文件对象),通过字节码文件对象,能够获取想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等)。每一个类对应一个字节码文件,也就对应着一个 Class 类型的对象,也就是字节码文件对象。

反射的各种功能都需要通过 Class 对象来实现,因此,需要知道如何获取 Class对象,主要有以下几种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
// 方法1:实例对象.getClass()。创建对象阶段获取
Person person = new Person();
Class<? extends Person> clazz1 = person.getClass();

// 方法2:类名.class。字节码阶段获取
Class<Person> clazz2 = Person.class;

// 方法3:Class.forName("类名字符串") 。源文件阶段获取
Class<?> clazz3 = Class.forName("com.zhaolq.mars.demo.a.Person"); // 此过程由ClassLoader加载

// 在运行期间,一个类只有一个 Class 对象产生。
System.out.println(clazz1.equals(clazz2)); // true
System.out.println(clazz2.equals(clazz3)); // true
System.out.println(clazz1.equals(clazz3)); // true
}
}

判断是否为某个类的实例

一般地,可以使用 instanceof 关键字来判断是否为某个类的实例。同时也可以借助反射中 Class 对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 Native 方法:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
Person person = new Person();
if (person instanceof Person) {
System.out.println("111111");
}
if (Person.class.isInstance(person)) {
System.out.println("222222");
}
}
}

获取父类和接口列表

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = Person.class;
// 获取该类的父类
Class superClazz = clazz.getSuperclass();
System.out.println(superClazz.getName());
// 获取该类实现的接口
Class[] interfaces = clazz.getInterfaces();
for (Class clz : interfaces) {
System.out.println(clz.getName());
}
}
}

创建实例

通过反射来生成对象主要有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) throws Exception {
// 方法1:使用 Class 对象的 newInstance() 方法
Class<?> clazz1 = Person.class;
Object obj1 = clazz1.newInstance(); // java9开始弃用,可替换为 clazz.getDeclaredConstructor().newInstance()
((Person) obj1).setData(16, "Shelly");
System.out.println(obj1);

// 方法2:通过 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance() 方法
Class<?> clazz2 = Person.class;
Constructor<?> constructor = clazz2.getConstructor(int.class, String.class);
Object obj2 = constructor.newInstance(18, "Shirley");
System.out.println(obj2);
}
}

获取方法

获取某个 Class 对象的方法集合,主要有以下几个方法:

  • Method getMethod(String name, Class<?>... parameterTypes): 获取“名称是name,参数是parameterTypes”的public的函数(包括从基类继承的、从接口实现的所有public函数)。
  • Method[] getMethods(): 获取全部的public的函数(包括从基类继承的, 从接口实现的所有public函数)。
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取”名称是name,参数是parameterTypes”,并且是类自身声明的函数,包含public、protected和private方法。
  • Method[] getDeclaredMethods(): 获取全部的类自身声明的函数,包含public、protected和private方法。
  • Method getEnclosingMethod(): 如果这个类是“其它类中某个方法的内部类”,调用getEnclosingMethod()就是这个类所在的方法,若不存在,返回null。没懂…

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Test {
public static void main(String[] args) throws Exception {
Class<?> clazz = Person.class;

System.out.println("指定的public方法:");
Method method = clazz.getMethod("toString");
System.out.println(Modifier.toString(method.getModifiers()) + " " + method.getReturnType() + " " + method.getName());

System.out.println();
System.out.println("全部的public方法:");
Method[] methods = clazz.getMethods();
for (Method method2 : methods) {
System.out.println(Modifier.toString(method2.getModifiers()) + " " + method2.getReturnType() + " " + method2.getName());
}

System.out.println();
System.out.println("指定自身声明的方法,包括private:");
Method declaredMethod = clazz.getDeclaredMethod("address");
System.out.println(Modifier.toString(declaredMethod.getModifiers()) + " " + declaredMethod.getReturnType() + " " + declaredMethod.getName());

System.out.println();
System.out.println("类自身声明的全部的方法,包括private:");
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod2 : declaredMethods) {
System.out.println(Modifier.toString(declaredMethod2.getModifiers()) + " " + declaredMethod2.getReturnType() + " " + declaredMethod2.getName());
}
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
指定的public方法:
public class java.lang.String toString

全部的public方法:
public class java.lang.String toString
public class java.lang.String getName
public void setName
public void address
public int getAge
public void setAge
public class com.zhaolq.mars.demo.a.Person setData
public final native void wait
public final void wait
public final void wait
public boolean equals
public native int hashCode
public final native class java.lang.Class getClass
public final native void notify
public final native void notifyAll

指定自身声明的方法,包括private:
public void address

类自身声明的全部的方法,包括private:
public class java.lang.String toString
public class java.lang.String getName
public void setName
public void address
public int getAge
public void setAge
public class com.zhaolq.mars.demo.a.Person setData

获取构造方法

获取某个 Class 对象的构造方法,主要有以下几个方法:

  • Constructor<T> getConstructor(Class<?>... parameterTypes): 获取 参数是parameterTypes 的public的构造函数。 parameterTypes 参数是一个Class对象数组,这些对象按声明的顺序标识构造函数的形参类型。
  • Constructor<?>[] getConstructors(): 获取全部的public构造函数。
  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取 参数是parameterTypes 的,并且是类自身声明的构造函数,包含public、protected和private。
  • Constructor<?>[] getDeclaredConstructors(): 获取类自身声明的全部的构造函数,包含public、protected和private。
  • Constructor<?> getEnclosingConstructor(): 如果这个类是“其它类的构造函数中的内部类”,调用 getEnclosingConstructor() 就是这个类所在的构造函数,若不存在,返回null。没懂…

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Test {
public static void main(String[] args) throws Exception {
Class<?> cls1 = Test.class;

System.out.println("指定的public构造函数:");
Class[] classArr = {String.class, Integer.class};
Constructor constructor1 = cls1.getConstructor(classArr);
System.out.println(constructor1);

System.out.println();
System.out.println("全部的public构造函数:");
Constructor[] constructor2 = cls1.getConstructors();
for (int i = 0; i < constructor2.length; i++) {
System.out.println(constructor2[i].toGenericString());
}

System.out.println();
System.out.println("指定的自身声明的构造函数,包括private:");
Constructor constructor3 = cls1.getDeclaredConstructor(String.class, Integer.class);
System.out.println(constructor3);

System.out.println();
System.out.println("类自身声明的全部的构造函数,包括private:");
Constructor[] constructor4 = cls1.getDeclaredConstructors();
for (int i = 0; i < constructor4.length; i++) {
System.out.println(constructor4[i].toGenericString());
}

System.out.println();
System.out.println("获取一个内部类的构造函数");
Constructor constructor5 = String.class.getEnclosingConstructor(); // null
System.out.println(constructor5);
}

public Test() {}

public Test(String str, Integer i) {}

protected Test(String str, String i) {}

private Test(String str, Boolean i) {}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
指定的public构造函数:
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

全部的public构造函数:
public com.zhaolq.mars.demo.a.Test()
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

指定的自身声明的构造函数,包括private:
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

类自身声明的全部的构造函数,包括private:
protected com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.String)
private com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Boolean)
public com.zhaolq.mars.demo.a.Test()
public com.zhaolq.mars.demo.a.Test(java.lang.String,java.lang.Integer)

获取一个内部类的构造函数
null

获取成员变量

获取某个 Class 对象的成员变量,主要有以下几个方法:

  • Field getField(String name): 获取 名称是name 的public的成员变量(包括从基类继承的, 从接口实现的所有public成员变量)。
  • Field[] getFields(): 获取全部的public成员变量(包括从基类继承的、从接口实现的所有public成员变量)。
  • Field getDeclaredField(String name): 获取 名称是name,并且是类自身声明的成员变量,包含public、protected和private成员变量。
  • Field[] getDeclaredFields(): 获取全部的类自身声明的成员变量,包含public、protected和private成员变量。

获取其他常用信息

Class 类提供了大量的实例方法来获取该 Class 对象所对应的详细信息,Class 类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考 JDK 文档。

获取类内信息

  • 构造器: Constructor<T> getConstructor(Class<?>... parameterTypes)
  • 包含的方法: Method getMethod(String name, Class<?>... parameterTypes)
  • 包含的属性: Field getField(String name)
  • 包含的Annotation: <A extends Annotation> A getAnnotation(Class<A> annotationClass)
  • 内部类: Class<?>[] getDeclaredClasses()
  • 外部类: Class<?> getDeclaringClass()
  • 所实现的接口: Class<?>[] getInterfaces()
  • 修饰符: int getModifiers()
  • 所在包: Package getPackage()
  • 类名: String getName()
  • 简称: String getSimpleName()

判断类本身信息的方法

  • 是否注解类型: boolean isAnnotation()
  • 是否使用了该Annotation修饰: boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
  • 是否匿名类: boolean isAnonymousClass()
  • 是否数组: boolean isArray()
  • 是否枚举: boolean isEnum()
  • 是否接口: boolean isInterface()
  • obj 是否是该 Class 的实例: boolean isInstance(Object obj)此方法是Java语言 instanceof 运算符的动态等价物
  • public boolean isLocalClass() // 类是不是本地类。本地类,就是定义在方法内部的类。
  • public boolean isMemberClass() // 当且仅当基础类是成员类时才返回true
  • public native boolean isPrimitive() // 类是不是“基本类型”,包括void和8个基本类型
  • public boolean isSynthetic() // 如果此类是合成类,则返回true;否则返回false。

使用反射获取泛型信息

为了通过反射操作泛型以迎合实际开发的需要,Java 新增了 java.lang.reflect.ParameterizedTypejava.lang.reflect.GenericArrayTypejava.lang.reflect.TypeVariablejava.lang.reflect.WildcardType 几种类型来代表不能归一到 Class 类型但是又和原始类型同样重要的类型。

  • ParameterizedType: 一种参数化类型,比如Collection
  • GenericArrayType: 一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable: 各种类型变量的公共接口
  • WildcardType: 一种通配符类型表达式,如?? extends Number? super Integer

其他

1
2
3
4
5
6
7
8
9
10
11
12
// 获取类的"annotationClass"类型的注解 (包括从基类继承的、从接口实现的所有public成员变量)
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
// 获取类的全部注解 (包括从基类继承的、从接口实现的所有public成员变量)
public Annotation[] getAnnotations()
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass)
// 获取类的"annotationClass"类型的,并且是类自身声明的注解,包含public、protected和private成员变量。
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)
// 获取类自身声明的全部注解 (包含public、protected和private成员变量)
public Annotation[] getDeclaredAnnotations()
public <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass)

Map<Class<? extends Annotation>, Annotation> getDeclaredAnnotationMap()

调用方法

当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke() 方法的原型为:

1
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) throws Exception {
Class<?> clazz = Person.class;
// 创建Person实例
Object obj = clazz.getDeclaredConstructor().newInstance();
// 获取Person类的SetData方法
Method method = clazz.getMethod("setDate", int.class, String.class);
// 调用method的对应的方法 => setDate(16, "Shelly")
Object result = method.invoke(obj, 16, "Shelly");
System.out.println(result);
}
}

创建数组

数组在 Java 里是比较特殊的一种类型,它可以赋值给一个 Object Reference。下面我们看一看利用反射创建数组的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) throws Exception {
// 使用`java.lang.reflect.Array`反射创建长度为25的字符串数组.
Class<?> clazz = Class.forName("java.lang.String");
Object array = Array.newInstance(clazz, 10);
// 往数组里添加内容
Array.set(array, 0, "Hello");
Array.set(array, 1, "Java");
Array.set(array, 2, "Kotlin");
Array.set(array, 3, "Android");
// 获取某一项的内容
System.out.println(Array.get(array, 3));
}
}

其中的 Array 类为 java.lang.reflect.Array 类。我们通过 Array.newInstance() 创建数组对象,而 newArray 方法是一个 native 方法,它的原型是:

1
2
3
4
5
6
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
return newArray(componentType, length);
}


private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;

反射的一些注意事项

由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。

另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

小结

反射作用:

  • java反射机制可以获取类名、类的对象、类的属性、方法、继承关系、Annotation注解等信息

优点:

  • 提高Java程序的灵活性和扩展性,降低稠合性,提高自适应能力
  • 允许程序创建和控制任何类的对象,无需硬编码目标类

缺点:

  • 对性能有影响。使用反射是一种解释操作,需要动态访问JVM以满足需求。
  • 一般来说,此类访问操作慢于直接执行java代码。若无必要的情况下,不建议基于反射特性进行编码。

小测

1、 Java是一门支持反射的语言,基于反射为Java提供了丰富的动态性支持,下面关于Java反射的描述,哪些是错误的? (多选)

A、Java反射主要涉及的类如Class, Method, Filed,等,他们都在java.lang.reflet包下

B、通过反射可以动态的实现一个接口,形成一个新的类, 并可以用这个类创建对象, 调用对象方法

C、通过反射, 可以突破Java语言提供的对象成员, 类成员的保护机制, 访问一般方式不能访问的成员

D、Java反射机制提供了字节码修改的技术, 可以动态的修剪一个类

解释

1、Class类在java.lang下

2、这是java的动态代理特性

3、反射可以更改类的访问限制;private、protect等关键字是编译期的定义

4、反射可以新增类,但不能修改本身存在的类的字节码

5、通过反射可以调用私有方法

答案:AD

远程调试

server:

  • jdk1.4+: -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005 (jdk1.5之后,可以不用-Xdebug)
  • jdk1.5+:-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • -Xrunjdwp/-agentlib:jdwp: 使用jdwp来运行调试环境
  • transport: 通信方式,dt_socket使用的是socket,dt_shmem使用的是共享内存,其中dt_shmem只适用于windows
  • server: 是否以作为调试服务端运行jvm
  • suspend: 程序启动后先暂停,等待客户端连接
  • address: 服务端监听的地址
  • onuncaught:当出现uncaught exception 后,是否中断jvm运行

client:

  • jdb -connect com.sun.jdi.SocketAttach:port=5005,hostname=localhost
  • stop, step, next, cont, locals, print, threads… , 可在jdk中通过help查看
  • 也可以通过eclipse,IDEA 远程调试功能连接

IDEA:

  • 打开 Run/Debug Configurations 窗口
  • 点击左上角加号 Add New Configuration
  • 选择 Remote JVM Debug

内存问题

运行时数据区:

  • 堆:新生代、老年代
  • 方法区:
  • 线程私有区:虚拟机栈、本地方法栈、程序计数器

常见问题:

  • “java.lang.OutOfMemoryError:Java heap space”: 堆内存不足。可能为内存泄漏、堆配置过小或配置不合理。可通过-Xms, -Xmx配置
  • java.lang.OutOfMemoryError: PermGen : JDK1.7以前,“space“: 永久代(方法区)空间不足。一般为加载类型过多引起。可通过-XX:PermSize和-XX:MaxPermSize配置,也可以查看是否使用-noclassgc 参数,JDK1.8之后为java.lang.OutOfMemoryError: Metaspace” 。
  • StackOverFlowError: 栈空间不足。一般为递归调用引起。通过-Xss配置
  • java.lang.OutOfMemoryError:可能为直接内存溢出。一般为通过NIO或JNI不断分配内存导致。通过-XX:MaxDirectMemorySize配置

注意:

  • 一般不要使用try-catch捕获OOM。

堆内存溢出:

  • 当出现java.lang.OutOfMemoryError: Java heap space异常,说明当前的堆内存不足,无法创建更多的Java对象

常见原因:

  • 堆内存太小,使用-Xmx 参数增加虚拟机最大堆内存的大小
  • Java代码内存泄漏导致的内存不足,属于代码的bug,mat等工具排查

定位方法:

  • jmap -histo 打印当前对象的个数和大小
  • jmap –histo:live 打印当前存活对象的个数和大小,
  • 此命令会触发一次full gc
  • jstat: -gc 查看gc情况
  • 过jmap -heap:format=b 获取内存信息
  • 在启动时增加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=”具体的路 径”,当系统OutOfMemory之后,会将内存信息收集下来

堆内存泄漏:

  • 已经无用的对象仍然被其它对象引用就造成了内存泄漏

原因:

  • 全局变量(特别是容器类) 引用一个对象, 在不需要的使用没有释放。
  • 虽然正常情况对象进行了释放, 但是在异常情况下, 由于释放代码没有被执行到导致的缓慢内存泄漏
  • runnable类型的对象被new了, 但是没有按照正常的逻辑提交给线程去执行。 runnable这种特殊对象一旦new出来, 会被虚拟机自身所引用, 尽管用户代码中没有显式引用。

jdk图形工具:

  • jconsole: 可以查看堆内存和堆外内存分配情况
  • jvisualvm: 功能较jconsole多
  • mat:下载地址:https://www.eclipse.org/mat/
  • Shallow heap: 对象自身占用的内存,Retained heap:除了对象自身内存外,还包括对象引用其他对象引用的内存
  • 软引用:如果一个对象只有软引用,而当前堆空间不足,则GC会回收
  • 弱引用:不管内存是否足够,都会回收
  • 虚引用:是否有虚引用,都不影响对象的回收。好处是,能在被GC的时候收到通知
  • jmc: 配合jfr,不仅能够进行监控,还能够分析jvm的profiling和事件。其中jfr在jdk8u262和jdk11中可用,jmc下载地址http://jdk.java.net/jmc/

CPU使用率高

定位高CPU使用率的线程:

  • top –H –p PID: 查看java进程各线程CPU的使用情况

查看线程调用栈:

  • jstack PID: 查看高CPU使用率的线程在做什么

线程状态:

  • Blocking: 现在在等待锁(Lock或synchronized)
  • Waiting: 等待其他线程执行某些操作
  • Runnable: 就绪或运行状态
  • Timed_Waiting: 限时等待

其他原因:

  • 线程多,上下文切换次数多. 可通过vmstat查看
  • gc次数过多.可通过gc日志查看

死锁

jstack:

  • 命令:jstack pid
  • 线程占有一个锁:locked
  • 线程等待其它线程释放锁:waiting to lock
  • 线程占有一个锁,同时执行该锁的wait():

先打印 locked,然后打印— waiting on

GC

GC日志:

  • -XX:PrintGCTimeStamps:打印 GC 时间
  • -XX:PrintGCDetails :打印 GC 日志;
  • -Xloggc: path:保存GC 日志路径。
  • jstat –gcutil: 显示垃圾收集信息

常见问题:

  • 频繁gc: 堆空间不足
  • gc时间长:堆空间过大
  • 回收垃圾少:内存泄漏

Linux常用命令

  • vmstat:查看CPU上下文切换、中断次数
  • pidstat:查看进程CPU、内存、I/O指标
  • iostat:查看系统IO情况
  • mpstat:查看每个cpu的性能指标
  • strace: 查看进程的系统调用
  • free:查看系统内存使用情况
  • cachestat:查看整个系统缓存命中情况
  • cachetop: 查看每个进程命中缓存情况
  • sar:查看系统网络收发情况
  • tcpdump:抓取分析网络包

垃圾回收介绍

jvm内存管理

什么是垃圾回收(Garbage Collection)

  • 把不用的内存回收掉
  • java采用自动内存管理技术,内存分配后由虚拟机自动管理

优缺点:

  • 优点:程序员不需要自己释放内存,只管new对象即可
  • 缺点:GC本身有开销,会挤占业务执行资源。

什么是垃圾:

  • 不会被访问到的对象是垃圾

引用计数法

原理

  • 记录每个对象被引用的数量,当被引用的数量为0时,则标记为垃圾
  • 缺点:无法处理循环引用的问题

可达性分析

原理

  • 从GC Roots开始遍历对象,没有被遍历到的对象为垃圾

GC Roots:

  • 方法栈使用到的参数、局部变量、临时变量等
  • 方法区中类静态属性引用的变量
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

垃圾回收算法

清除(sweep)

原理

  • 将垃圾对象所占据的内存标记为空闲内存,然后存在一个空闲列表(free list)中。当需要创建对象时,从空闲列表中寻找空闲内存,分配给新创建的对象

优缺点:

  • 优点:速度快
  • 缺点:容易造成内存碎片,分配效率低

整理(compact)

原理

  • 把存活的对象搬到内存的起始位置,然后在连续的空间内顺序分配

优缺点:

  • 优点:分配速度快,局部性好
  • 缺点:搬运对象麻烦,性能开销大

复制(copy)

原理

  • 将内存分为两个部分,并分别用from和to指针来维护。每次只在from指向的内存中分配内存,当发生垃圾回收时,将from指向区域中存活的对象复制到to指向的内存区域,然后将from指针和to指针互换位置。

优缺点:

  • 优点:同压缩算法,没有内存碎片。分配速度快,局部性好
  • 缺点:可用内存变少,堆空间使用效率低

垃圾收集器

jvm堆划分

jvm将堆划分为新生代和老年代。新生代存放新创建的对象,当对象生存超过一定时间时,会被移动至老年代。新生代采用的 GC 称为minor GC,老年代发生的 GC 称为 full GC 或 major GC,发生full GC会伴随至少一次minor GC

Minor GC

特点:发生次数多,采用时间短,回收掉大量对象

收集器:serial, Parallel Scavenge, Parallel New.均采用复制算法. Serial是单线程,Parallel New可以看成Serial多线程版本. Parallel Scanvenge和Parallel New类似,但更注重吞吐率,且不能与CMS一起使用

Full GC

特点:发生次数少,耗时长

收集器:Serial Old(整理), Parallel Old(整理), CMS(清除). Serial Old是单线程的,Parallel Old可以看成Serial Old的多线程版本. CMS是并发收集器,除了初始标记和重新标记操作需要Stop the world,其它时间可以与应用程序一起并发执行

垃圾回收触发条件

Minor GC

  • Eden区空间不足

Full GC

  • 老年代空间不足
  • 方法区(Metaspace)空间不足
  • 通过minor GC进入老年代的平均大小大于老年代的可用内存
  • 老年代被写满
  • 调用System.GC,系统建议执行full GC,但不一定执行。 《华为Java语言通用编程规范》规则8.7.5:禁止使用主动GC(除非在密码、RMI等方面),尤其是在频繁/周期性的逻辑中

SecurityManager应用场景

当运行未知的Java程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。

Java 安全管理器允许应用程序设置一个安全管理策略,通过安全管理策略实现对应用程序中敏感操作的管理。

安全策略配置文件

默认的安全管理器配置文件

$JAVA_HOME/jre/lib/security/java.policy (Java 8)

$JAVA_HOME/lib/security/default.policy(Java 9+)

当未指定配置文件时,将会使用该配置。

不同版本的 Java 默认的权限配置有所差异,导致部分原来在低版本 Java 上运行的程序,在高版本中可能出现 access denied 的权限异常。

Java 8 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Standard extensions get all permissions by default

grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};

// default permissions granted to all domains

grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See the API specification of java.lang.Thread.stop() for more
// information.
permission java.lang.RuntimePermission "stopThread";

// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "listen";

// "standard" properies that can be read by anyone

permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";

permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";

permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
};

配置文件详解见下文。

启动安全管理器

启动安全管理有两种方式,建议使用启动参数方式。

参数启动方式

-Djava.security.manager

指定配置文件

-Djava.security.manager -Djava.security.policy="E:/java.policy"

编码启动方式

System.setSecurityManager(new SecurityManager());

配置文件简单解释

基本配置原则

在启用安全管理器的时候,配置遵循以下基本原则:

  • 没有配置的权限表示没有。
  • 只能配置有什么权限,不能配置禁止做什么。
  • 同一种权限可多次配置,取并集。
  • 统一资源的多种权限可用逗号分割。

默认配置文件解释

第一部分授权:

1
2
3
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};

授权基于路径在 "file:${{java.ext.dirs}}/*" 的class和jar包,所有权限。

第二部分授权:

1
2
3
4
grant { 
permission java.lang.RuntimePermission "stopThread";
……
}

这是细粒度的授权,对某些资源的操作进行授权。具体不再解释,可以查看javadoc。如 RuntimePermission 的可授权操作经查看javadoc如下:

权限目标名称 权限所允许的操作 允许此权限所带来的风险
createClassLoader 创建类加载器 授予该权限极其危险。能够实例化自己的类加载器的恶意应用程序可能会在系统中装载自己的恶意类。这些新加载的类可能被类加载器置于任意保护域中,从而自动将该域的权限授予这些类。
getClassLoader 类加载器的获取(即调用类的类加载器) 这将授予攻击者得到具体类的加载器的权限。这很危险,由于攻击者能够访问类的类加载器,所以攻击者能够加载其他可用于该类加载器的类。通常攻击者不具备这些类的访问权限。
setContextClassLoader 线程使用的上下文类加载器的设置 在需要查找可能不存在于系统类加载器中的资源时,系统代码和扩展部分会使用上下文类加载器。授予 setContextClassLoader 权限将允许代码改变特定线程(包括系统线程)使用的上下文类加载器。
enableContextClassLoaderOverride 线程上下文类加载器方法的子类实现 在需要查找可能不存在于系统类加载器中的资源时,系统代码和扩展部分会使用上下文类加载器。授予 enableContextClassLoaderOverride 权限将允许线程的子类重写某些方法,这些方法用于得到或设置特定线程的上下文类加载器。
setSecurityManager 设置安全管理器(可能会替换现有的) 安全管理器是允许应用程序实现安全策略的类。授予 setSecurityManager 权限将通过安装一个不同的、可能限制更少的安全管理器,来允许代码改变所用的安全管理器,因此可跳过原有安全管理器所强制执行的某些检查。
createSecurityManager 创建新的安全管理器 授予代码对受保护的、敏感方法的访问权,可能会泄露有关其他类或执行堆栈的信息。
getenv.{variable name} 读取指定环境变量的值 此权限允许代码读取特定环境变量的值或确定它是否存在。如果该变量含有机密数据,则这项授权是很危险的。
exitVM.{exit status} 暂停带有指定退出状态的 Java 虚拟机 此权限允许攻击者通过自动强制暂停虚拟机来发起一次拒绝服务攻击。注意:自动为那些从应用程序类路径加载的全部代码授予 “exitVM.“ 权限,从而使这些应用程序能够自行中止。此外,”exitVM” 权限等于 “exitVM.“。
shutdownHooks 虚拟机关闭钩子 (hook) 的注册与取消 此权限允许攻击者注册一个妨碍虚拟机正常关闭的恶意关闭钩子 (hook)。
setFactory 设置由 ServerSocket 或 Socket 使用的套接字工厂,或 URL 使用的流处理程序工厂 此权限允许代码设置套接字、服务器套接字、流处理程序或 RMI 套接字工厂的实际实现。攻击者可能设置错误的实现,从而破坏数据流。
setIO System.out、System.in 和 System.err 的设置 此权限允许改变标准系统流的值。攻击者可以改变 System.in 来监视和窃取用户输入,或将 System.err 设置为 “null” OutputStream,从而隐藏发送到 System.err 的所有错误信息。
modifyThread 修改线程,例如通过调用线程的 interruptstopsuspendresumesetDaemonsetPrioritysetNamesetUncaughtExceptionHandler 方法 此权限允许攻击者修改系统中任意线程的行为。
stopThread 通过调用线程的 stop 方法停止线程 如果系统已授予代码访问该线程的权限,则此权限允许代码停止系统中的任何线程。此权限会造成一定的危险,因为该代码可能通过中止现有的线程来破坏系统。
modifyThreadGroup 修改线程组,例如通过调用 ThreadGroup 的 destroygetParentresumesetDaemonsetMaxPrioritystopsuspend 方法 此权限允许攻击者创建线程组并设置它们的运行优先级。
getProtectionDomain 获取类的 ProtectionDomain 此权限允许代码获得特定代码源的安全策略信息。虽然获得安全策略信息并不足以危及系统安全,但这确实会给攻击者提供了能够更好地定位攻击目标的其他信息,例如本地文件名称等。
getFileSystemAttributes 获取文件系统属性 此权限允许代码获得文件系统信息(如调用者可用的磁盘使用量或磁盘空间)。这存在潜在危险,因为它泄露了关于系统硬件配置的信息以及一些关于调用者写入文件特权的信息。
readFileDescriptor 读取文件描述符 此权限允许代码读取与文件描述符读取相关的特定文件。如果该文件包含机密数据,则此操作非常危险。
writeFileDescriptor 写入文件描述符 此权限允许代码写入与描述符相关的特定文件。此权限很危险,因为它可能允许恶意代码传播病毒,或者至少也会填满整个磁盘。
loadLibrary.{库名} 动态链接指定的库 允许 applet 具有加载本机代码库的权限是危险的,因为 Java 安全架构并未设计成可以防止恶意行为,并且也无法在本机代码的级别上防止恶意行为。
accessClassInPackage.{包名} 当类加载器调用 SecurityManager 的checkPackageAccess 方法时,通过类加载器的 loadClass 方法访问指定的包 此权限允许代码访问它们通常无法访问的那些包中的类。恶意代码可能利用这些类帮助它们实现破坏系统安全的企图。
defineClassInPackage.{包名} 当类加载器调用 SecurityManager 的 checkPackageDefinition 方法时,通过类加载器的 defineClass 方法定义指定的包中的类。 此权限允许代码在特定包中定义类。这样做很危险,因为具有此权限的恶意代码可能在受信任的包中定义恶意类,比如 java.securityjava.lang
accessDeclaredMembers 访问类的已声明成员 此权限允许代码查询类的公共、受保护、默认(包)访问和私有的字段和/或方法。尽管代码可以访问私有和受保护字段和方法名称,但它不能访问私有/受保护字段数据并且不能调用任何私有方法。此外,恶意代码可能使用该信息来更好地定位攻击目标。而且,它可以调用类中的任意公共方法和/或访问公共字段。如果代码不能用这些方法和字段将对象强制转换为类/接口,那么它通常无法调用这些方法和/或访问该字段,而这可能很危险。
queuePrintJob 打印作业请求的开始 这可能向打印机输出敏感信息,或者只是浪费纸张。
getStackTrace 获取另一个线程的堆栈追踪信息。 此权限允许获取另一个线程的堆栈追踪信息。此操作可能允许执行恶意代码监视线程并发现应用程序中的弱点。
setDefaultUncaughtExceptionHandler 在线程由于未捕获的异常而突然终止时,设置将要使用的默认处理程序 此权限允许攻击者注册恶意的未捕获异常处理程序,可能会妨碍线程的终止
Preferences 表示得到 java.util.prefs.Preferences 的访问权所需的权限。java.util.prefs.Preferences 实现了用户或系统的根,这反过来又允许获取或更新 Preferences 持久内部存储中的操作。 如果运行此代码的用户具有足够的读/写内部存储的 OS 特权,则此权限就允许用户读/写优先级内部存储。实际的内部存储可能位于传统的文件系统目录中或注册表中,这取决于平台 OS。

可配置项详解

当批量配置的时候,有三种模式:

  • directory/ :表示directory目录下的所有.class文件,不包括.jar文件
  • directory/* :表示directory目录下的所有的.class及.jar文件
  • directory/- :表示directory目录下的所有的.class及.jar文件,包括子目录

可以通过${}来引用系统属性,如:

1
"file:${{java.ext.dirs}}/*"

顶层抽象类

java.security.Permission 用来定义类所拥有的权限,具体见其子类。

每个子类具体拥有哪些权限,请查看类注释。

问题解决

当出现关于安全管理的报错的时候,基本有两种方式来解决。

取消安全管理器

一般情况下都是无意启动安全管理器,所以这时候只需要把安全管理器进行关闭,去掉启动参数即可。

增加相应权限

若因为没有权限报错,则报错信息中会有请求的权限和请求什么权限,如下:

1
Exception in thread "main" java.security.AccessControlException: access denied (java.io.FilePermission c:/protect.txt write)

上面例子,请求资源 c:/protect.txt 的 FilePermission 的写权限没有,因此被拒绝。

也可以开放所有权限:

1
2
3
grant { 
permission java.security.AllPermission;
};

1
2
3
grant {
permissionjava.io.FilePermission "c:/protect.txt", "read", "write";
};

推荐:

视频教程:https://www.bilibili.com/video/BV1wh411e7nd

对应代码:https://github.com/hellozhaolq/2021-Java-ThreadPool-Tutorial

对于数据库连接,我们经常听到数据库连接池这个概念。因为建立数据库连接是非常耗时的一个操作,其中涉及到网络IO的一些操作。因此就想出把连接通过一个连接池来管理。需要连接的话,就从连接池里取一个。当使用完了,就“关闭”连接,这不是真正意义上的关闭,只是把连接放回池里,供其他人在使用。所以对于线程,也有了线程池这个概念,其中的原理和数据库连接池差不多。

阅读全文 »
0%