王某某的笔记

记录我的编程之路

sun.misc.Unsafe 简介

sun.misc.Unsafe 是 Java 中的一个类,位于 sun.misc 包下。它提供了一组低级别的、不安全的操作,允许开发人员绕过 Java 的安全机制和内存管理,直接操作内存、对象、线程等。这使得它在某些高性能、低级别的编程中非常有用,但也非常危险。

主要功能

1. 直接内存访问:

  • 可以分配、释放和访问本机内存,类似于 C/C++ 中的指针操作。
    1
    2
    3
    4
    long memoryAddress = unsafe.allocateMemory(size);
    unsafe.putInt(memoryAddress, 42);
    int value = unsafe.getInt(memoryAddress);
    unsafe.freeMemory(memoryAddress);

2. 对象操作:

  • 可以在不调用构造函数的情况下创建对象。
    1
    2
    SomeClass instance = (SomeClass) unsafe.allocateInstance(SomeClass.class);

3. 字段操作:

  • 可以直接修改对象的字段值,绕过 Java 的访问控制。
    1
    2
    3
    4
    Field field = SomeClass.class.getDeclaredField("someField");
    long offset = unsafe.objectFieldOffset(field);
    unsafe.putInt(instance, offset, 42);

4. CAS 操作:

  • 提供了 compare-and-swap(比较并交换)操作,用于实现无锁编程。
    1
    2
    boolean success = unsafe.compareAndSwapInt(someObject, offset, expectedValue, newValue);

5. 内存屏障:

  • 提供了内存屏障操作,确保指令顺序不会因为 CPU 优化而打乱。
    1
    2
    unsafe.fullFence();

使用注意事项

  • 不安全性:由于它可以绕过 Java 的安全机制,错误使用可能导致程序崩溃、内存泄漏、数据损坏等问题。
  • 可移植性差:sun.misc.Unsafe 是 JDK 内部实现的一部分,不属于标准 Java API,因此不同的 JVM 实现中可能不兼容。
  • 受限访问:在 Java 9 及之后的版本中,直接访问 sun.misc.Unsafe 类变得更加困难,建议通过反射或者使用替代的 API(如 VarHandle)。

示例

以下是一个简单示例,展示如何使用 sun.misc.Unsafe 来分配和访问本机内存:

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
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeExample {
private static final Unsafe unsafe;

static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static void main(String[] args) {
long size = 4; // 4 bytes
long memoryAddress = unsafe.allocateMemory(size);

try {
unsafe.putInt(memoryAddress, 42);
int value = unsafe.getInt(memoryAddress);
System.out.println("Value: " + value); // Output: Value: 42
} finally {
unsafe.freeMemory(memoryAddress);
}
}
}


使用 sun.misc.Unsafe 是危险的,因为它绕过了 Java 的类型安全检查和内存管理机制。如果使用不当,可能会导致严重的错误,如内存泄漏、数据损坏或程序崩溃。并且,由于它不是 Java 标准 API 的一部分,在不同的 Java 版本和运行环境中可能会有不同的表现,甚至可能不被支持。



http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

//重新分配内存
public native long reallocateMemory(long address, long bytes);

//分配内存
public native long allocateMemory(long bytes);

//释放内存
public native void freeMemory(long address);

//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);

//从一个内存块拷贝到另一个内存块
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);

//获取值,不管java的访问限制,其他有类似的getInt,getDouble,getLong,getChar等等
public native Object getObject(Object o, long offset);

//设置值,不管java的访问限制,其他有类似的putInt,putDouble,putLong,putChar等等
public native void putObject(Object o, long offset);

//从一个给定的内存地址获取本地指针,如果不是allocateMemory方法的,结果将不确定
public native long getAddress(long address);

//存储一个本地指针到一个给定的内存地址,如果地址不是allocateMemory方法的,结果将不确定
public native void putAddress(long address, long x);

//该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的
public native long staticFieldOffset(Field f);

//报告一个给定的字段的位置,不管这个字段是private,public还是保护类型,和staticFieldBase结合使用
public native long objectFieldOffset(Field f);

//获取一个给定字段的位置
public native Object staticFieldBase(Field f);

//确保给定class被初始化,这往往需要结合基类的静态域(field)
public native void ensureClassInitialized(Class c);

//可以获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class arrayClass);

//可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用, 可以定位数组中每个元素在内存中的位置
public native int arrayIndexScale(Class arrayClass);

//获取本机内存的页数,这个值永远都是2的幂次方
public native int pageSize();

//告诉虚拟机定义了一个没有安全检查的类,默认情况下这个类加载器和保护域来着调用者类
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);

//定义一个类,但是不让它知道类加载器和系统字典
public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);

//锁定对象,必须是没有被锁的
public native void monitorEnter(Object o);

//解锁对象
public native void monitorExit(Object o);

//试图锁定对象,返回true或false是否锁定成功,如果锁定,必须用monitorExit解锁
public native boolean tryMonitorEnter(Object o);

//引发异常,没有通知
public native void throwException(Throwable ee);

//CAS,如果对象偏移量上的值=期待值,更新为x,返回true.否则false.类似的有compareAndSwapInt,compareAndSwapLong,compareAndSwapBoolean,compareAndSwapChar等等。
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);

// 该方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。类似的方法有getIntVolatile,getBooleanVolatile等等
public native Object getObjectVolatile(Object o, long offset);

//线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。
public native void park(boolean isAbsolute, long time);

//终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,也正是使用这两个方法
public native void unpark(Object thread);

//获取系统在不同时间系统的负载情况
public native int getLoadAverage(double[] loadavg, int nelems);

//创建一个类的实例,不需要调用它的构造函数、初使化代码、各种JVM安全检查以及其它的一些底层的东西。即使构造函数是私有,我们也可以通过这个方法创建它的实例,对于单例模式,简直是噩梦。
public native Object allocateInstance(Class cls) throws InstantiationException;

Fastdfs 问题分析记录

问题

生产上存在上传文件到fastdfs中时一直卡死的问题

大概半个月出现一次,重启就能恢复
日志中没有任何错误

分析

线程堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"taskExecutor-5" #638 prio=5 os_prio=0 tid=0x00007f970800b000 nid=0x1be3 waiting on condition [0x00007f9632008000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000e134e670> (a java.util.concurrent.CompletableFuture$Signaller)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1693)
at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3323)
at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1729)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at com.appleframework.file.provider.fdfs.FdfsProvider.upload(FdfsProvider.java:61)
at com.appleframework.file.spring.AbstractFSProviderSpringFacade.upload(AbstractFSProviderSpringFacade.java:70)
at com.chedaia.biz.renew.provider.service.impl.CustomerShoppingServiceImpl$1.run(CustomerShoppingServiceImpl.java:956)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

只知道是在等一个CompletableFuture异步计算的返回结果,可能是再等待获取存储服务器地址,也可能是再等待上传结果

检查代码并进行测试,没有发现问题
猜测可能是获取不到连接导致的,连接没有归还到连接池中等,也可能是fastdfs一直没有响应,或者网络出现了什么问题等。

直到生产上再次出现该问题
将生产上的程序堆栈dump出来查看对比之前看的代码,发现是获取的连接数达到了最大值

1
jmap -dump:format=b,file=文件名 [pid]

图片1.png

而连接池中并没连接

图片2.png

OQL

1
select x.deque from io.netty.channel.pool.FixedChannelPool x

总结问题:

FixedChannelPool 类的 acquiredChannelCount 字段
一直增加到了最大的限制(100)导致的
获取到的连接一直没有释放

检查原因:

查代码,并且重点测试这个变量,一直无法重现该问题(怀疑人生_

导出测试程序的dump文件和线上的进行比较
对比了一下测试版本和线上版本的dump文件,发现类不一致

图片3.png

线上版本没有这个变量

图片4.png

检查发现 调试版本 和 线上的版本 不一致

  • 调试版本:netty-all-4.0.34.Final.jar

  • 线上版本:netty-all-4.0.30.Final.jar

再次查看netty-all-4.0.30的代码,发现netty的连接池存在问题

问题重现

检查代码发现连接归还到连接池中再次获取时存在问题
当连接用完之后,归还到连接池中,过了一段时间之后连接变成了 TIME_WAIT 状态(或断开),再去获取连接(此时将获取到一个异常的连接)

图片5.png

获取到连接之后会对连接的有效性进行检查

图片6.png

检查到连接异常之后会关掉这个连接并再次获取连接

图片7.png

此时会再调用FixedChannelPool类的acquire0方法获取连接,这将导致acquiredChannelCount 变量再次 +1,这次获取连接将会+2,而在释放时只会-1,这将导致这个值一直增大,直到达到maxConnections 的限制,之后就再也获取不到连接了

图片8.png

释放连接时只会-1

图片9.png

修复方案

升级netty的包到更高的版本

netty-all-4.0.30.Final.jar 包存在这个问题

netty-all-4.0.34.Final.jar 已经修复了这个问题

PS:
这个4.0.34版本还是可能会有问题
private int acquiredChannelCount;
这个变量是int类型,存在并发问题,可能多个线程同时修改这个变量,应该用AtomicInteger,但是以目前系统的并发量来看,完全可以忽略。

想要加密java程序是比较困难的,一般的做法就是代码混淆,增加代码阅读的难度,另外就是修改Class文件的字节码,然后再虚拟机加载类的时候进行解密。修改字节码的解密方法一般是修改JDK中的ClassLoad或者自定义一个ClassLoad来解密Class。有些为了不让别人看到解密算法有些还会用C来写解密程序,再用jni来调用。

一般用改ClassLoader来加密的这种方法都可以用 javaagent 获取到解密后class。

使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
介绍地址:
https://www.ibm.com/developerworks/cn/java/j-lo-jse61/

输出Class文件到指定目录的代码

1、新建一个maven项目

如:class-agent

2、写两个类

PreMainExecutor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.wwh.agent;

import java.lang.instrument.Instrumentation;

public class PreMainExecutor {

public static void premain(String agentOps, Instrumentation inst) {
System.out.println("premain execute..........");
System.out.println("参数:" + agentOps);
// 添加Transformer
inst.addTransformer(new PrintClassFileAgent(agentOps));

// 可以用这个来加载jar包
// inst.appendToSystemClassLoaderSearch(jarfile);
}
}

PrintClassFileAgent.java

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
package com.wwh.agent;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class PrintClassFileAgent implements ClassFileTransformer {

public static final String OUT_FILE_DIR = "/opt/logs/wwh/classFile/";

private File outFileDir;

public PrintClassFileAgent(){

}

public PrintClassFileAgent(String fileDir){
String fileOutDir = OUT_FILE_DIR;
if (fileDir != null && !"".equals(fileDir)) {
fileOutDir = fileDir;
}
outFileDir = new File(fileOutDir);
outFileDir.mkdirs();

}

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("类加载器:" + loader);
System.out.println("类名称:" + className);

String pathName = className.replaceAll("[.]", "/");
pathName = pathName + ".class";

File f = new File(OUT_FILE_DIR + pathName);

f.getParentFile().mkdirs();
try {
f.createNewFile();
FileOutputStream fos = new FileOutputStream(f);
fos.write(classfileBuffer);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}

return null;
}

}

3、 修改pom文件

用于指定:Premain-Class

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
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wwh.agent</groupId>
<artifactId>class-agent</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>
com.wwh.agent.PreMainExecutor
</Premain-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>

</project>

用法

将上面的maven项目编译打包,将 class-agent-0.0.1-SNAPSHOT.jar 复制到目标位置。

用如下命令启动想要破解的程序:

1
2
3
4
5
java -javaagent:class-agent-0.0.1-SNAPSHOT.jar -cp runner-0.0.1-SNAPSHOT.jar com.wwh.runner.EmptyRunner

//指定输出Class文件目录的
java -javaagent:class-agent-0.0.1-SNAPSHOT.jar=/opt/xxx/out -cp runner-0.0.1-SNAPSHOT.jar com.wwh.runner.EmptyRunner

如果需要破解的程序本身就有使用javaagent,需要将上面的agent放到调用链的最后面。

1
2
java -javaagent:agentA.jar -javaagent:class-agent-0.0.1-SNAPSHOT.jar XXProgram

效果

将在目录下保存所有加载的类的class文件

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
E:\opt\logs\wwh\classFile>tree
......
......
├─nio
│ ├─ch
│ ├─cs
│ └─fs
├─reflect
│ ├─annotation
│ └─generics
│ ├─factory
│ ├─parser
│ ├─reflectiveObjects
│ ├─repository
│ ├─scope
│ ├─tree
│ └─visitor
├─security
│ ├─action
│ ├─jca
│ ├─provider
│ └─util
├─text
│ └─resources
│ └─zh
├─usagetracker
└─util
├─calendar
├─locale
│ └─provider
├─logging
├─resources
│ ├─en
│ └─zh
└─spi
......
......

参考资料

instrument 规范

https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html?is-external=true

Class VirtualMachine

https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html#loadAgent-java.lang.String-

Interface ClassFileTransformer

https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html


java agent 实现

代理需要编译成jar包的方式来运行,JAR文件manifest中的属性指定将加载哪个代理类来启动代理。

有下面两种方式可以启动一个agent

一:命令行接口

程序没有启动时可以通过在命令行上指定javaagent的方式来启动代理

1
2
3
4
-javaagent:jarpath[=options]

#如:
java -javaagent:xxx-agent.jar -cp xxx.jar com.wwh.xxxx

通过命令行的方式可以指定多个代理,并且支持参数。初始化Java虚拟机(JVM)之后,将按照指定代理的顺序调用每个premain方法,然后调用真正的应用程序main方法。每个premain方法必须返回,以便继续启动程序。

agent Jar包中的manifest文件必须包含Premain-Class, 指向代理的入口类,这个类中包含了一个公共静态的premain方法

premain 方法有两种签名,虚拟机会尝试先运行下面这个

1
public static void premain(String agentArgs, Instrumentation inst);

如果没有会尝试调用下面这个

1
public static void premain(String agentArgs);

当使用命令行选项启动代理时,不会调用agentmain方法。

代理类将由系统类加载器加载(参见ClassLoader.getSystemClassLoader)。这是类加载器,它通常加载包含应用程序主方法的类。premain方法将在与应用程序main方法相同的安全性和类加载器规则下运行。对于代理premain方法可以做什么,没有建模限制。application main可以做的任何事情,包括创建线程,都是合法的。

每个代理都通过agentArgs参数传递其代理选项。代理选项作为单个字符串传递,任何额外的解析都应该由代理本身执行。

二:在虚拟机启动之后再启动代理

程序已经启动后可以通过VirtualMachine 来加载启动代理,如下:

1
2
3
VirtualMachine vm = VirtualMachine.attach("2177");
vm.loadAgent(jar);
vm.detach();

注意:

  1. 代理JAR的manifest中必须包含属性 Agent-Class。此属性的值是代理类的名称。
  2. 代理类必须实现一个公共静态的 agentmain 方法,如下所示。

启动代理时先尝试运行

1
public static void agentmain(String agentArgs, Instrumentation inst);

找不到再尝试运行

1
public static void agentmain(String agentArgs);

agentmain方法不能阻塞,这个类同用可以拥有 premain 方法,不过并不会被调用

参数通过如下方式指定:

1
vm.loadAgent(jar, options);

Manifest 属性说明

代理JAR文件定义了以下清单属性:

  1. Premain-Class 此属性指定代理类,也就是包含premain方法的类。如果该属性不存在,JVM将中止。注意这是一个类名,而不是文件名或路径。
  2. Agent-Class 指定代理类,支持在VM启动后启动代理的机制,包含了agentmain 方法的类,如果该属性不存在,则代理将不会启动。注意这是一个类名,而不是文件名或路径。
  3. Boot-Class-Path 引导类装入器要搜索的路径列表
  4. Can-Redefine-Classes 布尔值( true 或 false ,不区分大小写)。代理是否可以重新定义类。此属性是可选的,默认为false。
  5. Can-Retransform-Classes 布尔值( true 或 false ,不区分大小写)。代理是否可以重新转换类。此属性是可选的,默认为false。
  6. Can-Set-Native-Method-Prefix 布尔值( true 或 false ,不区分大小写)。代理是否可以设置本地方法前缀。此属性是可选的,默认为false。

代理JAR文件可同时具有清单中的Premain-Class和Agent-Class属性。当使用-javaagent选项在命令行上启动代理时,执行Premain-Class属性指定的
代理类,而Agent-Class属性将被忽略。类似地,如果代理在VM启动之后再启动,则执行Agent-Class属性指定的代理类,而忽略Premain-Class属性的值。


相关类说明

几个关键类和接口

VirtualMachine

表示一个java虚拟机
VirtualMachine表示已附加到的Java虚拟机。它所附加的Java虚拟机有时称为目标虚拟机或目标VM。应用程序(通常是managemet控制台或分析器之类的工具)使用虚拟机将代理加载到目标VM中。例如,用Java语言编写的分析器工具可能附加到正在运行的应用程序,并加载其分析器代理来分析正在运行的应用程序。

通过调用带有标识目标虚拟机的标识符的attach方法来获得虚拟机。标识符依赖于实现,但在每个Java虚拟机都在自己的操作系统进程中运行的环境中,标识符通常是进程标识符(或pid)。另外,通过使用从list方法返回的虚拟机描述符列表中获得的VirtualMachineDescriptor调用attach方法来获得虚拟机实例。一旦获得对虚拟机的引用,就使用loadAgent、loadAgentLibrary和loadAgentPath方法将代理加载到目标虚拟机中。loadAgent方法用于加载用Java语言编写并部署在JAR文件中的代理。loadAgentLibrary和loadAgentPath方法用于加载部署在动态库或静态链接到VM并使用JVM工具接口的代理。

除了加载代理之外,虚拟机还提供对目标VM中的系统属性的读访问。这在某些环境中非常有用,比如java。home、os.name或os。arch用于构造将加载到目标VM的代理的路径。

一个启动jmx的例子

1
2
3
4
5
6
7
8
9
10
// attach to target VM
VirtualMachine vm = VirtualMachine.attach("2177");

// start management agent
Properties props = new Properties();
props.put("com.sun.management.jmxremote.port", "5000");
vm.startManagementAgent(props);

// detach
vm.detach();

虚拟机对于多个并发线程的使用是安全的。

ClassFileTransformer

代理提供此接口的实现,以便转换类文件。转换发生在JVM定义类之前。

一个代理提供者需要实现:ClassFileTransformer 接口,来转变class文件,这个接口有一个方法

1
2
3
4
5
6
7
byte[]
transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;

此方法的实现可以转换提供的类文件并返回一个新的替换类文件

一旦向addTransformer注册了一个transformer,每个新的类定义和每个类重定义都会调用这个transformer。每个类重新转换时也将调用具有重新转换能力的转换器。
对新类定义的请求是用 ClassLoader.defineClass 或本地调用触发的。
对类的重定义请求是通过 Instrumentation.redefineClasses 或本地调用触发的。
对类重新转换的请求是通过 Instrumentation.retransformClasses 或本地调用触发的。
在处理请求期间,在类文件字节被验证或应用之前调用转换器。
当有多个转换器时,转换由链接转换调用组成。也就是说,一个转换调用返回的字节数组将成为下一个调用的输入(通过classfileBuffer参数)。

关于transform输入的classfileBuffer参数:
如果实现方法确定不需要转换,则返回null。否则,它应该创建一个新的byte[]数组,将输入classfileBuffer连同所有所需的转换复制到其中,并返回新数组。不能修改输入classfileBuffer。

在重新转换和重新定义的情况下,转换器必须支持重新定义语义:如果转换器在初始定义期间更改的类稍后被重新转换或重新定义,转换器必须确保第二个类输出类文件是第一个输出类文件的合法重新定义。

如果transformer抛出异常(它没有捕获异常),后续的transformer仍然会被调用,并且负载、重新定义或重新转换仍然会被尝试。因此,抛出异常的效果与返回null相同。为了防止在transformer代码中生成未检查异常时出现意外行为,transformer可以捕获Throwable。如果转换器认为classFileBuffer不代表一个有效格式化的类文件,它应该抛出一个IllegalClassFormatException;而这与返回null具有相同的效果。它有助于记录或调试格式错误。

参数说明:

  1. loader 要转换的类的定义类加载器,如果是bootstrap loader则为空
  2. className 类名的内部形式为Java虚拟机规范中定义的完全限定类名和接口名。例如:”java/util/List”。
  3. classBeingRedefined 如果这是由重新定义或重新转换触发的,则这个类存在重新定义或重新转换,否则为null。
  4. protectionDomain 正在定义或重新定义的类的保护域
  5. classfileBuffer 类文件格式的输入字节缓冲区-不能修改

返回:
格式良好的类文件缓冲区(转换的结果),如果没有执行转换,则为null。

Instrumentation

该类提供测试Java编程语言代码所需的服务。插装是将字节码添加到方法中,以便收集工具使用的数据。由于这些更改纯粹是附加的,所以这些工具不会修改应用程序状态或行为。此类良性工具的例子包括监视代理、分析器、覆盖率分析器和事件日志记录器。

获取Instrumentation 接口实例有两种方法:

  1. 当JVM以指示代理类的方式启动时。在这种情况下,将一个插装实例传递给代理类的premain方法。
  2. 当JVM在启动后的某个时候提供启动代理的机制时。在这种情况下,将一个插装实例传递给代理代码的agentmain方法。

一旦代理获得一个Instrumentation 实例,代理可以在任何时候调用该实例上的方法。

1
Instrumentation.addTransformer(new Transformer(), true);  

第二个参数表示是否可以重新转换已经定义好了的类
对于启动后再附加agent的方式,如果想要改变已经加载了的类,需要设置为true

并且注意修改manifest文件中的

1
Can-Retransform-Classes: true

否则会报错:

1
adding retransformable transformers is not supported in this environment

示例

pom 文件示例:
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
<dependencies>
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<!-- 参数方式启动agent需要这个 -->
<Premain-Class>
com.wwh.agentmain.AgentMain
</Premain-Class>

<!-- 启动后附加启动agent需要这个 -->
<Agent-Class>
com.wwh.agentmain.AgentMain
</Agent-Class>

<!-- 是否可以重新转换类 -->
<Can-Retransform-Classes>
true
</Can-Retransform-Classes>

<!-- 是否可以重新定义类 -->
<Can-Redefine-Classes>
true
</Can-Redefine-Classes>

</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
manifest文件示例:

META-INF/MANIFEST.MF

1
2
3
4
5
6
7
8
9
Manifest-Version: 1.0
Premain-Class: com.wwh.agentmain.AgentMain
Archiver-Version: Plexus Archiver
Built-By: Administrator
Agent-Class: com.wwh.agentmain.AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.5.3
Build-Jdk: 1.8.0_151

代码例子

https://github.com/wangwen135/java-agent-test

问题

一个项目在开发环境,本地都能打包通过

在测试环境打包过不去

提示 error: cannot access xxxxxxx

原因

开发和测试环境用了两套私服

测试环境有人手动上传了jar包,该jar包对应的pom文件,由nexus生成【>POM was created by Sonatype Nexus】,没有dependencies 节点

处理

在nexus的 3rd party 中删除该jar,重新上传即可

注意:

如果通过手动上传的方式 GAV Definition 不能选择 GAV Parameters
应该选择:
GAV Definition: from pom

排查问题

检查环境、检查配置,对比jar包等都没有问题

使用 mvn dependency:tree 命令,发现测试环境比开发环境少依赖了几个jar包

然后找的mavne 的 【localRepository】 目录中,按照路径找到对应jar包【根据提示缺少的类和依赖关系】的路径

如:

/data/repo/com/xxx/xxx/xxxxxxx/0.1.9.RELEASE

这个目录下有xxxx-version.jar和一个对应的xxxx-version.pom文件,问题就是这个pom文件没有dependencies信息

有问题pom文件如:

1
2
3
4
5
6
7
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.appleframework.jms</groupId>
<artifactId>apple-jms-kafka</artifactId>
<version>0.1.9.RELEASE</version>
<description>POM was created by Sonatype Nexus</description>
</project>

正常的应该是:

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
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.appleframework.jms</groupId>
<artifactId>apple-jms</artifactId>
<version>0.1.9.RELEASE</version>
</parent>
<artifactId>apple-jms-kafka</artifactId>
<name>apple-jms-kafka</name>
<url>http://mvnrepo.appleframework.com</url>

<dependencies>

<dependency>
<groupId>com.appleframework.jms</groupId>
<artifactId>apple-jms-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.11.0.3</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
<scope>provided</scope>
</dependency>

</dependencies>
</project>

1
2
3
4
5
6

SET PATH=G:\360WIFI
Takeown /F %PATH% /r /d y
cacls %PATH% /t /e /g Administrators:F
rd /s /q %PATH%
@pause

实际测试情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\Windows\SysWOW64\Macromed\Flash>del /F activex.vch
C:\Windows\SysWOW64\Macromed\Flash\activex.vch
拒绝访问。

C:\Windows\SysWOW64\Macromed\Flash>takeown /F activex.vch

成功: 此文件(或文件夹): "C:\Windows\SysWOW64\Macromed\Flash\activex.vch" 现在由用户 "WWH\wangw" 所有。

C:\Windows\SysWOW64\Macromed\Flash>cacls activex.vch /e /g "WWH\wangw":F
处理的文件: C:\Windows\SysWOW64\Macromed\Flash\activex.vch

C:\Windows\SysWOW64\Macromed\Flash>del activex.vch

C:\Windows\SysWOW64\Macromed\Flash>

删除目录测试:

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

C:\Windows\SysWOW64\Macromed>rd /s Flash
Flash, 是否确认(Y/N)? y
Flash\Flash.ocx - 拒绝访问。
Flash\FlashUtil_ActiveX.dll - 拒绝访问。
Flash\FlashUtil_ActiveX.exe - 拒绝访问。

C:\Windows\SysWOW64\Macromed>takeown /F Flash /r

成功: 此文件(或文件夹): "C:\Windows\SysWOW64\Macromed\Flash" 现在由用户 "WWH\wangw" 所有。

成功: 此文件(或文件夹): "C:\Windows\SysWOW64\Macromed\Flash\Flash.ocx" 现在由用户 "WWH\wangw" 所有。

成功: 此文件(或文件夹): "C:\Windows\SysWOW64\Macromed\Flash\FlashUtil_ActiveX.dll" 现在由用户 "WWH\wangw" 所有。

成功: 此文件(或文件夹): "C:\Windows\SysWOW64\Macromed\Flash\FlashUtil_ActiveX.exe" 现在由用户 "WWH\wangw" 所有。

C:\Windows\SysWOW64\Macromed>cacls Flash /t /e /g "WWH\wangw":F
处理的目录: C:\Windows\SysWOW64\Macromed\Flash
处理的文件: C:\Windows\SysWOW64\Macromed\Flash\Flash.ocx
处理的文件: C:\Windows\SysWOW64\Macromed\Flash\FlashUtil_ActiveX.dll
处理的文件: C:\Windows\SysWOW64\Macromed\Flash\FlashUtil_ActiveX.exe

C:\Windows\SysWOW64\Macromed>rd /s Flash
Flash, 是否确认(Y/N)? y

C:\Windows\SysWOW64\Macromed>

配置

先安装redis

1
2
make
make PREFIX=/usr/local/redis install

找个地方创建两个文件,如:

1
2
mkdir d6379
mkdir d6380

把redis.conf文件复制到这两个目录

进入d6379目录中,配置文件就改了

1
daemonize yes

启动第一个redis

1
/usr/local/redis/bin/redis-server redis.conf 

进入d6380目录中,配置文件修改为

1
2
3
4
5
port 6380
daemonize yes

# slaveof <masterip> <masterport>
slaveof 127.0.0.1 6379

启动从节点

1
/usr/local/redis/bin/redis-server redis.conf 

查看一下进程情况

1
2
3
ps -ef | grep redis
root 8428 1 0 17:06 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:6379
root 8453 1 0 17:16 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:6380

测试

再开一个shell窗口,分别登入两个redis

登录主的

1
2
$ /usr/local/redis/bin/redis-cli 
127.0.0.1:6379>

登录从的

1
2
$ /usr/local/redis/bin/redis-cli -p 6380
127.0.0.1:6380>

分别查看他们的状态:
主的

1
2
3
4
127.0.0.1:6379> info Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=98,lag=0

从的

1
2
3
4
5
127.0.0.1:6380> info Replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379

在主的上设置值

1
2
3
4
127.0.0.1:6379> set key1 vvv1
OK
127.0.0.1:6379> keys *
1) "key1"

在从的上查看

1
2
3
4
127.0.0.1:6380> keys *
1) "key1"
127.0.0.1:6380> get key1
"vvv1"

可以看到从的已经自动从主的上复制了数据

再试一下在从的上写数据

1
2
127.0.0.1:6380> set key2 vvv2
(error) READONLY You can't write against a read only slave.

主从切换

SLAVEOF host port
SLAVEOF 命令用于在 Redis 运行时动态的修改复制(replication)功能的行为。

通过执行 SLAVEOF host port 命令,可以将当前服务器转变为指定服务器的从属服务器(slave server)。

如果当前服务器已经是某个主服务器(master server)的从属服务器,那么执行 SLAVEOF host port 将使当前服务器停止对旧主服务器的同步,丢弃旧数据集,转而开始对新主服务器进行同步。

另外,对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。

利用『 SLAVEOF NO ONE 不会丢弃同步所得数据集』这个特性,可以在主服务器失败的时候,将从属服务器用作新的主服务器,从而实现无间断运行。


将从服务器升级为主服务器,并且关闭复制功能

1
2
3
4
5
6
7
127.0.0.1:6380> SLAVEOF NO ONE
OK
127.0.0.1:6380> info Replication
# Replication
role:master
connected_slaves:0

此时可以写入数据

1
2
127.0.0.1:6380> set k5 'upgrade to master'
OK

将6379转成6380的从服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
127.0.0.1:6379> slaveof 127.0.0.1 6380
OK
127.0.0.1:6379> info Replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:2844
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:9c8b0e2e24636212f659b3f8bc17cbae51785db2
master_replid2:6e0c53cc1c7438c88273db17fa654f285026e1ce
master_repl_offset:2844
second_repl_offset:2777
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:2844

变成从的后会删除自己已有的数据,并将主服务器的数据复制过来
可以看到之前添加的数据也复制过来了

1
2
3
4
5
6
127.0.0.1:6379> keys *
1) "kk2"
2) "k5"
3) "key1"
127.0.0.1:6379> get k5
"upgrade to master"

主节点下的从节点的信息都可以通过info命令查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6380> info Replication
# Replication
role:master
connected_slaves:3
slave0:ip=192.168.1.215,port=6379,state=online,offset=4768,lag=1
slave1:ip=192.168.1.215,port=6380,state=online,offset=4768,lag=0
slave2:ip=127.0.0.1,port=6379,state=online,offset=4768,lag=0
master_replid:d3ea04b9553a6c20b2bbe836dd35e2311ee1ed06
master_replid2:d7ded5c06b7d723590dd4aa8bee2b7020f3cbc96
master_repl_offset:4768
second_repl_offset:4617
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:4533
repl_backlog_histlen:236

问题

生产环境栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
3282788         at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282789 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282790 Exception in thread "pool-250-thread-43" java.lang.StackOverflowError
3282791 at java.util.AbstractCollection.toArray(AbstractCollection.java:183)
3282792 at java.lang.String.split(String.java:2378)
3282793 at com.chedaia.task.address.util.ParkingPointUtil.isGps(ParkingPointUtil.java:32)
3282794 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:406)
3282795 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282796 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282797 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282798 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)
3282799 at com.chedaia.task.address.util.ParkingPointUtil.getPreviewGpsPoint(ParkingPointUtil.java:410)

原因

递归调用次数太多导致
且启动脚本中设置了-Xss

1
-XX:PermSize=128m -Xss256k 

解决办法

由于涉及到的代码依赖较多,先不改动代码
加大线程栈大小到2m
1
-XX:PermSize=128m -Xss2m 


线程栈大小与递归次数测试

代码

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
recursion(0);
}

public static void recursion(int i) {
System.out.println(i);
i++;
recursion(i);
}

执行结果

结果:966

此时的栈设置为: -Xss128k

1
2
3
4
5
6
7
8
9
10
963
964
965
966
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
at sun.nio.cs.UTF_8.access$200(UTF_8.java:57)
at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636)
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java

不同栈大小下的递归次数

线程栈大小 递归次数约为
-Xss128k 966
-Xss256k 2467
-Xss512k 5447
-Xss1m 11404
-Xss2m 23323
-Xss3m 35247
-Xss4m 41306

方法中的局部变量会占用栈空间

当递归的方法中定义了一些局部变量时会占用线程栈的空间,从而影响到递归的次数
如将上面的方法稍微修改一下,增加3个局部变量:

1
2
3
4
5
6
7
8
public static void recursion(int i) {
System.out.println(i);
long a = 1l;
long b = 2l;
long c = a + b;
i++;
recursion(i);
}

在栈大小设置为: -Xss128k 时,结果是:623

1
2
3
4
5
6
7
8
9
620
621
622
623
java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
at sun.nio.cs.UTF_8.access$200(UTF_8.java:57)
at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:636)
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)

官方说明

https://www.oracle.com/technetwork/java/hotspotfaq-138619.html#threads_oom

您可能遇到线程的默认堆栈大小的问题。在Java SE 6中,Sparc的默认值是32位VM中的512k, 64位VM中的1024k。

在x86 Solaris/Linux上,32位虚拟机中是320k, 64位虚拟机中是1024k。

在Windows上,默认的线程堆栈大小是从二进制文件(java.exe)读取的。在Java SE 6中,这个值在32位VM中是320k,在64位VM中是1024k。

您可以通过使用-Xss选项运行来减小堆栈大小。例如:

1
java -server -Xss64k

注意,在某些版本的Windows上,操作系统可能使用非常粗的粒度来计算线程堆栈大小。如果请求的大小小于默认大小1K或更大,则将堆栈大小四舍五入到默认大小;否则,堆栈大小将四舍五入为1 MB的倍数。

64k是每个线程允许的最小堆栈空间量。

不显式设置-Xss或-XX:ThreadStackSize时,或者把-Xss或者-XX:ThreadStackSize设为0,就是使用“系统默认值”。

用命令查看

1
2
3
4
5
6
7
# java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
intx CompilerThreadStackSize = 0 {pd product}
intx ThreadStackSize = 1024 {pd product}
intx VMThreadStackSize = 1024 {pd product}
java version "1.8.0_162"
Java(TM) SE Runtime Environment (build 1.8.0_162-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)

测试环境

  • 系统:WIN 10
  • MySQL版本:mysql-5.7.23

安装配置

解压文件

使用mysql-5.7.23-winx64.zip包解压缩安装
路径分别为:

  • D:\mysql\mysql-5.7.23-winx64
  • D:\mysql\mysql-5.7.23-winx64-3307

解压安装路径随意指定,注意需要与my.ini文件中的路径一致

my.ini配置文件

分别在两个目录中创建my.ini 文件

在同一台机器上测试,mysql实例使用不同端口
主数据库:3306 (默认)
从数据库:3307

内容如下:

主库my.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[mysqld]
# set basedir to your installation path
basedir=D:/mysql/mysql-5.7.23-winx64
# set datadir to the location of your data directory
datadir=D:/mysql/mysql-5.7.23-winx64/data

# 唯一标识
server-id=1

# binlog文件名
log-bin=mysql-binlog

# 要写binlog的数据库,要同步多个数据库,就多加几个binlog-do-db=数据库名
binlog-do-db=mstest
binlog-do-db=test

# 要忽略的数据库
binlog-ignore-db=mysql

从库my.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[mysqld]
# 指定端口
port=3307
# set basedir to your installation path
basedir=D:/mysql/mysql-5.7.23-winx64-3307
# set datadir to the location of your data directory
datadir=D:/mysql/mysql-5.7.23-winx64-3307/data

# 唯一标识
server-id=2

# 要复制多个数据库,就多加几个replicate-do-db=数据库名
replicate-do-db=mstest
replicate-do-db=test

# 要忽略的数据库
replicate-ignore-db=mysql

初始化并启动数据库

分别初始化、启动两个数据库,并修改root密码

1
2
3
4
5
mysqld --initialize --console

mysqld.exe --console

ALTER USER 'root'@'localhost' IDENTIFIED BY 'root';

具体见安装文档

数据库配置

主库配置

登录主库

1
>mysql -uroot -p

主数据库创建用于同步的用户

1
GRANT REPLICATION SLAVE ON *.* TO 'mstest'@'%' IDENTIFIED BY '123456';
从数据库配置

登录从库,指定端口3307

1
>mysql -uroot -p -P3307

配置从库连接到主库

1
2
3
4
5
change master to master_host='127.0.0.1',master_port=3306,
master_user='mstest',master_password='123456';

start slave;

查看状态

主库
1
2
3
4
5
6
7
mysql> show master status;
+---------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------------+----------+--------------+------------------+-------------------+
| mysql-binlog.000002 | 313 | mstest,test | mysql | |
+---------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
从库
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

mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 127.0.0.1
Master_User: mstest
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-binlog.000002
Read_Master_Log_Pos: 1306
Relay_Log_File: wwh-relay-bin.000005
Relay_Log_Pos: 323
Relay_Master_Log_File: mysql-binlog.000002
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB: mstest,test
Replicate_Ignore_DB: mysql
......
......
......
Master_Server_Id: 1
Master_UUID: ea65d565-a8a0-11e8-807e-0a002700000a
Master_Info_File: D:\mysql\mysql-5.7.23-winx64-3307\data\master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
......
......
1 row in set (0.00 sec)

同步测试

创建数据库

主库创建表,从库查看

主库插入记录,从库查看

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
mysql> create database test;
Query OK, 1 row affected (0.01 sec)

mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
| wwh |
+--------------------+
6 rows in set (0.00 sec)



mysql> status;
--------------
mysql Ver 14.14 Distrib 5.7.23, for Win64 (x86_64)

Connection id: 2
Current database:
Current user: root@localhost
SSL: Not in use
Using delimiter: ;
Server version: 5.7.23 MySQL Community Server (GPL)
Protocol version: 10
Connection: localhost via TCP/IP
Server characterset: latin1
Db characterset: latin1
Client characterset: gbk
Conn. characterset: gbk
TCP port: 3307
Uptime: 7 min 37 sec




show binlog events\G


show master status\G




查询命令

在master上查看当前有多少个从节点

1
2
select * from information_schema.processlist as p where p.command = 'Binlog Dump'; 

在windows 和 linux的命令行来回切换,经常敲错命令,如:ls、pwd、grep 等

通过写bat的办法让windows支持这几个常用命令

找一个目录,将其添加到环境变量中,在目录中新建几个bat文件,如下:

ll.bat

1
2
3
@echo off
REM 显示一个目录中的文件和子目录
dir

pwd.bat

1
2
3
@echo off
REM 显示当前目录的名称或将其更改。
chdir

ls.bat

1
2
3
4
@echo off
REM 显示一个目录中的文件和子目录 /W 用宽列表格式

dir /W

grep.bat

1
2
3
4
5
6
7
8
9
@echo off
REM 模拟grep
REM 在文件中搜索字符串。
REM FIND [/V] [/C] [/N] [/I] [/OFF[LINE]] "string" [[drive:][path]filename[ ...]]
REM /I 搜索字符串时忽略大小写。


find /I "%1"

clear.bat

1
2
3
4
@echo off
REM 清除屏幕

cls

checksum.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@echo OFF
:LOOP
set index=%1
if %index%! == ! goto END
echo.
echo File : %index%
echo.
set /p="MD5 : "<nul
certutil -hashfile "%index%" MD5|findstr /V "MD5"|findstr /V "CertUtil"
set /p="SHA1 : "<nul
certutil -hashfile "%index%" SHA1|findstr /V "SHA1"|findstr /V "CertUtil"
set /p="SHA256 : "<nul
certutil -hashfile "%index%" SHA256|findstr /V "SHA256"|findstr /V "CertUtil"
shift
goto LOOP
:END
echo.

exp.bat

1
2
3
4
@echo off
REM 资源管理器打开当前目录

explorer /select=%cd%

sp.bat

1
2
3
4
@echo off
REM 打开windows截图工具

start snippingtool

win10快捷键 win + shift + s

0%