diff --git a/2020/08/19/docker-frequently-used-commands/index.html b/2020/08/19/docker-frequently-used-commands/index.html index 003b9df8..9d9b38f1 100644 --- a/2020/08/19/docker-frequently-used-commands/index.html +++ b/2020/08/19/docker-frequently-used-commands/index.html @@ -27,7 +27,7 @@ - + @@ -237,7 +237,7 @@
在路由器管理后台的 NAT 设置功能里,配置好对外端口号和 Windows 10 主机上 OpenVPN 端口号的映射关系。
iOS 使用 OpenVPN 的 FAQ
如何通过 iOS Keychain 使用客户端证书和密钥
如何配置 iOS OpenVPN 客户端的证书认证
从 OpenVPN 社区 下载 Windows 64-bit MSI installer。本次安装的版本为 OpenVPN 2.6.4。
---注意事项:在选择安装类型时选择 Customize 而不要选择 Install Now。额外勾选 OpenVPN -> OpenVPN Service -> Entire feature will be installed on local hard drive 和 OpenSSL Utilities -> EasyRSA 3 Certificate Management Scripts -> Entire feature will be installed on local hard drive。
-
安装完毕后,会弹出一条消息提示未找到可读的连接配置文件,暂时忽略。
此时在 控制面板\网络和 Internet\网络连接 中可以看到创建了两个新的网络适配器 OpenVPN TAP-Windows6 和 OpenVPN Wintun。
打开 Windows 10 终端程序。
进入 OpenVPN 默认安装目录中的 easy-rsa 目录。
cd 'C:\Program Files\OpenVPN\easy-rsa' |
执行命令进入 Easy-RSA 3 Shell
-.\EasyRSA-Start.bat |
初始化公钥基础设施目录 pki
-./easyrsa init-pki |
构建证书颁发机构(CA)密钥,CA 根证书文件将在后续用于对其他证书和密钥进行签名。该命令要求输入 Common Name,输入主机名即可。创建的 ca.crt 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki
中,ca.key 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\private
中。
./easyrsa build-ca nopass |
构建服务器证书和密钥。创建的 server.crt 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\issued
中,server.key 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\private
中。
./easyrsa build-server-full server nopass |
构建客户端证书和密钥。创建的 client.crt 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\issued
中,client.key 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\private
中。
./easyrsa build-client-full client nopass |
生成 Diffie-Hellman 参数
-./easyrsa gen-dh |
从目录 C:\Program Files\OpenVPN\sample-config
复制服务端配置文件模板 server.ovpn 到目录 C:\Program Files\OpenVPN\config
中,修改以下配置:
port 1194 |
端口号按需修改,默认为1194,需要保证 OpenVPN 的网络流量可以通过防火墙,设置 Windows 10 Defender 允许 OpenVPN 通过即可。dh2048.pem 修改为生成的文件名 dh.pem。取消注释 duplicate-cn,让多个客户端使用同一个客户端证书。注释掉 tls-auth ta.key 0。复制 ca.crt,dh.pem,server.crt 和 server.key 到目录 C:\Program Files\OpenVPN\config 中。
-启动 OpenVPN,点击连接,系统提示分配 IP 10.8.0.1。按配置,每次 OpenVPN server 都将为自己分配 10.8.0.1。
-route add -net 10.8.0.0/24 gw 192.168.46.1 |
这是为了让 OpenVPN 服务端的其他私有子网上的设备知道来自 10.8.0.0/24 的 IP Packet 应该路由回 OpenVPN 服务端。
透过openvpn来访问内网资源
OpenVPN 路由详解
OpenVPN中的另一个路由问题 - 在VPN上无法访问本地计算机
openvpn添加本地路由表
windows开启路由转发
linux route命令的使用详解
Windows命令行route命令使用图解
从 OpenVPN 社区 下载 Windows 64-bit MSI installer。本次安装的版本为 OpenVPN 2.6.4。
+++注意事项:在选择安装类型时选择 Customize 而不要选择 Install Now。额外勾选 OpenVPN -> OpenVPN Service -> Entire feature will be installed on local hard drive 和 OpenSSL Utilities -> EasyRSA 3 Certificate Management Scripts -> Entire feature will be installed on local hard drive。
+
安装完毕后,会弹出一条消息提示未找到可读的连接配置文件,暂时忽略。
此时在 控制面板\网络和 Internet\网络连接 中可以看到创建了两个新的网络适配器 OpenVPN TAP-Windows6 和 OpenVPN Wintun。
打开 Windows 10 终端程序。
进入 OpenVPN 默认安装目录中的 easy-rsa 目录。
cd 'C:\Program Files\OpenVPN\easy-rsa' |
执行命令进入 Easy-RSA 3 Shell
+.\EasyRSA-Start.bat |
初始化公钥基础设施目录 pki
+./easyrsa init-pki |
构建证书颁发机构(CA)密钥,CA 根证书文件将在后续用于对其他证书和密钥进行签名。该命令要求输入 Common Name,输入主机名即可。创建的 ca.crt 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki
中,ca.key 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\private
中。
./easyrsa build-ca nopass |
构建服务器证书和密钥。创建的 server.crt 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\issued
中,server.key 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\private
中。
./easyrsa build-server-full server nopass |
构建客户端证书和密钥。创建的 client.crt 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\issued
中,client.key 保存在目录 C:\Program Files\OpenVPN\easy-rsa\pki\private
中。
./easyrsa build-client-full client nopass |
生成 Diffie-Hellman 参数
+./easyrsa gen-dh |
从目录 C:\Program Files\OpenVPN\sample-config
复制服务端配置文件模板 server.ovpn 到目录 C:\Program Files\OpenVPN\config
中,修改以下配置:
port 1194 |
端口号按需修改,默认为1194,需要保证 OpenVPN 的网络流量可以通过防火墙,设置 Windows 10 Defender 允许 OpenVPN 通过即可。dh2048.pem 修改为生成的文件名 dh.pem。取消注释 duplicate-cn,让多个客户端使用同一个客户端证书。注释掉 tls-auth ta.key 0。复制 ca.crt,dh.pem,server.crt 和 server.key 到目录 C:\Program Files\OpenVPN\config 中。
+启动 OpenVPN,点击连接,系统提示分配 IP 10.8.0.1。按配置,每次 OpenVPN server 都将为自己分配 10.8.0.1。
+String s1 = new String("abc");
这个语句创建了几个字符串对象”。这个问题曾经困扰我,当时的我不能理解这个问题想要考察的是什么?new String("abc")
。
-字面量(literal)是用于表达源代码中的一个固定值的表示法(notion),比如代码中的整数、浮点数、字符串。简而言之,字符串字面量就是双引号包裹的字符串,例如:
-String s1 = "a"; |
在 Java 中,字符串对象就是一个 String 类型的对象,因此在程序运行时,String 类型的变量 s1 指向的一定是一个 String 对象。字面量 “a” 在某一个时刻,没有经过 new 关键字,变成了一个 String 对象。
-接下来我们来思考一个问题,程序中每一个字符串字面量都要对应着生成一个单独的 String 对象吗?考虑到 Java 中 String 对象是不可变的,显然相同的字符串字面量完全可以共用一个 String 对象从而避免重复创建对象。JVM 也是这样设计的,这些可以共用的 String 对象组成了一个字符串常量池。
-ps: 以上的“遇到某一个字符串字面量”就是很纯粹地指代程序的源代码中出现用双引号括起来的字符串字面量。
-因此,如果字符串常量池中没有值为 “abc” 的 String 对象,new String("abc")
语句将涉及两个 String 对象的创建,第一个是因为括号里的 “abc” 而在字符串常量池中生成的,第二个才是 new 关键字在堆中创建的;否则只会涉及一个 String 对象的创建。
为什么上面改用如果字符串常量池中没有值为 “abc” 的 String 对象呢?这是因为,字符串常量池里保留的 String 对象有两种产生来源:
java.lang.String#intern
主动地尝试将字符串对象放入字符串常量池。public class StringTableTest_1 { |
使用 javap -v .\StringTableTest_1.class
进行反编译,摘取重要部分:
Constant pool: |
在《深入理解Java虚拟机》提到:字符串常量池的位置从 JDK 7 开始,从永久代中移到了堆中。在这句话中,字符串常量池像是一个特定的内存区域,存储了 interned string 的实例。
- - -书中使用了以下方式来验证字符串常量池的位置。
-public class StringTableTest_8 { |
在 JDK 8 中异常如下:
-Exception in thread "main" java.lang.OutOfMemoryError: Java heap space |
在 JDK 6 中异常如下:
-java.lang.OutOfMemoryError: PermGen space |
同时书中也提到了,在字符串常量池的位置改变后,它只用保存第一次出现时字符串对象的引用。JDK 8 中的 intern 方法可以印证该说法,方法注释中提到:如果字符串常量池中已存在相等(equals)的字符串,那就返回已存在的对象(这样原先准备加入的对象就可以释放);否则,将字符串对象加入字符串常量池中,直接返回对该对象的引用(不用像 JDK 6 时,复制一个对象加入常量池,返回该复制对象的引用)。
-public class StringTableTest_5 { |
实验结果证实了上述说法。
-但是 xinxi 提及:字符串常量池,也称为 StringTable,本质上是一个惰性维护的哈希表,是一个纯运行时的结构,只存储对 java.lang.String
实例的引用,而不存储 String 对象的内容。当我们提到一个字符串进入字符串常量池其实是说在这个 StringTable 中保存了对它的引用,反之,如果说没有在其中就是说 StringTable 中没有对它的引用。
zyplanke 分析 StringTable 在内存中的形式时,也表达了类似的观点。
尽管这个疑问似乎不妨碍我们理解很多东西,但是深究之后,真的让人困惑,网上也没有搜集到更多的信息。字符串常量池和 StringTable 是否等价?字符串常量池更准确的说法是否是“一个保存引用的 StringTable 加上分布在堆(JDK 6 以前的永久代)中的字符串实例”?
已经好几次打开 jvm 的源码,却看不懂它到底什么意思啊!!!!!难道是时候开始学 C++ 了吗。
前面提到了第一次遇到的字符串字面量会在某一个时刻,生成对应的字符串对象进入字符串常量池,同时也提到了,字符串常量池(StringTable)的维护是懒惰的,那么这些究竟是什么时候发生的呢?
-public class StringTableTest_12 { |
0: new #2 // class java/lang/String |
RednaxelaFX 的文章提到:
---在类加载阶段,JVM 会在堆中创建对应这些 class 文件常量池中的字符串对象实例,并在字符串常量池中驻留其引用。具体在 resolve 阶段执行。这些常量全局共享。
-
xinxi 的文章中补充到:
---这里说的比较笼统,没错,是 resolve 阶段,但是并不是大家想的那样,立即就创建对象并且在字符串常量池中驻留了引用。 JVM规范里明确指定resolve阶段可以是lazy的。
-
……
就 HotSpot VM 的实现来说,加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池(即在 StringTable 中并没有相应的引用,在堆中也没有对应的对象产生)。
《深入理解Java虚拟机》中提到:
---《Java虚拟机规范》之中并未规定解析阶段发生的具体时间,只要求了在执行ane-warray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invoke-special、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield和putstatic这17个用于操作符号引用的字节码指令之前,先对它们所使用的符号引用进行解析。所以虚拟机实现可以根据需要来自行判断,到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析它。
-
综上可知,字符串字面量的解析是属于类加载的解析阶段,但是《Java虚拟机规范》并未规定解析发生的具体时间,只要求在执行一些字节码指令前进行,其中包括了 ldc 指令。虚拟机的具体实现,比如 Hotspot 就在执行 ldc #indexNumber
前触发解析,根据字符串常量池中是否已存在字符串对象决定是否创建对象,并将对象推送到栈顶。
这也证实了前文中提到的字符串字面量生成字符串对象和 new 关键字无关。
使用 IDEA memory 功能,观察字符串对象的个数逐个变化。
-public class StringTableTest_4 { |
前文提到字符串常量池在 JDK 7 开始移到堆中,是因为考虑在方法区中的垃圾回收是比较困难的,同时随着字节码技术的发展,CGLib 等会大量动态生成类的技术的运用使得方法区的内存紧张,将字符串常量池移到堆中,可以有效提高其垃圾回收效率。
-public class StringTableTest_9 { |
[GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->856K(9728K), 0.0007745 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] |
当 size 过小,哈希碰撞增加,链表变长,效率会变低,需要增大 buckets size。
-public class StringTableTest_10 { |
当你需要大量缓存重复的字符串时,使用 intern 可以大大减少内存占用。
-public class StringTableTest_11 { |
使用 VisualVM 观察字符串和 char[] 内存占用情况,可以发现提升显著。
- - -字符串变量的拼接,底层是使用 StringBuilder 实现:new StringBuilder().append("a").append("b").toString()
,而 toString 方法使用拼接得到的 char 数组创建一个新的 String 对象,因此 s3 和 s4 是不相同的两个对象。
public class StringTableTest_2 { |
0: ldc #2 // String a |
字符串常量的拼接是在编译期间,因为已知结果而被优化为一个字符串常量。又因为 “ab” 字符串在 StringTable 中是已存在的,所以不会重新创建新对象。
-public class StringTableTest_3 { |
0: ldc #2 // String a |
String s1 = new String("abc");
这个语句创建了几个字符串对象”。这个问题曾经困扰我,当时的我不能理解这个问题想要考察的是什么?new String("abc")
。
+字面量(literal)是用于表达源代码中的一个固定值的表示法(notion),比如代码中的整数、浮点数、字符串。简而言之,字符串字面量就是双引号包裹的字符串,例如:
+String s1 = "a"; |
在 Java 中,字符串对象就是一个 String 类型的对象,因此在程序运行时,String 类型的变量 s1 指向的一定是一个 String 对象。字面量 “a” 在某一个时刻,没有经过 new 关键字,变成了一个 String 对象。
+接下来我们来思考一个问题,程序中每一个字符串字面量都要对应着生成一个单独的 String 对象吗?考虑到 Java 中 String 对象是不可变的,显然相同的字符串字面量完全可以共用一个 String 对象从而避免重复创建对象。JVM 也是这样设计的,这些可以共用的 String 对象组成了一个字符串常量池。
+ps: 以上的“遇到某一个字符串字面量”就是很纯粹地指代程序的源代码中出现用双引号括起来的字符串字面量。
+因此,如果字符串常量池中没有值为 “abc” 的 String 对象,new String("abc")
语句将涉及两个 String 对象的创建,第一个是因为括号里的 “abc” 而在字符串常量池中生成的,第二个才是 new 关键字在堆中创建的;否则只会涉及一个 String 对象的创建。
为什么上面改用如果字符串常量池中没有值为 “abc” 的 String 对象呢?这是因为,字符串常量池里保留的 String 对象有两种产生来源:
java.lang.String#intern
主动地尝试将字符串对象放入字符串常量池。public class StringTableTest_1 { |
使用 javap -v .\StringTableTest_1.class
进行反编译,摘取重要部分:
Constant pool: |
在《深入理解Java虚拟机》提到:字符串常量池的位置从 JDK 7 开始,从永久代中移到了堆中。在这句话中,字符串常量池像是一个特定的内存区域,存储了 interned string 的实例。
+ + +书中使用了以下方式来验证字符串常量池的位置。
+public class StringTableTest_8 { |
在 JDK 8 中异常如下:
+Exception in thread "main" java.lang.OutOfMemoryError: Java heap space |
在 JDK 6 中异常如下:
+java.lang.OutOfMemoryError: PermGen space |
同时书中也提到了,在字符串常量池的位置改变后,它只用保存第一次出现时字符串对象的引用。JDK 8 中的 intern 方法可以印证该说法,方法注释中提到:如果字符串常量池中已存在相等(equals)的字符串,那就返回已存在的对象(这样原先准备加入的对象就可以释放);否则,将字符串对象加入字符串常量池中,直接返回对该对象的引用(不用像 JDK 6 时,复制一个对象加入常量池,返回该复制对象的引用)。
+public class StringTableTest_5 { |
实验结果证实了上述说法。
+但是 xinxi 提及:字符串常量池,也称为 StringTable,本质上是一个惰性维护的哈希表,是一个纯运行时的结构,只存储对 java.lang.String
实例的引用,而不存储 String 对象的内容。当我们提到一个字符串进入字符串常量池其实是说在这个 StringTable 中保存了对它的引用,反之,如果说没有在其中就是说 StringTable 中没有对它的引用。
zyplanke 分析 StringTable 在内存中的形式时,也表达了类似的观点。
尽管这个疑问似乎不妨碍我们理解很多东西,但是深究之后,真的让人困惑,网上也没有搜集到更多的信息。字符串常量池和 StringTable 是否等价?字符串常量池更准确的说法是否是“一个保存引用的 StringTable 加上分布在堆(JDK 6 以前的永久代)中的字符串实例”?
已经好几次打开 jvm 的源码,却看不懂它到底什么意思啊!!!!!难道是时候开始学 C++ 了吗。
前面提到了第一次遇到的字符串字面量会在某一个时刻,生成对应的字符串对象进入字符串常量池,同时也提到了,字符串常量池(StringTable)的维护是懒惰的,那么这些究竟是什么时候发生的呢?
+public class StringTableTest_12 { |
0: new #2 // class java/lang/String |
RednaxelaFX 的文章提到:
+++在类加载阶段,JVM 会在堆中创建对应这些 class 文件常量池中的字符串对象实例,并在字符串常量池中驻留其引用。具体在 resolve 阶段执行。这些常量全局共享。
+
xinxi 的文章中补充到:
+++这里说的比较笼统,没错,是 resolve 阶段,但是并不是大家想的那样,立即就创建对象并且在字符串常量池中驻留了引用。 JVM规范里明确指定resolve阶段可以是lazy的。
+
……
就 HotSpot VM 的实现来说,加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池(即在 StringTable 中并没有相应的引用,在堆中也没有对应的对象产生)。
《深入理解Java虚拟机》中提到:
+++《Java虚拟机规范》之中并未规定解析阶段发生的具体时间,只要求了在执行ane-warray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invoke-special、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield和putstatic这17个用于操作符号引用的字节码指令之前,先对它们所使用的符号引用进行解析。所以虚拟机实现可以根据需要来自行判断,到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析它。
+
综上可知,字符串字面量的解析是属于类加载的解析阶段,但是《Java虚拟机规范》并未规定解析发生的具体时间,只要求在执行一些字节码指令前进行,其中包括了 ldc 指令。虚拟机的具体实现,比如 Hotspot 就在执行 ldc #indexNumber
前触发解析,根据字符串常量池中是否已存在字符串对象决定是否创建对象,并将对象推送到栈顶。
这也证实了前文中提到的字符串字面量生成字符串对象和 new 关键字无关。
使用 IDEA memory 功能,观察字符串对象的个数逐个变化。
+public class StringTableTest_4 { |
前文提到字符串常量池在 JDK 7 开始移到堆中,是因为考虑在方法区中的垃圾回收是比较困难的,同时随着字节码技术的发展,CGLib 等会大量动态生成类的技术的运用使得方法区的内存紧张,将字符串常量池移到堆中,可以有效提高其垃圾回收效率。
+public class StringTableTest_9 { |
[GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->856K(9728K), 0.0007745 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] |
当 size 过小,哈希碰撞增加,链表变长,效率会变低,需要增大 buckets size。
+public class StringTableTest_10 { |
当你需要大量缓存重复的字符串时,使用 intern 可以大大减少内存占用。
+public class StringTableTest_11 { |
使用 VisualVM 观察字符串和 char[] 内存占用情况,可以发现提升显著。
+ + +字符串变量的拼接,底层是使用 StringBuilder 实现:new StringBuilder().append("a").append("b").toString()
,而 toString 方法使用拼接得到的 char 数组创建一个新的 String 对象,因此 s3 和 s4 是不相同的两个对象。
public class StringTableTest_2 { |
0: ldc #2 // String a |
字符串常量的拼接是在编译期间,因为已知结果而被优化为一个字符串常量。又因为 “ab” 字符串在 StringTable 中是已存在的,所以不会重新创建新对象。
+public class StringTableTest_3 { |
0: ldc #2 // String a |
public class ByteCodeTest_2 { |
在编译期间就可计算得到操作数栈和本地变量表的大小。
+stack=2, locals=4, args_size=1 |
Slot,即槽位,可理解为索引。
+Start Length Slot Name Signature |
3 = Integer 32768 |
0: bipush 10 |
public class ByteCodeTest_3 { |
0: bipush 10 |
public class ByteCodeTest_4 { |
0: iconst_0 |
<cond>
,一个 int 和 0 的比较成立时进入分支,跳转到指定行号。<cond>
,一个 int 和 0 的比较成立时进入分支,跳转到指定行号。public class ByteCodeTest_2 { |
在编译期间就可计算得到操作数栈和本地变量表的大小。
-stack=2, locals=4, args_size=1 |
Slot,即槽位,可理解为索引。
-Start Length Slot Name Signature |
3 = Integer 32768 |
0: bipush 10 |
public class ByteCodeTest_3 { |
0: bipush 10 |
public class ByteCodeTest_4 { |
0: iconst_0 |
<cond>
,一个 int 和 0 的比较成立时进入分支,跳转到指定行号。<cond>
,一个 int 和 0 的比较成立时进入分支,跳转到指定行号。