Java 查漏补缺之: GC 垃圾回收

说到 Java 很多人脑海会直接蹦出内存自动回收,会经常听到 GC 这些词,GC 指的是 Garbage Collection 也就是垃圾回收。说到垃圾回收就不可避免的去看下 Java 的内存管理机制。

内存管理

提到内存管理可能很多人都会联想起 C/C++ 的手动内存管理,以及 Java/Python 的自动管理,但实际上这都是指的堆内存管理。常规的内存管理可以分成两个部分,栈内存管理和堆内存管理。

栈的发明解决了部分内存的自动回收,但是栈的局限在于只能自动管理固定长度的内存,而对于堆内存,不同语言有不同的管理方式:

  • 纯手动管理 C/C++
  • 自动管理 Java/Python/PHP/C# 等
  • 半自动 C++ 智能指针 Swift/Rust 等

什么是 GC 以及为何要有 GC

对于通常含义上的 GC 可以参考维基词条, John McCarthy 在发明 Lisp 时一并发明了内存自动管理机制。

什么是内存垃圾,也就是程序在执行过程中再无法访问的对象,这些对象所占用的内存空间可以收回来重新使用。

优点:

  • 编码容易
  • 减少因内存管理而导致的 bug,野指针,内存泄露等等

GC 的缺点:

  • 需要消耗额外的 CPU / Memory 资源
  • 代码执行时间无法估计

GC 实现方式

Reference counting

引用计数,最简单也最容易实现的一种,原理是在每个对象中保存该对象的引用计数,当引用发生增减时对计数进行更改。

优点:

  • 当对象不再被引用立即就会被释放,算法运行快
  • 空间释放是针对个别执行,和其他算法相比,GC 中断时间比较短

问题:

  • 额外的内存占用,每个对象需要一个 counter
  • 引用发生增减时需要对计数做出正确的增减,如果发生计数错误可能会导致难以发现的内存错误
  • 循环引用,两个对象互相引用,能解决但需要大量计算
  • 引用计数不适合并行处理,多线程同时对引用计数进行增减时,引用计数可能会产生不一致的问题,而如果采用加锁方式,带来的开销也非常大

引用计数的例子:

  • Python
  • PHP
  • Swift/ OC

Mark and Sweep

标记清除,是最早开发出来的算法(1960 年),原理,从根开始可能被引用的对象用递归的方式进行标记,然后将没有标记的对象作为垃圾回收。

缺点:

  • 在分配了大量对象,而只有一小部分存活的情况下,算法消耗的时间多
  • 执行时间不可控

Copy and Collection

复制收集,为了克服标记清除的问题,在算法中将根开始被引用的对象复制到另外的空间中,然后将复制的对象所能够引用的对象用递归方式复制下去。

复制收集方式的过程相当于标记清除算法中的标记阶段,但由于清除阶段依然要对所有对象进行扫描,如果存在大量对象,而且大量对象已经死亡的情况,开销会加大。

优点:

  • 没有内存碎片
  • 复制收集过程中,按照对象被引用的顺序将对象复制到新空间,关系较近的对象被放到较近空间的可能性提高,局部性能提升,内存缓存可能更容易命中

缺点:

  • 和标记清除相比,复制对象的开销加大,当存活对象较多的情况下,性能损耗

JVM 如何实现 GC

现代版的 GC 使用分代收集,按照对象存活时间长短来使用不同的垃圾回收算法。

heap 堆内存分为:

  • Young Generation:新创建的对象,Young Generation 又被分成 Eden space (所有新对象开始的地方),两个 Survivor spaces(在存活一个 gc 之后移动到这里),当对象在 Young Generation 被回收,这是 minor garbage collection event(简称 minor gc)。
  • Old Generation: 当对象存活足够长时间,会从 Young Generation 移动到 Old Generation。当对象在 Old Generation 被回收,这是一次 major garbage collection event.
  • Permanent Generation: 类,方法等 Metadata 会保存在 Permanent Generation。不再被使用的 Classes (ClassLoader 回收后)会被回收。

所以一个对象在不同分区的流程可能是:

  • 新对象在 Eden 中创建
  • Eden 满时进入 Survivor spaces
  • 两个 Survivor 空间的对象相互交换
  • 在 Survivor 存活一定时间后进入 Old

一种 JVM 的实现,由 1999 年引入,HotSpot 为代表,HotSpot JVM 有四种 Garbage Collector:

  • Serial GC : 所有的 garbage collection events 通过一个线程连续管理, Compaction 在每一次 garbage collection 之后执行
  • Parallel GC : 并行进行 minor garbage collection,一个线程用来 major garbage collection 和 Old Generation compaction
  • Concurrent-Mark-and-Sweep GC : 简称 CMS,多个线程用来 minor garbage collection ,使用和 Parallel 相同的算法。CMS 在应用程序之外运行,GC 和 应用程序并行,减少中断。不会执行 compaction
  • G1 GC(1.7+) : Garbage First,新的 garbage collector,用来替换 CMS,同样是并行并发的,但是原理和工作方式完全不一样

Java 的 GC 是不确定的,没有方法来预测何时会发生 gc。在代码中可以使用 System.gc() 或者 Runtime.gc() 方法来暗示 gc,但是 Java 不能保证 gc 一定会执行。

GC 的调优可以从 JVM 的参数调节:

  • -Xms 初始堆大小,可以设置和 Xmx 相同,避免每次垃圾回收后 JVM 重新分配内存
  • -Xmx 最大堆大小
  • -XX:NewRatio=n Young 和 old 区的大小比 1:n
  • -XX:MaxPermSize Permanent 大小
  • -XX:+UseG1GC 使用 G1
  • -XX:MaxGCPauseMillis=n 最大希望暂停时间
  • -XX:InitiatingHeapOccupancyPercent=n 堆使用到多少百分比时开始 CMS 过程
  • -XX:+PrintGC 输出 GC 日志
  • -XX:+PrintGCDetails 输出 GC 的详细日志
  • -XX:+PrintGCTimeStamps 输出 GC 时间戳
  • -XX:+PrintGCDateStamps 输出 GC 时间戳(日期形式,2019-01-01T01:01:02.212+0800)
  • -XX:+PrintHeapAtGC 进行 GC 的前后打印堆信息
  • -verbose:gc
  • -XX:+PrintReferenceGC 年轻代各个引用的数量以及时长
  • -Xloggc:../logs/gc.log 日志文件输出路径

JVM 内存区域划分

Heap 区

  • Eden
  • Survivor
  • Old gen

非 Heap 区:

  • Code Cache, 代码缓存区,它主要用于存放 JIT 所编译的代码
  • Perm Gen,Permanent Generation space,是指内存的永久保存区域
  • Jvm Stack
  • Local Method Stack

为什么需要 Survivor 区

前提知识,新生代内存中除了 Eden 区外还有两个 Survivor 区,Eden 占 80%,两块 Survivor 区占比 20%。

如果没有 Survivor ,Eden 区每进行一次 Minor GC,存活的对象都会被送到老年区,老年代很快被填满。

每进行一次 Minor GC,存活下来的对象会被计数 +1,当对象在 Minor GC 下存活多次,达到一个阈值后会被移动到老年代。

Survivor 存在的意义,减少被送到老年代的对象,减少 Full GC 发生,只有经历 16 次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。

JVM 参数设置和调优

生产环境 Xms 和 Xmx 设置相同的值

In a production environment, if you monitor the GC data, you will notice that is a relatively short period of time (usually less than an hour), the JVM will eventually increase the heap size to the -Xmx setting. Each time the JVM increases the heap size it must ask the OS for additional memory, which takes time (and thus adds to the response time of any requests that were is process when the GC hit). And usually the JVM will never let go of that memeory. Therefore, since the JVM will eventually grab the -Xmx memory, you might as well set it to that at the beginning.

Another point is that with a smaller heap size (starting with -Xms), GCs will happen more often. So by starting with a larger heap initially the GCs will happen not as often.

Finally, in a production environment, you usually run only one app server per OS (or per VM). So since the app server is not competing for memory with other apps you might as well give it the memory up front.

Note that the above is for production. It applies also to the syatem test environment since the system test environment should mimic production as close as possible.

For development, make -Xms and -Xmx different. Usually, you are not doing much work with the app server in development, so it will often stay with the -Xms heap setting. Also, since in development the app server will share the machine with lots of other apps (mail client, word processors, IDEs, databases, browsers, etc), setting the -Xms to a smaller size lets the app server play more nicely with the other software that is also competing for the same resources.

gc log

enable gc log

通过如下任意一个开启 gc log:

-XX:+PrintGC
-XX:+PrintGCDetails
-Xloggc:gc.log

开启 -XX:+PrintGC 后,打印日志:

[GC (Allocation Failure)  61905K->9848K(256000K), 0.0040139 secs]

说明:

  • GC 表示是一次 YGC(Young GC)
  • Allocation Failure 表示是失败
  • 61905KK->9848K 表示年轻代从 61905KK 降为 9848K
  • 256000K 表示整个堆的大小
  • 0.0040139 secs 表示这次 GC 总计所用的时间

开启 -XX:+PrintGCDetails 后,日志:

2020-03-28T08:55:24.916+0800: 229805.169: [GC (Allocation Failure) 2020-03-28T08:55:24.916+0800: 229805.170: [ParNew: 584336K->24291K(629120K), 0.0145141 secs] 1849190K->1289986K(2027264K), 0.0155393 secs] [Times: user=0.08 sys=0.02, real=0.02 secs]

说明:

  • 第一个时间戳 2020-03-28T08:55:24.916+0800 是 Mirror GC 发生的时间
  • 229805.169 GC 开始的时间,这里是相对 JVM 启动时间,单位秒
  • GC 用来区分 GC 类型,Minor GC 或者 Full GC
  • Allocation Failure 分配内存失败
  • ParNew 收集器名称
  • 584336K->24291K 前后年轻代使用
  • 629120K 整个年轻代的容量
  • 跟随的时间是 gc 发生的时间
  • 1849190K->1289986K 堆前后使用情况
  • 后接的时间是 ParNew 收集器标记和复制年轻代存活的对象的时间

    最后出现的 [Times] 中三个时间是:

  • user: GC 线程在垃圾收集中使用 CPU 时间
  • sys: 系统调用时间
  • real: 应用被暂停的时钟时间,GC 是多线程的所以, real < (user + sys)

Example

public class GCLogDemo {

	public static void main(String[] args) {
		int _1m = 1024 * 1024;
		byte[] data = new byte[_1m];
		// 将 data 置为 null 即让它成为垃圾
		data = null;
		// 通知垃圾回收器回收垃圾
		System.gc();
	}
}

相关命令

jstat

jstat 可以用来查看 JVM 数据信息:

jstat [options] vmid [interval] [count]
  • options 使用 -gc 或者 -gcutil
  • 这里的 vmid 是 vm 的进程号,当前运行的 java 进程号

比如查看 gc 情况

jstat -gc [PID]

每 5 秒打印一次特定 PID 的 GC 情况

jstat -gc [PID] 5000
jstat -gc [PID] 5s

结果说明:

前提知识,新生代内存中除了 Eden 区外还有两个 Survivor 区,Eden 占 80%,两块 Survivor 区占比 20%。

S0C:当前年轻代中第一个 survivor(幸存区)的容量 (字节) ,简记成 Survivor 0 Current
S1C:当前年轻代中第二个 survivor(幸存区)的容量 (字节) 
S0U:年轻代中第一个 survivor(幸存区)目前已使用空间 (字节) 
S1U:年轻代中第二个 survivor(幸存区)目前已使用空间 (字节) 
EC:当前年轻代中 Eden(伊甸园)的容量 (字节) 
EU:年轻代中 Eden(伊甸园)目前已使用空间 (字节) 
OC:当前 Old 代的容量 (字节) 
OU:Old 代目前已使用空间 (字节) 
PC:当前 Perm(持久代)的容量 (字节) 
PU:Perm(持久代)目前已使用空间 (字节) 
YGC:从应用程序启动到采样时年轻代中 gc 次数 
YGCT:从应用程序启动到采样时年轻代中 gc 所用时间 (s) 
FGC:从应用程序启动到采样时 old 代(全 gc)gc 次数 
FGCT:从应用程序启动到采样时 old 代(全 gc)gc 所用时间 (s) 
GCT:从应用程序启动到采样时 gc 用的总时间 (s) 
NGCMN:年轻代 (young) 中初始化(最小)的大小 (字节) 
NGCMX:年轻代 (young) 的最大容量 (字节) 
NGC:年轻代 (young) 中当前的容量 (字节) 
OGCMN:old 代中初始化(最小)的大小 (字节) 
OGCMX:old 代的最大容量 (字节) 
OGC:old 代当前新生成的容量 (字节) 
PGCMN:perm 代中初始化(最小)的大小 (字节) 
PGCMX:perm 代的最大容量 (字节)   
PGC:perm 代当前新生成的容量 (字节) 
S0:年轻代中第一个 survivor(幸存区)已使用的占当前容量百分比 
S1:年轻代中第二个 survivor(幸存区)已使用的占当前容量百分比 
E:年轻代中 Eden(伊甸园)已使用的占当前容量百分比 
O:old 代已使用的占当前容量百分比 
P:perm 代已使用的占当前容量百分比 
S0CMX:年轻代中第一个 survivor(幸存区)的最大容量 (字节) 
S1CMX :年轻代中第二个 survivor(幸存区)的最大容量 (字节) 
ECMX:年轻代中 Eden(伊甸园)的最大容量 (字节) 
DSS:当前需要 survivor(幸存区)的容量 (字节)(Eden 区已满) 
TT: 持有次数限制 
MTT : 最大持有次数限制 

如果使用 -gcutil 则是打印 GC 的使用率:

jstat -gcutil -h 10 [PID] 1000

jmap

使用 jmap 打印堆相关信息,更多细节可以参考这篇文章

jhat

更多关于 jhat 的用法可以参考这篇

reference


2015-02-27 java , gc , garbage-collection

每天学习一个命令:xclip 与剪贴板交互

xclip 命令可以从 stdin,或者文件读入数据到剪贴板,或者将剪贴板内容粘贴到目的应用中。xclip 命令建立了终端和剪切板之间通道,可以用命令的方式将终端输出或文件的内容保存到剪切板中,也可以将剪切板的内容输出到终端或文件

安装

sudo apt-get xclip

命令格式

xclip [OPTION] [FILE] ...

常用参数:

-i      从 stdin 读入
-o      打印到标准输出

使用实例

不加选项时只在保存在 X PRIMARY(终端剪切板),加上选项 -selection c 后保存在 X CLIPBOARD(外部程序剪切板)

为了区分这二者的区别,可以简单的做一个试验。

echo "Hello World" | xclip

此时 Hello World 字符只是在终端的剪贴板中,可以尝试在终端鼠标中键粘贴,发现终端的粘贴板是已经被修改的,此时用 Ctrl + v 粘贴到其他 GUI 应用程序(比如 Chrome 地址栏)发现粘贴板并不是 Hello World。

echo "TEST OUTSIDE CLIPBOARD" | xclip -sel c

此时会发现 Chrome 中可以粘贴 TEST 这行文本,而鼠标中键粘贴到终端的还是上面的 Hello World。

终端输出保存到剪切板中

ls -al | xclip
echo "SOME" | xclip
xclip /etc/passwd
xclip < /etc/passwd

此时 ls -al 的输出内容已经保存在剪切板中了,此时 xclip -o 可以看到剪切板的内容。

但此时还不可以粘贴到终端以外的程序中,此时需要用到: xclip -selection c

ls -al | xclip -selection c
xclip -sel c /etc/passwd
xclip -sel c < /etc/passwd

剪切板内容输出到终端

xclip -o
xclip -selection c -o

剪切板内容输出到文件

xclip -o > ~/test.txt
xclip -selection c -o > ~/test.txt

reference

  • man xclip

2015-02-26 linux , command , xclip

Java 的 IO 操作 java.io 包

InputStream 和 Reader 的区别

  • InputStream 是 byte 导向
  • Reader Writer 是字符导向

2015-02-20 java , java-io , io

每天学习一个命令:ffprobe 查看多媒体信息

在 ffmpeg package 中有一个 ffprobe 工具,主要用来查看多媒体文件或者流媒体信息,在线的视频信息也能够快速获取。大部分情况下个人比较喜欢使用 ffmpeg -i input.mp4 来快速查看,这种时候在终端上比较快速,而如果有些时候想要分析一下媒体文件,需要编程获取得到的媒体文件结果,显然 ffmpeg 的输出结果简直无法忍受,而 ffprobe 提供非常清晰的输出格式,非常方便的可以提供给编程软件解析使用。

官网说明:http://ffmpeg.org/ffprobe.html

命令格式

ffprobe [OPTION] file

常用的参数

-show_format            显示输入多媒体流的容器格式信息
-show_streams           显示输入多媒体流中每一个流的信息
-i input_file           指定输入文件
-print_format json      json 形式输出
-of 或者 -print_format  default/compact/csv/flat/ini/json/xml

命令行:

./ffprobe -print_format json -show_format -show_streams -i ./video/c.ts

其中:  

-print_format json 以 json 格式输出 , 
-show_format 输出封装格式信息 ,
-show_streams 输出流信息,
-i ./video/c.ts 输入文件

使用实例

基本用法

ffprobe -v error -show_format -show_streams input.mp4

输出该视频的基本信息,如果上面的命令输出结果过多,而只想要比如 size 可以

ffprobe -v error -show_entries format=size -of default=noprint_wrappers=1 input.mp4

如果只想要结果可以

ffprobe -v error -show_entries format=size -of default=noprint_wrappers=1:nokey=1 input.mp4

上面的命令中:

  • -v 参数是日志输出级别
  • error 则略去了 build 和 generic 信息,暴露 error 错误
  • -print_format 则是输出结果格式

获取视频时长

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4

直接输出视频时长。

下面是一段 shell 脚本,之前遇到过有一批视频地址,想要获取这批视频的市场,用 ffprobe 就能够完成。

while IFS='' read -r line || [[ -n "$line" ]]; do
    lineArray=($line)
    echo ${lineArray[0]}
    duration=$(ffprobe -i ${lineArray[1]} -show_entries format=duration -v quiet -of csv="p=0")
    echo $duration
    echo "${lineArray[0]} ${duration}" >> duration.txt
done < "$1"

以 json 格式输出

ffprobe -show_streams -show_entries format=bit_rate,filename,start_time:stream=duration,width,height,display_aspect_ratio,r_frame_rate,bit_rate -of json -v quiet -i 98a74a06741a091b8a42aaa31b4edc66.mp4

输出:

{
    "programs": [

    ],
    "streams": [
        {
            "width": 720,
            "height": 1280,
            "display_aspect_ratio": "0:1",
            "r_frame_rate": "30/1",
            "duration": "40.833333",
            "bit_rate": "1710937",
            "disposition": {
                "default": 1,
                "dub": 0,
                "original": 0,
                "comment": 0,
                "lyrics": 0,
                "karaoke": 0,
                "forced": 0,
                "hearing_impaired": 0,
                "visual_impaired": 0,
                "clean_effects": 0,
                "attached_pic": 0
            },
            "tags": {
                "language": "und",
                "handler_name": "VideoHandler"
            }
        },
        {
            "r_frame_rate": "0/0",
            "duration": "40.890431",
            "bit_rate": "128102",
            "disposition": {
                "default": 1,
                "dub": 0,
                "original": 0,
                "comment": 0,
                "lyrics": 0,
                "karaoke": 0,
                "forced": 0,
                "hearing_impaired": 0,
                "visual_impaired": 0,
                "clean_effects": 0,
                "attached_pic": 0
            },
            "tags": {
                "language": "und",
                "handler_name": "SoundHandler"
            }
        }
    ],
    "format": {
        "filename": "98a74a06741a091b8a42aaa31b4edc66.mp4",
        "start_time": "-0.046440",
        "bit_rate": "1065995"
    }
}

外延

mediainfo 也是一个用来获取音频视频信息的工具,比如封装格式、音视频编码格式、码率等信息。

mediainfo 可以获取的信息包括

  • General: title, author, director, album, track number, date, duration…
  • Video: codec, aspect, fps, bitrate…
  • Audio: codec, sample rate, channels, language, bitrate…
  • Text: language of subtitle
  • Chapters: number of chapters, list of chapters

mediainfo 支持的格式

  • Video: MKV, OGM, AVI, DivX, WMV, QuickTime, Real, MPEG-1, MPEG-2, MPEG-4, DVD (VOB)…
  • Video Codecs: DivX, XviD, MSMPEG4, ASP, H.264, AVC…
  • Audio: OGG, MP3, WAV, RA, AC3, DTS, AAC, M4A, AU, AIFF…
  • Subtitles: SRT, SSA, ASS, SAMI…

mediainfo 输出的字段不容易被解析,表述方法不统一。例如,对于 h264 这种编码格式,mediainfo 可能输出的表述为 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10;还比如,对于 mp3 这样的音频格式,居然会分两个字段进行描述,分别说明 mpeg 和 layer3.

reference


2015-02-09 linux , ffmpeg , ffplay , ffprobe , command

每天学习一个命令:sed 流式字符编辑器

sed 全名叫 stream editor,是面向字符流的编辑器,能够完美地配合正则表达式使用。sed 提供的功能是交互式文本编辑器的延伸,提供的查找替换程序可以被全局应用于单个文件或多个文件。

面向字符流,是因为输入流通过程序并将输出直接输出到标准输出。

sed 处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用 sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非使用重定向存储输出。sed 主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

sed 功能:

  • 主要用来自动编辑一个或多个文件;
  • 简化对文件的反复操作;
  • 编写转换程序等。

awk 的典型示例是将数据转化为格式化报表。

行编辑器 ed

awk 的起源追溯到 sed 和 grep,再往前追溯就到了 ed,最初的 UNIX 行编辑器。关于 ed 编辑器可以参考之前的『文章

sed 使用参数

sed [-neifr] [ 命令 ]

选项与参数:

  • -n :只有经过 sed 特殊处理的那一行(或者命令)才会被列出来。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。
  • -e :直接在命令列模式上进行 sed 的命令编辑;
  • -f :从文件执行 sed 命令,-f filename 则可以运行 filename 内的 sed 命令;
  • -r :sed 默认支持正则表达式,使用 -r 开启扩展的正则表达式
  • -i :直接修改读取的文件内容,而不是输出到终端。

命令说明: [n1[,n2]]command

n1, n2 :在 n1 到 n2 行之间使用命令,举例来说,如果我的命令是需要在 10 到 20 行之间进行的,则 10,20[ 命令行为 ]

command:

a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d :删除
i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p :列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s :替换,通常这个 s 的命令可以搭配正则 `1,20s/old/new/g`

实例

行删除及增加

以行为单位的新增 / 删除

/etc/passwd 的内容列出并且列印行号,同时,请将第 2~5 行删除,这里的删除是指在输出结果中删除,并不是真正去删除文件中的内容,如果要直接对文件进行修改,可以参考后文中的 -i 参数。

nl /etc/passwd | sed '2,5d'
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

说明:

  • sed 的命令为 ‘2,5d’ ,d 就是删除
  • sed 后面接的命令,请务必以 '' 两个单引号括住

只要删除第 2 行

nl /etc/passwd | sed '2d'

要删除第 3 到最后一行

nl /etc/passwd | sed '3,$d'

删除空白行

sed '/^$/d' file.txt

在第二行后(即是加在第三行)添加内容

nl /etc/passwd | sed '2a drink tea'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
drink tea
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

那如果是要在第二行前

nl /etc/passwd | sed '2i drink tea'

如果是要增加两行以上,在第二行后面加入两行字

nl /etc/passwd | sed '2a Drink tea or ......\
> drink beer ?'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
Drink tea or ......
drink beer ?
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

以行为单位替换

以行为单位的替换与显示

将第 2-5 行的内容替换为自己的内容

nl /etc/passwd | sed '2,5c No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync

另外一个比较常见的场景是,在一个文件中针对特定的行需要在行尾增加一个字符。比如

192.168.1.1 host1
192.168.1.2 host2
192.168.1.3 host3

想要在 host2 后面增加字符 host25 变成 192.168.1.2 host2 host25, 那就可以

sed '/192.168.1.2/s/$/ host25' path/to/file

验证无误之后可以直接使用 sed -i 原地替换。

显示特定行

仅列出 /etc/passwd 文件内的第 5-7 行

nl /etc/passwd | sed -n '5,7p'
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

这个 sed 的以行为单位的显示功能,就能够将某一个文件内的某些行号选择出来显示。

数据的搜寻并显示

搜索 /etc/passwd 有 root 关键字的行

nl /etc/passwd | sed '/root/p'
1  root:x:0:0:root:/root:/bin/bash
1  root:x:0:0:root:/root:/bin/bash
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3  bin:x:2:2:bin:/bin:/bin/sh
4  sys:x:3:3:sys:/dev:/bin/sh
5  sync:x:4:65534:sync:/bin:/bin/sync

如果 root 找到,除了输出所有行,还会输出匹配行。

使用-n的时候将只打印包含正则的行。

nl /etc/passwd | sed -n '/root/p'
1  root:x:0:0:root:/root:/bin/bash

输出指定的行数 (输出 2-5 行的数据)

sed -n '2,5p' file

数据搜寻删除

删除 /etc/passwd 所有包含 root 的行,其他行输出

nl /etc/passwd | sed  '/root/d'
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3  bin:x:2:2:bin:/bin:/bin/sh

搜索执行命令

搜索 /etc/passwd, 找到 root 对应的行,执行后面花括号中的一组命令,每个命令之间用分号分隔,这里把 bash 替换为 blueshell,再输出这行:

nl /etc/passwd | sed -n '/root/{s/bash/blueshell/;p}'
1  root:x:0:0:root:/root:/bin/blueshell

如果只替换 /etc/passwd 的第一个 bash 关键字为 blueshell,就退出

nl /etc/passwd | sed -n '/bash/{s/bash/blueshell/;p;q}'
1  root:x:0:0:root:/root:/bin/blueshell

最后的 q 是退出。

数据的搜寻并替换

结尾的 g 表示匹配所有的

sed 's/regex/replace/g' file.txt

假如没有结尾的 g,比如

sed 's/book/books/' file.txt

则表示匹配一个 book,并替换为 books。

多点编辑

一条 sed 命令,删除 /etc/passwd 第三行到末尾的数据,并把 bash 替换为 blueshell

nl /etc/passwd | sed -e '3,$d' -e 's/bash/blueshell/'
1  root:x:0:0:root:/root:/bin/blueshell
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh

-e表示多点编辑,第一个编辑命令删除 /etc/passwd 第三行到末尾的数据,第二条命令搜索 bash 替换为 blueshell。

直接修改文件内容

sed 可以启用 -i 选项直接修改文件的内容,不必使用管道命令或者重定向。

sed -i 's/\.$/\!/g' filename.txt         # 将文件每一行最后的 `.` 替换为 `!`
sed -i '$a # add to last' filename.txt   # 每一行后面 ($) 增加 (a) 后面的内容

sed 可以直接修改文件内容,这样对于大文本,可以不需要使用 vim 打开在进行编辑,直接使用 sed 行读取编辑就能够实现行修改和替换的作用。

过滤部分内容

利用替换可以将不需要的内容替换成空

sed -n -e 's/^.*id=//p'

可以打印 id= 后面的内容,然后再做处理。

Sed 处理 Tab

在 sed 的语法中,比如替换一行中的 Tab 到逗号,会发现

sed -i 's/\\t/,/g' some.txt

\t 其实并没有用,而是需要按下 Ctrl+v 然后输入 Tab 才有效。

sed -i 's/	/,/g' some.txt

这样才有效。

reference


2015-01-15 linux , command , sed , editor

Vim 插件之: vim-surrounding

vim-surrounding 插件可以轻松的一次性修改成对出现的,比如 (), [], {}, 双引号,XML 标签等等。提供了

  • 增加
  • 删除
  • 修改

包围内容的方法。

首先放上链接:

Installation

Plugin 'tpope/vim-surrounding'

Usage

用下面的例子做 demo

print("hello world")

光标定位在 hello world 包括引号,那么使用如下的命令可以实现双引号替换成单引号:

cs"'

change surrounding

Change surroundings is cs. 接受两个参数,目标,和替换内容

cs"'            # change " to '
cs"<q>          # change " to <q>
cs)]            # change ) to ]

如果要替换标签的内容,比如说将 h1 替换为 h2,则需要用到 t

<h1>Title</h1>

则需要 cst<h2>,同理要将 <h1> 替换成双引号,则 cst"

假如有一行内容

<h1>This is a title</h1>

cs 还有一个变种 cS,效果则是将变化的内容放到新行中。

add surrounding

给 hello 增加 <h2>

hello

那么可以使用 csw<h2>,简单记忆成 change surrounding of word <h2> ,给 word 增加 <h2> 标记

可以看到 cs 接受两个参数,会用后一个参数替换前一个。

delete surrounding

比如删除双引号,delete + surrounding + “

ds"             # delete surrounding "
ds(             # delete surrounding (
dst             # delete surrounding tags

dscs 都将 target 作为第一个参数,所有的 target (text-objects) 目前都是一个字符。

(), [], {}, <>
b, r, B, a 分别对应上面括号
', ", `
t 表示 HTML 或者 XML 标签
w, W, s 分别是 word, WORD, sentence
p 表示 paragraph

ys 给 surrounding 增加标记

给 hello 增加 <h2>

hello

使用更加复杂一点的 you surrounding inside word with <h2>

ysiw<h2>

ys 接受 vim motion 或者 text object 作为一个 object

如果要对整行操作可以使用 yss 后接修改的内容,比如给整行增加花括号

yssB

cS 一样,ys 也有变种版本 ySySS,会在新行添加内容,比如给 paragraph 添加双引号

ySS"

Visual mode

在选择模式下可以使用 S + 需要添加的内容,来看快速对选择的内容增加 surroundings。

比如我想要给下面这一行中的一部分内容,比如说 main title 增加一个 <h1> 标记。

This is the main title  sub title`

那么只需要将 This is the main title 使用 v 选中,然后按下 S<h1>Enter 回车之后前后就加上了 h1 标签。

在单行选择模式下, surroundings 会添加在行中,在 blockwise 选择模式,每一行都会 surround。

a = testa
b = testb
c = testc

加入上面的三行内容,想要给后面的内容增加双引号,那么可以使用列选选择然后 S + " 就可以快速添加。

Conclusion

Normal mode
-----------
ds  - delete a surrounding
cs  - change a surrounding
ys  - add a surrounding
yS  - add a surrounding and place the surrounded text on a new line + indent it
yss - add a surrounding to the whole line
ySs - add a surrounding to the whole line, place it on a new line + indent it
ySS - same as ySs

Visual mode
-----------
s   - in visual mode, add a surrounding
S   - in visual mode, add a surrounding but place text on new line + indent it

2015-01-04 vim , vim-plugin , vim-surrounding , tpope

jhat 使用

jhat 是 Java 的堆分析工具(Java heap Analyzes Tool),在 JDK 6u7 之后成为 JDK 标配。

用法

jhat [options] heap-dump-file

说明:

  • options 参数
  • heap-dump-file 二进制 Java 堆文件,可以使用 jmap 导出

可选参数

-stack false|true

关闭对象分配调用栈跟踪 (tracking object allocation call stack)。 如果分配位置信息在堆转储中不可用,则必须将此标志设置为 false. 默认值为 true.

-refs false|true

关闭对象引用跟踪 (tracking of references to objects)。 默认值为 true. 默认情况下,返回的指针是指向其他特定对象的对象,如反向链接或输入引用 (referrers or incoming references), 会统计 / 计算堆中的所有对象。

-port port-number

设置 jhat HTTP server 的端口号。默认值 7000.

-exclude exclude-file

指定对象查询时需要排除的数据成员列表文件 (a file that lists data members that should be excluded from the reachable objects query)。 例如,如果文件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时,引用路径涉及 java.lang.String.value 的都会被排除。

-baseline exclude-file

指定一个基准堆转储 (baseline heap dump)。 在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的 (marked as not being new). 其他对象被标记为新的 (new). 在比较两个不同的堆转储时很有用。

-debug int

设置 debug 级别。0 表示不输出调试信息。 值越大则表示输出更详细的 debug 信息。

-J< flag >

因为 jhat 命令实际上会启动一个 JVM 来执行,通过 -J 可以在启动 JVM 时传入一些启动参数。例如,-J-Xmx512m 则指定运行 jhat 的 Java 虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个 JVM 启动参数,则传入多个 -Jxxxxxx.

实例

使用如下命令获取二进制堆转储文件

jmap -dump:format=b,file=heap-dump.hprof pid

然后使用

jhat -J-Xmx1024m heap-dump.hprof

来查看和分析堆信息,然后访问本地 7000 端口即可。

jhat 中可以使用 OQL(对象查询语言)来查询,这个 OQL 也是非常庞大,如果要展开说就很多了,这里举一个例子,比如要查找字符串对象中,保存了长度大于 100 的字符串可以使用

select s from java.lang.String s where s.count > 100

关于 OQL 更多的使用方法可以网上查询。

reference

  • map jhat

2015-01-03 java , jvm , jhat , heap , tool

jmap 命令使用及内存分析

jdk 自带的命令用来 dump heap info,或者查看 ClassLoader info,等等。

命令格式

jmap [OPTION] PID

使用实例

不加任何参数

直接使用命令

jmap pid

查看 pid 内存信息。

查看堆信息

jmap -heap pid

查看堆对象信息

统计对象 count ,live 表示在使用

jamp -histo pid
jmap -histo:live pid

查看 classLoader

jmap -clstats pid

生成堆快照

jmap -dump:format=b,file=heapdump.phrof pid

hprof 二进制格式转储 Java 堆到指定 filename 的文件中,live 选项将堆中活动的对象转存。

执行的过程中为了保证 dump 的信息是可靠的,所以会暂停应用, 线上系统慎用

文件可以用 jhat 分析。

错误

在运行 jmap 的时候可能遇到如下错误:

Attaching to process ID 18078, please wait...
Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.131-b11. Target VM is 25.152-b38
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.131-b11. Target VM is 25.152-b38
    at sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:435)
    at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:305)
    at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140)
    at sun.jvm.hotspot.tools.Tool.start(Tool.java:185)
    at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
    at sun.jvm.hotspot.tools.PMap.main(PMap.java:72)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.tools.jmap.JMap.runTool(JMap.java:201)
    at sun.tools.jmap.JMap.main(JMap.java:130)

解决办法就是保证 jmap 的版本 也就是 JDK 的版本和运行的 JVM 的版本,也就是 JRE 的版本一致。

我使用 Java VisualVM GUI 来查看当前进程使用的 Java 版本,或者直接 ps 查看进程,然后再使用对应的 jmap 的版本。

要保证 jmap 运行的版本和运行的 java 进程程序使用同一个的 JRE(JDK) 的方法就是在 Linux 下使用

sudo update-alternatives --config java

来配置保证使用相同的 Java 程序。

reference


2015-01-02 jmap , jstack , jdk , jvm , java

网件 WNDR3800 刷机

进入 U-boot

路由先断电,然后按住复位键或者 WPS 键开机,保持 10S 钟左右,然后用网线连接 LAN 口和电脑,打开浏览器进 192.168.1.1,就可以进入 U-boot 控制台,进去刷写固件

操作路径 :固件更新 -> 固件 -> 选择固件文件 -> 上传 -> 更新,刷完后机器会自动重启。

固件

自行编译

或者下载他人编译好的固件。


2014-11-25 openwrt , wndr3800 , router , linux , tutorial

继续折腾 WNDR3800 之 shadowsocks

之前 也说过在 Openwrt 下使用迅雷远程下载,现在因为不想在 PC 端总是开着一个 shadowsocks 的程序,所以将 shadowsocks 安装到路由器端。然后还顺带解决一下 DNS 污染,和流量智能转发。

Shadowsocks

下载 编译好的 ipk

opkg update
opkg install libpolarssl
opkg install shadowsocks-libev_1.5.1_ar71xx.ipk

shadowsocks 安装后有三个命令,ss-local 启动 sock5 代理,ss-redir 启动透明代理,ss-tunnel 启动隧道。这里使用了 ss-local 和 ss-redir

# 编辑 vim /etc/shadowsocks.json
{
    "server":"『服务器 ipv4/ipv6 地址』",
    "server_port":8388, #服务器端口
    "local_port":1081, #本地 sock5 代理端口
    "password":"『密码』",
    "timeout":300,
    "method":"『加密方式』"
}

修改配置文件/etc/init.d/shadowsocks

START=95
SERVICE_USE_PID=1
SERVICE_WRITE_PID=1
SERVICE_DAEMONIZE=1
CONFIG=/etc/shadowsocks.json
start() {
    service_start /usr/bin/ss-local -c $CONFIG
	service_start /usr/bin/ss-redir -c $CONFIG
}
stop() {
    service_stop /usr/bin/ss-local
	service_stop /usr/bin/ss-redir
}

添加执行权限,设置开机启动

chmod +x /etc/init.d/shadowsocks
/etc/init.d/shadowsocks enable

pdnsd

opkg update
opkg install pdnsd

配置/etc/init.d/pdnsd.conf

global {
    #debug = on;
    perm_cache=1024;
    cache_dir="/var/pdnsd";
    run_as="nobody";
    server_port = 1053;   #使用 1053 作为 DNS 端口,默认是 53,一定要修改否则会跟默认 dnsmasq 冲突
    server_ip = 127.0.0.1;
    status_ctl = on;
    query_method=tcp_only;#最重要的配置,只使用 tcp 查询上级 DNS
    min_ttl=15m;
    max_ttl=1w;
    timeout=10;
}
server {
    label= "googledns"; #这个 label 随便写
    ip = 8.8.8.8;    #这里为上级 dns 的 ip 地址,要求必须支持 TCP 查询,相关说明见后文注解
    root_server = on;
    uptest = none;   #不去检测 dns 是否无效。
}

启用 pdnsd,并设置为开机启动:

/etc/init.d/pdnsd enable
/etc/init.d/pdnsd restart

完成!

dnsmasq 和 ipset

openwrt 默认安装的 dnsmasq 不支持 ipset,需要先卸载,换成 dnsmasq-full,输入一下命令查看 dnsmasq 版本,写着 no ipset 的就需要安装完整版。

dnsmasq -v

运行以下命令:

opkg update
opkg install kmod-ipt-ipset ipset ipset-dns
opkg remove dnsmasq
opkg install dnsmasq-full

dnsmasq-full 从这里 下载,设置 dnsmasq 对特定域名使用本地的 pdnsd 进行解析: 为了保持配置文件整洁,建议在 /etc/dnsmasq.conf 最后加入:

conf-dir=/etc/dnsmasq.d

然后新建 mkdir 目录 /etc/dnsmasq.d ,在里面加入一个 conf,名字任选。譬如 /etc/dnsmasq.d/fuckgfw.conf , 下面是我的文件内容,你可以按自己需要整理自己的:

#Google and Youtube
server=/.google.com/127.0.0.1#1053
server=/.google.com.hk/127.0.0.1#1053
server=/.gstatic.com/127.0.0.1#1053
server=/.ggpht.com/127.0.0.1#1053
server=/.googleusercontent.com/127.0.0.1#1053
server=/.appspot.com/127.0.0.1#1053
server=/.googlecode.com/127.0.0.1#1053
server=/.googleapis.com/127.0.0.1#1053
server=/.gmail.com/127.0.0.1#1053
server=/.google-analytics.com/127.0.0.1#1053
server=/.youtube.com/127.0.0.1#1053
server=/.googlevideo.com/127.0.0.1#1053
server=/.youtube-nocookie.com/127.0.0.1#1053
server=/.ytimg.com/127.0.0.1#1053
server=/.blogspot.com/127.0.0.1#1053
server=/.blogger.com/127.0.0.1#1053

#FaceBook
server=/.facebook.com/127.0.0.1#1053
server=/.thefacebook.com/127.0.0.1#1053
server=/.facebook.net/127.0.0.1#1053
server=/.fbcdn.net/127.0.0.1#1053
server=/.akamaihd.net/127.0.0.1#1053

#Twitter
server=/.twitter.com/127.0.0.1#1053
server=/.t.co/127.0.0.1#1053
server=/.bitly.com/127.0.0.1#1053
server=/.twimg.com/127.0.0.1#1053
server=/.tinypic.com/127.0.0.1#1053
server=/.yfrog.com/127.0.0.1#1053

#Google and Youtube
ipset=/.google.com/setmefree
ipset=/.google.com.hk/setmefree
ipset=/.gstatic.com/setmefree
ipset=/.ggpht.com/setmefree
ipset=/.googleusercontent.com/setmefree
ipset=/.appspot.com/setmefree
ipset=/.googlecode.com/setmefree
ipset=/.googleapis.com/setmefree
ipset=/.gmail.com/setmefree
ipset=/.google-analytics.com/setmefree
ipset=/.youtube.com/setmefree
ipset=/.googlevideo.com/setmefree
ipset=/.youtube-nocookie.com/setmefree
ipset=/.ytimg.com/setmefree
ipset=/.blogspot.com/setmefree
ipset=/.blogger.com/setmefree

#FaceBook
ipset=/.facebook.com/setmefree
ipset=/.thefacebook.com/setmefree
ipset=/.facebook.net/setmefree
ipset=/.fbcdn.net/setmefree
ipset=/.akamaihd.net/setmefree

#Twitter
ipset=/.twitter.com/setmefree
ipset=/.t.co/setmefree
ipset=/.bitly.com/setmefree
ipset=/.twimg.com/setmefree
ipset=/.tinypic.com/setmefree
ipset=/.yfrog.com/setmefree

#Dropbox
ipset=/.dropbox.com/setmefree

#1024
ipset=/.t66y.com/setmefree

#shadowsocks.org
ipset=/.shadowsocks.org/setmefree

#btdigg
ipset=/.btdigg.org/setmefree

#sf.net
ipset=/.sourceforge.net/setmefree

#feedly
ipset=/.feedly.com/setmefree

按照这种格式指定特定的域名走代理。 server=/google.com/127.0.0.1#1053的含义是 google.com 通过本地 1053 端口解析地址 ipset=/google.com/setmefree 的含义给 google.com 的数据包打上标记,一会配置iptables时会用到 接下来配置iptables,在/etc/firewall.user里加上两行

ipset -N setmefree iphash
iptables -t nat -A PREROUTING -p tcp -m set --match-set setmefree dst -j REDIRECT --to-port 1081

每条记录都需要跟一条 ipset 设置,不要忘了。作用是把打上了标记的数据包重定向到 ss-redir 的透明代理端口

root@OpenWrt:~# cd /usr/bin
touch shadowsocks-firewall
vi shadowsocks-firewall

修改文件内容

#!/bin/sh

#create a new chain named SHADOWSOCKS
iptables -t nat -N SHADOWSOCKS

# Ignore your shadowsocks server's addresses
# It's very IMPORTANT, just be careful.
iptables -t nat -A SHADOWSOCKS -d YOUR-SERVERS-IP-ADDRESS -j RETURN

# Ignore LANs IP address
iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 240.0.0.0/4 -j RETURN

# Ignore Asia IP address
iptables -t nat -A SHADOWSOCKS -d 1.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 14.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 27.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 36.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 39.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 42.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 49.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 58.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 59.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 60.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 61.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 101.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 103.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 106.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 110.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 111.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 112.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 113.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 114.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 115.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 116.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 117.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 118.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 119.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 120.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 121.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 122.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 123.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 124.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 125.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 126.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 169.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 175.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 180.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 182.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 183.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 202.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 203.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 210.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 211.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 218.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 219.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 220.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 221.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 222.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 223.0.0.0/8 -j RETURN

# Anything else should be redirected to shadowsocks's local port
iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 1081

# Apply the rules
iptables -t nat -A PREROUTING -p tcp -j SHADOWSOCKS

解释 iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 8024 这里的 8024 必须和 OpenWrt 路由器 /etc/shadowsocks.json里的 local_port 一样,也就是说,如果 /etc/shadowsocks.json"local_port":1081, 那这里的 8024 也要改成 1081(如果照着我上面的本地端口填了 8080,这里就改成 8080)

配置成功后,chmod +x shadowsocks-firewall给其运行权限。

运行

 /usr/bin/shadowsocks-firewall

重启 dnsmasq 和 firewall 就可以实现流量自动分流了

/etc/init.d/dnsmasq restart
/etc/init.d/firewall restart

以后只要修改dnsmasq的配置文件就可以指定更多的地址走代理

可选方案 解决 dns 污染

这里比较方便的是,通过 ChinaDNS 就可以了。

和安装 ShadowSocks 一样,可以先通过 WinSCP 上传到路由器。

opkg install ChinaDNS-C_1.0.0_ar71xx.ipk  # 安装
/etc/init.d/chinadns start                # 运行
/etc/init.d/chinadns enable               # 开机启动

reference

  1. http://hong.im/2014/07/08/use-ipset-with-shadowsocks-on-openwrt/
  2. http://www.jianshu.com/p/4800eec60516
  3. http://blog.berry10086.com/Tech/Openwrt/openwrt-shadowsocks-ipset/
  4. http://www.shuyz.com/install-shadowsocks-on-hg255d-openwrt-and-config-nat.html
  5. http://hong.im/2014/03/16/configure-an-openwrt-based-router-to-use-shadowsocks-and-redirect-foreign-traffic/

2014-11-24 shadowsocks , openwrt , knowledge , shadowsocks-libev

电子书

最近文章

  • 修复突然断电后 git 仓库 corrupt 今天在 Ubuntu 下编译项目,突然负载飙升到 140 多,然后整个系统就卡住,所有 UI 卡死,无奈之下只能对系统强制重启,不过重启之后发生了一件更严重的问题,当我访问我的项目,执行 git status 之后,显示:
  • 金融的本质 读书笔记 《金融的本质》是美联储主席[[本 伯南克]]关于美联储历史,以及在 2008 年应对金融危机手段的一本科普读物。本伯南克以非常通俗的语言讲述了美联储的历史,以及央行的职能。每一章节后面都有一个答学生的提问,所以看起来就像是一本写给学生的科普读物,读完可以对美联储是做什么的?为什么要成立美联储?以及当危机来临时美联储能够做什么?有非常详细的介绍和解释。
  • 若为自由故 读书笔记 《若为自由故:自由软件之父理查德·斯托曼传》是 [[Richard Stallman]] 的个人传记,在读完 [[自由软件 自由社会]] 的时候就把这一本书加入了待看列表。《自由软件 自由社会》中只介绍了 Stallman 关于自由软件的哲学思考,没有涉及到 Stallman 是怎么形成自己的思想的原由。而这一本传记能够更清楚的看到 Stallman 如何争取一台打印机驱动,到对争取软件自由运动的全部过程。
  • 微软新一代 CEO 萨提亚 纳德拉自传《刷新》 读书笔记 《刷新:重新发现商业与未来》是微软第三任 CEO [[萨提亚 纳德拉]] 的传记,好朋友之前推荐过一次,后来在听《无人知晓》播客的时候又再一次被提到,所以就把这一本列在了 [[20220605 21 天挑战计划]] 中。
  • 在 Proxmox VE 上使用 S.M.A.R.T 信息监控硬盘状态 自己组的 NAS,是用 Proxmox VE 做虚拟化,然后再其中安装了 OpenMediaVault 作为 NAS 系统,因为时间有些久1,所以不免有点担心硬盘的寿命,所以今天来好好了解一下 SMART 信息,并对磁盘进行一个全面的诊断,以保护数据的安全。 从零开始搭建 NAS 硬件篇 ↩