洋蔥

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

从软件上 V2Ray 不区分服务器版和客户端版,也就是说在服务器和客户端运行的 V2Ray 是同一个软件,区别只是配置文件的不同。

部署

应用场景

问题:外网无法访问部署在内网的 MySQL,导致 SpringBoot 项目启动时连不上数据库,接口无法调试,开发效率低。

解决:参考以下方式部署 v2ray,然后配置 JVM 参数 -DsocksProxyHost=127.0.0.1 -DsocksProxyPort=18998

部署方案一

在服务器部署 V2Ray 客户端。配置文件如下(更多配置见官网 v2fly):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"inbounds": [
{
"listen": "0.0.0.0", // 服务器监听IP
"port": 8997, // 服务器监听端口
"protocol": "socks", // 协议名称
"settings": { // 详见每个协议中的InboundConfigurationObject
"auth": "noauth", // "noauth" | "password"。password模式下IDEA代理MySQL连接,提示socks验证失败
"accounts": [ // 支持多个用户账号。此选项仅当auth为password时有效。
{
"user": "zhaolq",
"pass": "123456789"
}
],
"udp": true // 是否开启UDP协议的支持
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}

执行 chmod ./v2ray +x./v2ray run 启动 V2Ray 客户端,可以使用 SwitchyOmega 插件测试 socks 代理是否生效。

注意:

Navicat 不支持 socks 代理,但可以使用 HTTP 隧道。Nginx 配置见 Nginx教程 章节的 常用配置。具体参考 https://blog.csdn.net/baikunlong/article/details/132596536

部署方案二(推荐加密协议)

服务端

在服务器部署 V2Ray 服务端,本地部署客户端。

1
2
cd /opt/local/
unzip v2ray-linux-64.zip -d ./v2ray-linux-64

服务端配置(更多配置见官网 v2fly):

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
{
// 请提前创建log目录。请使用绝对路径。
"log" : {
"access": "/opt/local/v2ray-linux-64/log/access.log",
"error": "/opt/local/v2ray-linux-64/log/error.log",
"loglevel": "warning"
},
"inbounds": [
{
"port": 8998, // 服务器监听端口
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "755bc63b-e26d-47cd-85a1-b6ebbecbbabc"
}
]
}
}
],
"outbounds": [
{
"protocol": "freedom"
}
]
}

Linux 启动前需授予可执行权限 chmod ./v2ray +x,然后参考下文 开机自启

客户端

客户端配置(更多配置见官网 v2fly):

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
48
49
50
51
52
53
54
55
56
{
/*
// 请提前创建log目录。请使用绝对路径。
"log" : {
"access": "X:\Program\v2ray-windows-64\log\access.log",
"error": "X:\Program\v2ray-windows-64\log\error.log",
"loglevel": "warning"
},
// 配置Routing路由。不建议配置,因为这样才能打印访问日志。
"routing": {
"domainStrategy": "IPOnDemand",
"rules": [
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "direct" // 当一个规则生效时,即将这个连接转发至它所指定的 outboundTag。当没有匹配到任何规则时,流量默认被转发至第一个 outbound。
}
]
},
*/
"inbounds": [
{
"port": 18998, // SOCKS 代理端口,在浏览器中需配置代理并指向这个端口
"listen": "127.0.0.1",
"protocol": "socks",
"settings": {
"udp": true
}
}
],
// 一个数组,每个元素是一个出站连接配置。列表中的第一个元素作为主出站协议。当路由匹配不存在或没有匹配成功时,流量由主出站协议发出。
"outbounds": [
{
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "192.168.0.7", // 服务器地址,请修改为你自己的服务器 ip 或域名
"port": 8998, // 服务器端口
"users": [
{
"id": "755bc63b-e26d-47cd-85a1-b6ebbecbbabc"
}
]
}
]
}
},
{
"protocol": "freedom",
"tag": "direct" // 出站连接的标识,用于在其它的配置中定位此连接。当其值不为空时,必须在所有 tag 中唯一。配置Routing路由时可以指定使用哪个出站连接
}
]
}

windows启动脚本

1
2
3
:: 新建文件 v2ray-run.cmd
v2ray.exe run
pause

开机自启

v2ray-linux-64.zip 解压文件中包含 systemd/system/v2ray.service,需要的选项包括 User、ExecStart 等。

vim /etc/systemd/system/v2ray.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# v2ray.service
[Unit]
Description=V2Ray Service
Documentation=https://www.v2fly.org/
After=network.target nss-lookup.target

[Service]
User=root
Group=root
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/opt/local/v2ray-linux-64/v2ray run -config /opt/local/v2ray-linux-64/config.json
Restart=on-failure
RestartPreventExitStatus=23

[Install]
WantedBy=multi-user.target

重新加载配置文件:systemctl daemon-reload

If you want to keep a secret, you must also hide it from yourself.

Project V,一个工具集合,官网: https://www.v2ray.com/ ,它可以帮助你打造专属的基础通信网络。Project V 的核心工具称为V2Ray,其主要负责网络协议和功能的实现,与其它 Project V 通信。V2Ray 可以单独运行,也可以和其它工具配合,以提供简便的操作流程。

Project V 工具比较复杂,本文仅针对 Shadowsocks

官网:https://shadowsocks.org/

阅读全文 »

markdown转html后没有缩进问题:

1
2
3
  半角空格
  全角空格
  不断行的空格

注意注意:H5不=HTML5

  H5 是一个产品名词

  HTML5是一个技术名词

阅读全文 »

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:抓取分析网络包
0%