Better understanding Linux secondary dependencies solving with examples

除了man 资料外觉得是挺详细的资料,收藏!

08 Jan 2015 by David Corvoysier

A few months ago I stumbled upon a linking problem with secondary dependencies I couldn’t solved without overlinking the corresponding libraries.

I only realized today in a discussion with my friend Yann E. Morin that not only did I use the wrong solution for that particular problem, but that my understanding of the gcc linking process was not as good as I had imagined.

This blog post is to summarize what I have now understood.

There is also a small repository on github with the mentioned samples.

A few words about Linux libraries

This paragraph is only a brief summary of what is very well described in The Linux Documentation Project library howto.

Man pages for the linux linker and loader are also a good source of information.

There are three kind of libraries in Linux: static, shared and dynamically loaded (DL).

Dynamically loaded libraries are very specific to some use cases like plugins, and would deserve an article on their own. I will only focus here on static and shared libraries.

Static libraries

A static library is simply an archive of object files conventionally starting with thelib prefix and ending with the .a suffix.



Static libraries are created using the ar program:

$ ar rcs libfoobar.a foo.o bar.o

Linking a program with a static library is as simple as adding it to the link command either directly with its full path:

$ gcc -o app main.c /path/to/foobar/libfoobar.a

or indirectly using the -l/L options:

$ gcc -o app main.c -lfoobar -L/path/to/foobar

Shared libraries

A shared library is an ELF object loaded by programs when they start.

Shared libraries follow the same naming conventions as static libraries, but with the .sosuffix instead of .a.


Shared library objects need to be compiled with the -fPIC option that produces position-independent code, ie code that can be relocated in memory.

$ gcc -fPIC -c foo.c
$ gcc -fPIC -c bar.c

The gcc command to create a shared library is similar to the one used to create a program, with the addition of the -shared option.

$ gcc -shared -o foo.o bar.o

Linking against a shared library is achieved using the exact same commands as linking against a static library:

$ gcc -o app main.c


$ gcc -o app main.c -lfoobar -L/path/to/foobar

Shared libraries and undefined symbols

An ELF object maintains a table of all the symbols it uses, including symbols belonging to another ELF object that are marked as undefined.

At compilation time, the linker will try to resolve an undefined symbol by linking it either statically to code included in the overall output ELF object or dynamically to code provided by a shared library.

If an undefined symbol is found in a shared library, a DT_NEEDED entry is created for that library in the output ELF target.

The content of the DT_NEEDED field depends on the link command: – the full path to the library if the library was linked with an absolute path, – the library name otherwise (or the library soname if it was defined).

You can check the dependencies of an ELF object using the readelf command:

$ readelf -d main


$ readelf -d

When producing an executable a symbol that remains undefined after the link will raise an error: all dependencies must therefore be available to the linker in order to produce the output binary.

For historic reason, this behavior is disabled when building a shared library: you need to specify the --no-undefined (or -z defs) flag explicitly if you want errors to be raised when an undefined symbol is not resolved.

$ gcc -Wl,--no-undefined -shared -o -fPIC bar.c


$ gcc -Wl,--no-undefined -shared -o -fPIC bar.c

Note that when producing a static library, which is just an archive of object files, no actual ‘linking’ operation is performed, and undefined symbols are kept unchanged.

Library versioning and compatibility

Several versions of the same library can coexist in the system.

By conventions, two versions of the same library will use the same library name with a different version suffix that is composed of three numbers:

  • major revision,
  • minor revision,
  • build revision.


This is often referred as the library real name.

Also by convention, the library major version should be modified every time the library binary interface (ABI) is modified.

Following that convention, an executable compiled with a shared library version is theoretically able to link with another version of the same major revision.

This concept if so fundamental for expressing compatibility between programs and shared libraries that each shared library can be associated a soname, which is the library name followed by a period and the major revision:


The library soname is stored in the DT_SONAME field of the ELF shared object.

The soname has to be passed as a linker option to gcc.

$ gcc -shared -Wl,-soname, -o foo.o bar.o

As mentioned before, whenever a library defines a soname, it is that soname that is stored in the DT_NEEDED field of ELF objects linked against that library.

Solving versioned libraries dependencies at build time

As mentioned before, libraries to be linked against can be specified using a shortened name and a path:

$ gcc -o app main.c -lfoobar -L/path/to/foobar

When installing a library, the installer program will typically create a symbolic link from the library real name to its linker name to allow the linker to find the actual library file.


/usr/lib/ ->

The linker uses the following search paths to locate required shared libraries:

  • directories specified by -rpath-link options (more on that later)
  • directories specified by -rpath options (more on that later)
  • directories specified by the environment variable LD_RUN_PATH
  • directories specified by the environment variable LD_LIBRARY_PATH
  • directories specified in DT_RUNPATH or DT_RPATH of a shared library are searched for shared libraries needed by it
  • default directories, normally /lib and /usr/lib
  • directories listed inthe /etc/ file

Solving versioned shared libraries dependencies at runtime

On GNU glibc-based systems, including all Linux systems, starting up an ELF binary executable automatically causes the program loader to be loaded and run.

On Linux systems, this loader is named /lib/ (where X is a version number). This loader, in turn, finds and loads recursively all other shared libraries listed in theDT_NEEDED fields of the ELF binary.

Please note that if a soname was specified for a library when the executable was compiled, the loader will look for the soname instead of the library real name. For that reason, installation tools automatically create symbolic names from the library soname to its real name.


/usr/lib/ ->

When looking fo a specific library, if the value described in the DT_NEEDED doesn’t contain a /, the loader will consecutively look in:

  • directories specified at compilation time in the ELF object DT_RPATH (deprecated),
  • directories specified using the environment variable LD_LIBRARY_PATH,
  • directories specified at compile time in the ELF object DT_RUNPATH,
  • from the cache file /etc/, which contains a compiled list of candidate libraries previously found in the augmented library path (can be disabled at compilation time),
  • in the default path /lib, and then /usr/lib (can be disabled at compilation time).

Proper handling of secondary dependencies

As mentioned in the introduction, my issue was related to secondary dependencies, ie shared libraries dependencies that are exported from one library to a target.

Let’s imagine for instance a program main that depends on a library libbar that itself depends on a shared library libfoo.

We will use either a static libbar.a or a shared


int foo()
    return 42;


int foo();

int bar()
    return foo();


int bar();

int main(int argc, char** argv)
    return bar();

Creating the shared library

libfoo has no dependencies but the libc, so we can create it with the simplest command:

$ gcc -shared -o -fPIC foo.c

Creating the libbar.a static library

As said before, static libraries are just archives of object files, without any means to declare external dependencies.

In our case, there is therefore no explicit connection whatsoever between libbar.a and

$ gcc -c bar.c
$ ar rcs libbar.a bar.o

Creating the dynamic library

The proper way to create the shared library it by explicitly specifying it depends on libfoo:

$ gcc -shared -o -fPIC bar.c -lfoo -L$(pwd)

This will create the library with a proper DT_NEEDED entry for libfoo.

$ readelf -d
Dynamic section at offset 0xe08 contains 25 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: []
 0x0000000000000001 (NEEDED)             Shared library: []

However, since undefined symbols are not by default resolved when building a shared library, we can also create a “dumb” version without any DT_NEEDED entry:

$ gcc -shared -o -fPIC bar.c

Note that it is very unlikely that someone actually chooses to create such an incomplete library on purpose, but it may happen that by misfortune you encounter one of these beasts in binary form and still need to link against it (yeah, sh… happens !).

Creating the main executable

Linking against the libbar.a static library

As mentioned before, when linking an executable, the linker must resolve all undefined symbols before producing the output binary.

Trying to link only with libbar.a produces an error, since it has an undefined symbol and the linker has no clue where to find it:

$ gcc -o app_s main.c libbar.a
libbar.a(bar.o): In function `bar':
bar.c:(.text+0xa): undefined reference to `foo'
collect2: error: ld returned 1 exit status

Adding to the link command solves the problem:

$ gcc -o app main.c libbar.a -L$(pwd) -lfoo

You can verify that the app binary now explicitly depends on libfoo:

$ readelf -d app
Dynamic section at offset 0xe18 contains 25 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: []
 0x0000000000000001 (NEEDED)             Shared library: []

At run-time, the dynamic linker will look for, so unless you have installed it in standard directories (/lib or /usr/lib) you need to tell it where it is:

LD_LIBRARY_PATH=$(pwd) ./app

To summarize, when linking an executable against a static library, you need to specify explicitly all dependencies towards shared libraries introduced by the static library on the link command.

Note however that expressing, discovering and adding implicit static libraries dependencies is typically a feature of your build system (autotools, cmake).

Linking against the shared library

As specified in the linker documentation, when the linker encounters an input shared library it processes all its DT_NEEDED entries as secondary dependencies:

  • if the linker output is a shared relocatable ELF object (ie a shared library), it will add all DT_NEEDED entries from the input library as new DT_NEEDED entries in the output,
  • if the linker ouput is a non-shared, non-relocatable link (our case), it will automatically add the libraries listed in the DT_NEEDED of the input library on the link command line, producing an error if it can’t locate them.

So, let’s see what happens when dealing with our two shared libraries.

Linking against the “dumb” library

When trying to link an executable against the “dumb” version of, the linker encounters undefined symbols in the library itself it cannot resolve since it lacks theDT_NEEDED entry related to libfoo:

$ gcc -o app main.c -L$(pwd) -lbar_dumb undefined reference to `foo'
collect2: error: ld returned 1 exit status

Let’s see how we can solve this.

Adding explicitly the dependency

Just like we did when we linked against the static version, we can just add libfoo to the link command to solve the problem:

$ gcc -o app main.c -L$(pwd) -lbar_dumb -lfoo

It creates an explicit dependency in the app binary:

$ readelf -d app
Dynamic section at offset 0xe18 contains 25 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: []
 0x0000000000000001 (NEEDED)             Shared library: []
 0x0000000000000001 (NEEDED)             Shared library: []

Again, at runtime you may need to tell the dynamic linker where is:

$ LD_LIBRARY_PATH=$(pwd) ./app

Note that having an explicit dependency to libfoo is not quite right, since our application doesn’t use directly any symbols from libfoo. What we’ve just done here is called overlinking, and it is BAD.

Let’s imagine for instance that in the future we decide to provide a newer version oflibbar that uses the same ABI, but based on a new version of libfoo with a different ABI: we should theoretically be able to use that new version of libbar without recompiling our application, but what would really happen here is that the dynamic linker would actually try to load the two versions of libfoo at the same time, leading to unpredictable results. We would therefore need to recompile our application even if it is still compatible with the newest libbar.

As a matter of fact, this actually happened in the past: a libfreetype update in the debian distro caused 583 packages to be recompiled, with only 178 of them actually using it.

Ignoring libfoo dependency

There is another option you can use when dealing with the “dumb” library: tell the linker to ignore its undefined symbols altogether:

$ gcc -o app main.c -L$(pwd) -lbar_dumb -Wl,--allow-shlib-undefined

This will produce a binary that doesn’t declare its hidden dependencies towards libfoo:

$ readelf -d app
Dynamic section at offset 0xe18 contains 25 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: []
 0x0000000000000001 (NEEDED)             Shared library: []

This isn’t without consequences at runtime though, since the dynamic linker is now unable to resolve the executable dependencies:

$ ./app: symbol lookup error: ./ undefined symbol: foo

Your only option is then to load libfoo explicitly (yes, this is getting uglier and uglier):

$ LD_PRELOAD=$(pwd)/ LD_LIBRARY_PATH=$(pwd) ./app
Linking against the “correct” library
Doing it the right way

As mentioned before, when linking against the correct shared library, the linker encounters the DT_NEEDED entry, adds it to the link command and finds it at the path specified by -L, thus solving the undefined symbols … or at least that is what I expected:

$ gcc -o app main.c -L$(pwd) -lbar
/usr/bin/ld: warning:, needed by, not found (try using -rpath or -rpath-link)
/home/diec7483/dev/linker-example/ undefined reference to `foo'
collect2: error: ld returned 1 exit status

Why the error ? I thought I had done everything by the book !

Okay, let’s take a look at the ld man page again, looking at the -rpath-link option. This says:

When using ELF or SunOS, one shared library may require another. This happens when an “ld -shared” link includes a shared library as one of the input files. When the linker encounters such a dependency when doing a non-shared, non-relocatable link, it will automatically try to locate the required shared library and include it in the link, if it is not included explicitly. In such a case, the -rpath-link option specifies the first set of directories to search. The -rpath-link option may specify a sequence of directory names either by specifying a list of names separated by colons, or by appearing multiple times.

Ok, this is not crystal-clear, but what it actually means is that when specifying the path for a secondary dependency, you should not use -L but -rpath-link:

$ gcc -o app main.c -L$(pwd) -lbar -Wl,-rpath-link=$(pwd)

You can now verify that app depends only on libbar:

$ readelf -d app
Dynamic section at offset 0xe18 contains 25 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: []
 0x0000000000000001 (NEEDED)             Shared library: []

And this is finally how things should be done.

You may also use -rpath instead of -rpath-link but in that case the specified path will be stored in the resulting executable, which is not suitable if you plan to relocate your binaries. Tools like cmake use the -rpath during the build phase (make), but remove the specified path from the executable during the installation phase(make install).


To summarize, when linking an executable against:

  • a static library, you need to specify all dependencies towards other shared libraries this static library depends on explicitly on the link command.

  • a shared library, you don’t need to specify dependencies towards other shared libraries this shared library depends on, but you may need to specify the path to these libraries on the link command using the -rpath/-rpath-link options.

Note however that expressing, discovering and adding implicit libraries dependencies is typically a feature of your build system (autotools, cmake), as demonstrated in my samples.


要探讨memcache 的集群,那么就要先理解其特征,伪分布式。


2.伪,上面的分布式需要依靠客户端实现,这是它第一个伪,还一个是 memcache 本身做不到可靠性,因为服务端相互之间不通信,不能同步数据,所以如果某一点挂了,就该缓存就失效了,并且如果客户端的算法是直接HASH的话,可能还会 影响后续缓存的命中。


如果让客户端去实现分布式,我觉得这个会麻烦,同时而magent 这个代理就解决了memcashe上述的不足,客户端就只需要存入缓存,取缓存就够了,把一切什么麻烦的分布式,数据同步,可靠性,可用性全都交给magent 。所以一般我们需要集群方式使用memcache 的时候,一般都会增加magent 这个代理节点。当然客户端是少不了,既然提到了那也就讲一下,一般的使用memcache 的流程:

客户端(我们的应用服务) 通过memcache 提供的客户端(SDK)与memcache服务端连接,进行缓存数据的读写操作,如果需要集群,那么我们一般连接到magent,通过magent进行缓存数据读写,具体读写到哪个memcache节点,magent替我们做主,可靠性,可用性等也通过magent帮我们解决,其分布式使用.

magent wiki 给我们的简单示意图,这样memcached 就有主备,可以解决单点问题。

magent is a proxy server sitting between memcache clients(such as php programs) and memcached servers. magent have several advantages over direct connections

    |  |  |                   |  |  |
  Normal Servers Farm      Backup Servers Farm

1.mkdir magent magent/ 
4.tar zxvf magent-0.5.tar.gz 
6.sed -i “s#LIBS = -levent#LIBS = -levent -lm#g” Makefile 
8.cp magent /usr/bin/magent ../

一步步来,当然安装还是要有root 权限,有啥问题就google 吧,貌似ketama.h


#ifndef SSIZE_MAX  
#define SSIZE_MAX      32767  



memcached -m 1 -u weekcashe -d -l -p 11222

memcached -m 1 -u weekcashe -d -l -p 11223

memcached -m 1 -u weekcashe -d -l -p 11224
magent -u weekcashe -n 51200 -l -p 10000 -s -s -b

magent -u weekcashe -n 51200 -l -p 11000 -s -s -b


[weekcashe@iZ28gxqlqfsZ ~]$ telnet 10000 Trying Connected to Escape character is '^]'. set key 0 0 8 888888 STORED quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 11222 Trying Connected to Escape character is '^]'. get key VALUE key 0 8 888888 END quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 11223 Trying Connected to Escape character is '^]'. get key END quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 11224 Trying Connected to Escape character is '^]'. get key VALUE key 0 8 888888 END quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 11000 Trying Connected to Escape character is '^]'. get key VALUE key 0 8 888888 END quit Connection closed by foreign host.


[weekcashe@iZ28gxqlqfsZ ~]$ ps x
 1600 ?        Ssl    0:00 memcached -d -u weekcashe -p 11222 -c 256 -P /tmp/
 1615 ?        Ssl    0:00 memcached -d -u weekcashe -p 11223 -c 256 -P /tmp/
 1630 ?        Ssl    0:00 memcached -d -u weekcashe -p 11224 -c 256 -P /tmp/
 1658 ?        Ss     0:00 magent -u weekcashe -n 51200 -l -p 10000 -s -s -b
 1660 ?        Ss     0:00 magent -u weekcashe -n 51200 -l -p 11000 -s -s -b
 1726 pts/2    R+     0:00 ps x
[weekcashe@iZ28gxqlqfsZ ~]$ kill 1600
[weekcashe@iZ28gxqlqfsZ ~]$ telnet 11222
telnet: connect to address Connection refused

[weekcashe@iZ28gxqlqfsZ ~]$ telnet 10000
Connected to
Escape character is '^]'.
get key
VALUE key 0 8

Connection closed by foreign host.

[weekcashe@iZ28gxqlqfsZ ~]$ memcached -d -u weekcashe -p 11222 -c 256 -P /tmp/
[weekcashe@iZ28gxqlqfsZ ~]$ ps x
 1365 ?        S      0:00 sshd: weekcashe@pts/0
 1366 pts/0    Ss     0:00 -bash
 1487 ?        S      0:00 sshd: weekcashe@pts/2
 1488 pts/2    Ss     0:00 -bash
 1615 ?        Ssl    0:00 memcached -d -u weekcashe -p 11223 -c 256 -P /tmp/
 1630 ?        Ssl    0:00 memcached -d -u weekcashe -p 11224 -c 256 -P /tmp/
 1658 ?        Ss     0:00 magent -u weekcashe -n 51200 -l -p 10000 -s -s -b
 1660 ?        Ss     0:00 magent -u weekcashe -n 51200 -l -p 11000 -s -s -b
 1730 ?        Ssl    0:00 memcached -d -u weekcashe -p 11222 -c 256 -P /tmp/
 1744 pts/2    R+     0:00 ps x

[weekcashe@iZ28gxqlqfsZ ~]$ telnet 10000
Connected to
Escape character is '^]'.
get key


但是当11222被重新启动后,因为数据丢失,但是magent 未更新hash 对,依然命中到 11222去取数据,导致取不到数据。备机数据失效。这个问题留待解决。

你好通过 magent 端口 10000 写入的数据存储在了 memcashe 端口11222及备份memcashe 11224上,并且可以在 magent 11000 上获取到。并且设置获取数据magent 与直接在memcashe 端口上的接口是一致的。

OK,现在后台模拟集群的memcashe环境已经搭建完毕,通过magent 也已经解决了伪分布式带来的一些麻烦,那么接下就可以直接开始使用用客户端接口进行实际编码工作了。

xmemcached 客户端SDK使用了 NIO 机制,可以方便实现大并发。如果一般玩票性质就直接上 memcached client for java ,虽然其通信方式是传统的阻塞IO模型但足够稳定,不是超大型的已经够用。






yum -y install memcached




# memcached -h
memcached 1.4.4
-p <num>      TCP port number to listen on (default: 11211)
-U <num>      UDP port number to listen on (default: 11211, 0 is off)
-s <file>     UNIX socket path to listen on (disables network support)
-a <mask>     access mask for UNIX socket, in octal (default: 0700)
-l <ip_addr>  interface to listen on (default: INADDR_ANY, all addresses)
-d            run as a daemon
-r            maximize core file limit
-u <username> assume identity of <username> (only when run as root)
-m <num>      max memory to use for items in megabytes (default: 64 MB)
-M            return error on memory exhausted (rather than removing items)
-c <num>      max simultaneous connections (default: 1024)
-k            lock down all paged memory.  Note that there is a
              limit on how much memory you may lock.  Trying to
              allocate more than that would fail, so be sure you
              set the limit correctly for the user you started
              the daemon with (not for -u <username> user;
              under sh this is done with 'ulimit -S -l NUM_KB').
-v            verbose (print errors/warnings while in event loop)
-vv           very verbose (also print client commands/reponses)
-vvv          extremely verbose (also print internal state transitions)
-h            print this help and exit
-i            print memcached and libevent license
-P <file>     save PID in <file>, only used with -d option
-f <factor>   chunk size growth factor (default: 1.25)
-n <bytes>    minimum space allocated for key+value+flags (default: 48)
-L            Try to use large memory pages (if available). Increasing
              the memory page size could reduce the number of TLB misses
              and improve the performance. In order to get large pages
              from the OS, memcached will allocate the total item-cache
              in one large chunk.
-D <char>     Use <char> as the delimiter between key prefixes and IDs.
              This is used for per-prefix stats reporting. The default is
              ":" (colon). If this option is specified, stats collection
              is turned on automatically; if not, then it may be turned on
              by sending the "stats detail on" command to the server.
-t <num>      number of threads to use (default: 4)
-R            Maximum number of requests per event, limits the number of
              requests process for a given connection to prevent 
              starvation (default: 20)
-C            Disable use of CAS
-b            Set the backlog queue limit (default: 1024)
-B            Binding protocol - one of ascii, binary, or auto (default)
-I            Override the size of each slab page. Adjusts max item size
              (default: 1mb, min: 1k, max: 128m)


# memcached -d -u username -c 256 -P /tmp/


/etc/init.d/memcached start
chkconfig --level 2345 memcached on

启动完成后,检测下memcached 服务是否已经OK,由于我们启动使用了默认端口,查看默认配置可以到如下路劲文件去看

vi /etc/sysconfig/memcached



memcached-tool stats
#   Field       Value
         accepting_conns           1
               auth_cmds           0
             auth_errors           0
                   bytes           0
              bytes_read           7
           bytes_written           0
              cas_badval           0
                cas_hits           0
              cas_misses           0
               cmd_flush           0
                 cmd_get           0
                 cmd_set           0
             conn_yields           0
   connection_structures           6
        curr_connections           5
              curr_items           0
               decr_hits           0
             decr_misses           0
             delete_hits           0
           delete_misses           0
               evictions           0
                get_hits           0
              get_misses           0
               incr_hits           0
             incr_misses           0
          limit_maxbytes    67108864
     listen_disabled_num           0
                     pid        6322
            pointer_size          64
           rusage_system    0.000000
             rusage_user    0.000999
                 threads           4
                    time  1453353845
       total_connections           6
             total_items           0
                  uptime          19
                 version       1.4.4
telnet 11211
Connected to
Escape character is '^]'.
set foo 0 0 3
get foo
VALUE foo 0 3
Cache Results

function get_foo(foo_id)
    foo = memcached_get("foo:" . foo_id)
    return foo if defined foo

    foo = fetch_foo_from_database(foo_id)
    memcached_set("foo:" . foo_id, foo)
    return foo
kill `cat /tmp/`
/etc/init.d/memcached stop





  • 熟悉一种文本编辑器,比如Vim, Emacs, Notepad++, TextMate等。知道哪些是开源的,哪些是闭源的,哪些要收费。养成不用盗版软件的习惯。

  • 安装JDK(建议用你的Linux发行版自带的软件包管理器安装openjdk,过程中可能需要读发行版特定的文档)

  • 写一个Java的Helloworld程序,并用命令行工具javac编译,再用java命令运行这个程序。过程中熟悉源代码、字节码、虚拟机这些东西,以及Java的包(package)对.class文件所在的路径的影响。如果这两个命令行工具使用熟练了,可以开始选一个喜欢的集成开发环境,比如Eclipse。当然,养成不用盗版软件的习惯。熟悉一下如何建立“工程”,以及快捷键的使用。

  • 学习Java的面向过程编程,包括基本数据结构、表达式、语句、控制流、函数调用。

  • 学习Java的面向对象编程,包括类、引用类型和值类型的区别、成员、方法、访问控制、继承、多态、接口、接口实现。顺便学习一下面向对象的基本思想,即对象、消息、封装、继承、多态等,这些通用的内容不是Java特有的。这时候应该已经涉及了Java的垃圾回收。要留意即使有垃圾回收的情况下也会发生的内存泄露(如自己设计数组容器,元素是引用,逻辑上删除了元素,但并没有清成null)。注意垃圾回收只能回收内存中的对象,除了内存以外,其它资源不能依靠垃圾回收来关闭。比如,文件、管道、Socket、数据库连接等,垃圾回收是不会帮你关闭的。

  • 学习Java的异常处理,但更重要的是学习什么时候用特殊返回值而不使用异常,什么时候应该抛出异常而不处理异常,知道什么是pokemon catch及其危害,了解为什么Java的checked exception是一个糟糕的特性。如果愿意,同时学习一下Java1.7的try-with-resource语句和AutoCloseable接口。

  • 熟悉Java常用的数据结构,如基本的数组类型,以及泛型容器(java.util.*),尤其是java.util.List接口和java.util.ArrayList实现;以及java.util.Map接口和java.util.HashMap实现。(java1.5以前的没有泛型参数的就不用碰了)同时留意一下基本类型int, double等和装箱类型Integer和Double的区别,以及它们是如何自动转换的。

  • 熟悉Java标准库里的各种工具,包括日期时间、字符串格式化、IO等。**知道文件要自己在finally子句中close(),或者用Java1.7的try-with-resource,不要妄想垃圾回收器会帮你关掉文件。

  • 学习一下Java的命名习惯,以及JavaBeans的常规,知道为什么getter/setter比直接操作成员变量好。按这种方式给Java的变量、方法命名。同时看看你的IDE能不能自动帮你生成getter和setter。

  • 使用一个第三方的库(比如Apache Commons Lang通用工具库),让你的程序依赖于它的二进制jar包(而不是直接拷贝源代码),用命令行编译、运行(注意classpath等);也熟悉一下如何用你的集成开发环境添加第三方依赖。感受一下手动管理依赖关系的麻烦。

  • 学习Maven的使用,试着让Maven帮你解决依赖关系,再试着让Maven帮你创建一个Eclipse工程。再试试用Maven打包发布。

  • 学习软件测试,以及JUnit的使用,以及怎么在IDE中使用JUnit。有空看一下coverage工具。

  • 读读四人帮写的《设计模式》(这本书是用C++和Smalltalk语言为例子的,但仍然适合Java)。具体的是这本书,图书馆应该能还借到英文原版,因为我借到过。


  1. 关于语言

    • 如果学Java学得不舒服了,学Python。
    • 如果对面向对象编程的概念有点不习惯,学Smalltalk。(Ruby也行,但不如Smalltalk经典。Ruby的文档是一大硬伤。)
    • 如果嫌Java太啰嗦,学Python
    • 如果嫌Java太啰嗦,又想用JVM,自己又有精力,学Scala
    • 如果对对象之间的关系有点晕,学一学UML,以及它的一些图,可以对程序和运行进行直观的建模。你的IDE也许有插件可以生成UML图。但是不要太沉迷于这些方法论。
  2. 调试和辅助工具


    • 试试用jconsole或者VisualVM监控另一个jvm的状态。
    • 用profiling工具寻找程序中慢的地方。Eclipse有profiling工具。VisualVM也有这样的功能。(如果不介意使用闭源软件的话,也试试JProfiler和YourKit)
    • 有的JVM允许在运行时更新代码。Eclipse可以和某些JVM集成。这样你可以频繁修改代码而不用频繁重启JVM。对于某些“重型”工程很有用。(如果不介意使用闭源软件的话,也试试jRebel)
  3. 多线程


    • 如果还舒服,学习一下Runnable的用法,以及自带的Executer等基本多线程工具。
    • 应该已经留意到java.util里面的很多容器不是线程安全的,但是java.util.Collections可以帮你创建一些安全的版本。另外关注一下java.util.concurrent里面有ConcurrentMap等容器可供使用。
    • 如果有空的话,看看memory model(内存一致性模型)和无锁同步(见java memory model和java.util.concurrent.atomic)。
    • 如果还有空,再了解一下除了“共享内存多线程编程”以外有没有别的模型(多进程multi-processing、消息传递message passing等)。
  4. 反射、元编程

    • 学习Java的反射机制,以及Annotation的用法。
    • 如果还舒服,试试java.lang.reflect.Proxy的用法。
    • 如果仍然还舒服,玩一玩CGLib(一个第三方的库)。
  5. 网络编程


    • 如果不是很关心HTTP,看看java.nio,学习单线程轮询式IO复用(Selector)。
      1. 如果有点不明白nio的意图的话,了解一下c10k问题。
      2. 如果身体没有异样的话,大概了解一下操作系统(包括C语言)提供的select, poll, epoll, kqueue等接口。
      3. 如果身体仍然没有异样的话,试着用java.nio写一个文件服务器。
      4. 如果还有精力的话,上网扒一扒有没有其他的通信库,如netty等。
    • 如果关心Web还有HTTP,就学习一下HTTP协议,以及用Java进行HTTP的客户端编程。
      1. 如果还舒服,学学HTML,写写HTML的静态网页(不需要Java)
      2. 如果还舒服,用Java写一个基于DOM、XPath或者CSS Selector的网页解析器(爬网页)。
      3. 如果还舒服,学学Java的Servlet接口(先别学jsp)进行Web服务器端编程。学学标准的Servlet容器怎么用,包括web.xml的用法以及listener、filter等概念。以及某个Servlet容器(如Jetty或者Tomcat)的具体用法。
      4. 如果仍然还舒服,试着学一种模板语言(如haml, velocity, freemarker,【还有其他更好的框架吗?我不知道】, String.format,如果真的想学JSP的话JSP倒是也行,但不推荐)。
      5. 如果仍然觉得舒服,学学Spring框架中的Web框架,或者Struts,看你的口味。
      6. 如果还舒服,看看Spring Bean Container以及里面各种乱七八糟的工具。
      7. 如果还舒服,或者有需求,了解一下什么是RESTful Web Service,复习一下HTTP,找找适合的Java工具。
      8. 你可能会觉得Jackson是一个解析JSON用的好用的东西。
  6. 数据库


    • 可能中间会涉及“事务”问题,让你不知不觉地开始去了解java transaction api(JTA)。
    • 如果还舒服,学一学对象关系转换(如Hibernate)。
    • 也可以学学非关系数据库,以及如何用Java访问它们。
  7. 日志记录


    • 如果有精力的话,大概了解一下世界上有多少种Java日志框架,以及slf4j是怎么桥接这些框架的。
  8. 构建(build)系统


    • 如果还舒服的话,学习一下用Ivy从Maven的仓库里下载软件包,解决依赖关系。
  9. 版本控制


    • 如果感觉很舒服的话,为你们实验室搭建一个Linux+SSH+Git服务器,装个GitLab(一种Web界面)。
    • 了解“集中式版本控制器”和“分布式版本控制器”的区别,并说服同事们不要再用SVN、CVS或者SourceSafe等老旧的“集中式版本控制器”了。
    • 开设一个GitHub账户。如果你不喜欢Git,就用BitBucket等。
  10. 持续集成

    自己(或者为你们实验室)搭建一个持续集成(Continuous Integration)服务器,如Jenkins,定期编译你的程序。建议同时使用Git等分布式版本控制器。

    • 如果你做开源软件,试试GitHub和Travis。
  11. 零碎工具

    淘一淘java.nio.files里面有什么好用的东东,然后再淘一淘Apache Commons Lang和Commons IO里有什么好用的工具。Commons Logging就不要再用了,用SLF4j和Logback。

  12. XML


    • 如果觉得不舒服了,就学学JSON和YAML。
    • 如果还是不舒服,就学学文本文件解析。
  13. 语法分析和编译器

    学学Antlr或者别的Parser Generator的用法

    • 如果觉得舒服,自己写一个计算器。
    • 如果还觉得舒服,自己写一种Domain-Specific Language (DSL)。
  14. 高效容器


  15. 分布式计算


    • 如果还舒服,学学Scala语言以及号称比MapReduce快得多的Apache Spark。
  16. 进程间通信


  17. 其他语言(JVM)


  18. 其他语言(非JVM)


  19. Java语言和Java虚拟机

    通读一遍(一目十行地读,不用细读)Java Language Specification,以及Java Virtual Machine Specification。

    • 了解以下解释器(interpreter)、编译器(compiler)、即时编译器(just-in-time compiler)和优化器(optimiser)的概念。
    • 如果对编译器的话题不感到畏惧,了解一下method JIT和tracing JIT的概念和区别。
  20. 内存管理

    学学垃圾回收的几种基本算法,包括mark-sweep、mark-compact、semi-space、generational、mark-region等,各自的性能,以及为什么朴素的reference counting是不完整的。知道为什么finalizer性能很糟糕,而且标准并不要求finalizer在程序退出前一定会执行。

    • 如果还舒服,了解一下如何设置Java虚拟机的堆大小限制(如HotSpot虚拟机的-Xmx选项等)。
    • 了解一下Java里的WeakReference以及SoftReference和PhantomReference,以及它们什么时候有用,以及为什么它们实现起来有些困难。
    • 如果有精力,了解一下Hotspot虚拟机的内存管理算法是什么样的。
  21. 动态装载

    学学Java的动态装载(class loading)

    • 如果还舒服的话,学学OSGI以及它的一种实现(如Felix或者Equinox)
    • 如果仍然很舒服的话,学学写基于Eclipse平台的程序。不是Eclipse集成开发环境,只是利用他们的图形框架,写自己的应用程序。
    • 如果还觉得舒服的话,写Eclipse集成开发环境的插件。
  22. 本地/外语接口

    学习一下Java Native Interface(JNI),试着写一个Java和C语言混合编程的程序。

    • 如果觉得不舒服了或者觉得欲仙欲死,就学一学Java Native Access(JNA),试一试不用任何胶水代码而从Java直接装载C库,直接调用C函数。
    • 如果连JNA也懒得学,就学一学SWIG,自动生成绑定。
    • 如果觉得舒服,就学一学Java Virtual Machine Tooling Interface(JVMTI),用C程序来监视JVM的状态。
  23. 密码学


    • 如果觉得有点不舒服(你应该不会觉得舒服吧,除非你是学密码学的,要不然总会觉得自己写的程序有安全漏洞),就写一个“人品计算器”来放松一下,要求每次输入同一个姓名,得到的人品值是固定的,但又要让人无法用别人的人品值猜自己的人品值。
  24. 移动终端


    • 如果有精力的话,看看Dalvik虚拟机是怎么回事。
    • 建议买一个iPhone或者iPad,或许你再也不想用Android手机或者平板了。
  25. 历史


    • Applet,想想它比起html5+css3+javascript的缺点在哪里。
    • AWT、Swing,想想为什么很少有人用Java写图形界面程序。你觉得Swing的程序看上去舒服吗?中国人和残疾人喜欢用Swing程序吗?
    • JNDI,想想它比起Spring Bean Container的缺点在哪里。
    • JSP,想想它比起MVC结构的缺点在哪里。
    • WSDL/SOAP,把它们和XML-RPC、RESTful Web Service比较一下。
    • XSLT,以及为什么它是图灵完备的。可是它真的比Java本身更好用吗?
    • Log4j、java.util.logging、Apache Commons Logging,各自有什么问题,以及Log4j的作者本人为什么又开发了SLF4j和Logback?
    • Java最早是为什么设计的?
    • Type erasure是怎么回事?为什么ArrayList<int>不行但ArrayList<Integer>就可以?挖一挖历史。

其实Java不算完全面向对象的语言。Java更偏实用性,很多控制流(if语句、while循环、for循环等)来自面向过程的语言;基本数据类型(int, char, double等)也不是对象。但另一些语言,比如SmallTalk,更偏向纯粹的面向对象的设计,包括基本的数据类型都是对象,if/while/for等也用对象和方法来实现。比如:


(a + b) sqrt



(x < y) ifTrue: [ 
       max := y. 
       i := j 
     ] ifFalse: [ 
       max := x. 
       i := k 

ifTrue:ifFalse: 是一个Boolean对象的一个方法,取两个参数,每个是一个“块”,分别在真和假的情况下执行。


[i < 100] whileTrue: [ 
       sum := sum + i. 
       i := i + 1 





因为nginx处理静态页面的速度很快,并且是免费的,它还可以配置负载均衡的服务器集群来搭建多个tomcat,所以nginx+tomcat是企业搭 建javaee项目很好的选择。nginx主要是通过反向代理的方法将jsp,jspx后缀或者是javaee框架设置的特定的页面 (.do,.action)请求来交给tomcat处理,自己处理.html,.css或者是一些图片和flash。






                listen       80;#定义访问的端口号
                server_name;  #定义访问的域名
index index.html index.htm index.jsp default.html default.htm default.php;#默认的根目录访问文件
                root  /home/wwwroot/;#定义服务器访问的默认根目录
                location ~ \.(jsp|jspx|do)?$ #tomcat的访问文件后缀
include proxy.conf
                location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
                                expires      30d;
                location ~ .*\.(js|css)?$
                                expires      12h;
                access_log  /home/wwwlogs/;


这种默认的配置方法写java代码 request.getRemoteAddr()是获取不到用户访问的真实ip的。只能得到你自己服务器的ip 地址,因为nginx转发了请求。注意上面代码中我在 proxy_pass 下面配置了include proxy.conf,所以你需要在nginx目录里proxy.conf里面配置一些东西。

proxy_connect_timeout 300s;
proxy_send_timeout   900;
proxy_read_timeout   900;
proxy_buffer_size    32k;
proxy_buffers     4 32k;
proxy_busy_buffers_size 64k;
proxy_redirect     off;
proxy_hide_header  Vary;
proxy_set_header   Accept-Encoding ”;
proxy_set_header   Host   $host;
proxy_set_header   Referer $http_referer;
proxy_set_header   Cookie $http_cookie;
proxy_set_header   X-Real-IP  $remote_addr;
proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;#后端的Web服务器可以通过X-Forwarded-For获取用户真实IP


通过以上的配置nginx这一块就弄好了,你还需要更改一下tomcat的server.xml文件,把tomcat解析javaee项目的目录地 址改成和nginx的目录一致。打开tomcat/conf/server.xml文件,把Host标签appBase属性改成nginx配置里面的 root默认路径。

<Host name=”localhost”  appBase=”/home/wwwroot/”
unpackWARs=”true” autoDeploy=”true” />





#cd /usr/local

#tar zxvf apache-tomcat-6.0.18.tar.gz









6、新建文件目录/home/www为网站存放目录,设置server.xml文件,在Host name=”localhost”处将appBase=的指向路径改为/home/www/web
7、创建index.jsp至/home/www/web/ROOT,内容为:“My web!”  



#cd /usr/local

#tar zxvf  nginx-0.7.63.tar.gz


#cd nginx-0.7.63

#./configure --with-http_stub_status_module --with-http_ssl_module  #启动server状态页和https模块

执行完后会提示一个错误,说缺少PCRE library 这个是HTTP Rewrite 模块,也即是url静态化的包

#tar zxvf pcre-7.9.tar.gz

#cd pcre-7.9



#make install


#cd nginx-0.7.63



#make install


#!nginx (-) 

# proxy.conf 

proxy_redirect          off;

proxy_set_header        Host $host;

proxy_set_header        X-Real-IP $remote_addr;  #获取真实ip

#proxy_set_header       X-Forwarded-For   $proxy_add_x_forwarded_for; #获取代理者的真实ip

client_max_body_size    10m;

client_body_buffer_size 128k;

proxy_connect_timeout   90;

proxy_send_timeout      90;

proxy_read_timeout      90;

proxy_buffer_size       4k;

proxy_buffers           4 32k;

proxy_busy_buffers_size 64k;

proxy_temp_file_write_size 64k;



#user  www www; 


worker_processes 8;


error_log  /usr/local/nginx/logs/nginx_error.log  crit;

pid        /usr/local/nginx/;

#Specifies the value for maximum file descriptors that can be opened by thisprocess.

worker_rlimit_nofile 65535;




use epoll;

worker_connections 65535;






include       mime.types;

default_type  application/octet-stream;

include /usr/local/nginx/conf/proxy.conf;

#charset  gb2312;


server_names_hash_bucket_size 128;

client_header_buffer_size 32k;

large_client_header_buffers 4 32k;

client_max_body_size 8m;

sendfile on;

tcp_nopush     on;

keepalive_timeout 60;

tcp_nodelay on;

#  fastcgi_connect_timeout 300;

#  fastcgi_send_timeout 300;

#  fastcgi_read_timeout 300;

#  fastcgi_buffer_size 64k;

#  fastcgi_buffers 4 64k;

#  fastcgi_busy_buffers_size 128k;

#  fastcgi_temp_file_write_size 128k;

#  gzip on;

#  gzip_min_length  1k;

#  gzip_buffers     4 16k;

#  gzip_http_version 1.0;

#  gzip_comp_level 2;

#  gzip_types       text/plain application/x-javascript text/css application/xml;

#  gzip_vary on;

#limit_zone  crawler  $binary_remote_addr  10m;



server_name _;

return 404;




listen       80;

server_name  localhost;

index index.html index.htm index.jsp;#设定访问的默认首页地址

root  /home/www/web/ROOT;#设定网站的资源存放路径

#limit_conn   crawler  20;    

location ~ .*.jsp$ #所有jsp的页面均交由tomcat处理


index index.jsp;

proxy_pass http://localhost:8080;#转向tomcat处理


location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ #设定访问静态文件直接读取不经过tomcat


expires      30d;


location ~ .*\.(js|css)?$


expires      1h;



log_format  access  '$remote_addr - $remote_user [$time_local] "$request" '

'$status $body_bytes_sent "$http_referer" '

'"$http_user_agent" $http_x_forwarded_for';

access_log  /usr/local/nginx/logs/localhost.log access;#设定访问日志的存放路径




#/usr/local/nginx/sbin/nginx -t


the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok

  the configuration file /usr/local/nginx/conf/nginx.conf was tested successfully

如果提示unknown host,则可在服务器上执行:ping如果也是同样提示unknown host则有两种可能:




ps -ef | grep "nginx: master process" | grep -v "grep" | awk -F ' ' '{print $2}'


#/usr/local/nginx/sbin/nginx -s stop


/usr/local/nginx/sbin/nginx -t


  the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok

  the configuration file /usr/local/nginx/conf/nginx.conf was tested successfully


ps -ef | grep "nginx: master process" | grep -v "grep" | awk -F ' ' '{print $2}'


kill -HUP 6302


kill -HUP `cat /usr/local/nginx/`

9、nginx启动好后启动tomcat,此时输入http://主机ip地址即可看到“My web!”