一次Java内存溢出异常的分析过程

Posted on

一次Java内存溢出异常的分析过程

前些天,服务器上一个服务跑了一个多月突然当掉了。看了下日志,程序抛出了java.lang.OutOfMemoryError,之前也出现过同样的错误,服务跑了三个月内存溢出。出现这个异常,初步判断是程序有内存泄漏,接下来需要利用一些工具来分析具体原因。

首先使用jdk自带的工具jmap转储(dump)java内存堆数据到本地文件中。jmap转储(dump)命令格式如下:

jmap -dump:

表示dump选项,表示需要dump的java应用程序的进程ID

dump-options可以有以下三种参数,参数之间用逗号隔开:

live,只dump活动的object,如果没有指定,表示dump所有object

format=b,字节流格式

file=,是转储文件的保存路径

下面是dump命令的一个例子:

jmap -dump:format=b,file=heap.bin 8023

dump完成后,用Eclipse Memory Analyzer打开转储文件进行分析。Eclipse Memory Analyzer是一个java内存堆转储文件分析工具,可以生成内存分析报告(包括内存泄露Leak Suspects )。下面是分析完成后查看Top Consumers所看到的图表:

看到这两张图表,问题就比较明朗了。berkeley db中的数据结点com.sleepycat.je.tree.BIN占用了大量内存,导致内存溢出了。为了提高访问效率,berkeley db会缓存大量的结点,缓存大小限制可以在EnvironmentConfig设置,默认为jvm内存大小限制的60%。而这个应用程序中使用了5个EnvironmentImpl,并且都未单独设置缓存大小,总的缓存限制为jvm内存限制的300%(图表中BIN结点已经占用了94.55%的内存),远远超出java的内存限制。随着服务的运行,缓存数据越来越多,最后导致内存溢出错误。因此,为每个EnvironmentImpl设置合适的缓存大小限制,就可以避免再次发生内存溢出。 来源: [http://soft.chinabyte.com/database/475/12147475.shtml](http://soft.chinabyte.com/database/475/12147475.shtml)

Java 工具(jmap

Posted on

Java 工具(jmap,jstack)在linux上的源码分析

Java 工具(jmap,jstack)在linux上的源码分析(一)

在我们常用的Jstack, Jmap 用于分析java虚拟机的状态的工具,通过起另一个虚拟机通过运行sun.tools包下的java文件,去跟踪另一个虚拟机的状态。

如果让你设计一个跟踪另一个进程的方法,你也通常会考虑这几种常用的方式。

第一种,就是通知被跟踪的进程,让进程执行相应的消息,同时对该消息做出反应。

第二种,就是通过内核的调用,直接能够访问进程的内存,堆栈情况,通过分析被跟踪的进程的内存结构,从而知道当前被跟踪的进程的状态。

第一种方式

优势:

对调用者和被调用者只要达成简单的通讯协议,调用者无需知道被调用者的逻辑,结构,只需要简单的发送命令的方式,被调用者能够接受到命令,并且对该命令进行回应就可以。

缺点:

如果被调用者当时的状态本来就不正常,或者繁忙,没办法对该命令做出响应,那这个跟踪进程往往是在规定的等待时间里,无法返回正确的需要的信息。其次被调用者在分析的过程中,有可能需要暂停进程中的其他的线程,而对被跟踪的进程有一定的影响。

第二种方式

优势:

通过内核的支持,访问被跟踪的内存,并作出快照,后台分析,很少影响被跟踪的进程。

缺点:

这种方式需要对被跟踪程的内存分配和使用非常的了解,无法解耦,而本身系统内核调用也会出问题。

Java工具类中也是大致实现了这2中方式,工具中会先选择第一种方式,如果发现第一种方式不能成功,将会建议使用-F参数,也就是第二种方式。

我们先讲第一种方式。

既然是需要向被跟踪进程发出命令,在linux中可以选择多种方式进行进程中通讯 共享内存,文件之类,其中创建socket的文件实现通讯是比较简单的方法。

下面是整个的流程图:

来源: [http://blog.csdn.net/raintungli/article/details/7023092](http://blog.csdn.net/raintungli/article/details/7023092) Java 工具(jmap,jstack)在linux上的源码分析(二)信号处理

当java虚拟机启动的时候,会启动很多内部的线程,这些线程主要在thread.cpp里的create_vm方法体里实现

而在thread.cpp里主要起了2个线程来处理信号相关的 [cpp] view plaincopy

  1. JvmtiExport::enter_live_phase();
  2. // Signal Dispatcher needs to be started before VMInit event is posted
  3. os::signal_init();
  4. // Start Attach Listener if +StartAttachListener or it can't be started lazily
  5. if (!DisableAttachMechanism) {
  6. if (StartAttachListener || AttachListener::init_at_startup()) {
  7. AttachListener::init();
  8. }
  9. }

1. Signal Dispatcher 线程

在os.cpp中的signal_init()函数中,启动了signal dispatcher 线程,对signal dispather 线程主要是用于处理信号,等待信号并且分发处理,可以详细看signal_thread_entry的方法 [cpp] view plaincopy

  1. static void signal_thread_entry(JavaThread/* thread, TRAPS) {
  2. os::set_priority(thread, NearMaxPriority);
  3. while (true) {
  4. int sig;
  5. {
  6. // FIXME : Currently we have not decieded what should be the status
  7. // for this java thread blocked here. Once we decide about
  8. // that we should fix this.
  9. sig = os::signal_wait();
  10. }
  11. if (sig == os::sigexitnum_pd()) {
  12. // Terminate the signal thread
  13. return;
  14. }
  15. switch (sig) {
  16. case SIGBREAK: {
  17. // Check if the signal is a trigger to start the Attach Listener - in that
  18. // case don't print stack traces.
  19. if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
  20. continue;
  21. }
  22. // Print stack traces
  23. // Any SIGBREAK operations added here should make sure to flush
  24. // the output stream (e.g. tty->flush()) after output. See 4803766.
  25. // Each module also prints an extra carriage return after its output.
  26. VM_PrintThreads op;
  27. VMThread::execute(&op);
  28. VM_PrintJNI jni_op;
  29. VMThread::execute(&jni_op);
  30. VM_FindDeadlocks op1(tty);
  31. VMThread::execute(&op1);
  32. Universe::print_heap_at_SIGBREAK();
  33. if (PrintClassHistogram) {
  34. VM_GC_HeapInspection op1(gclog_or_tty, true // force full GC before heap inspection //,
  35. true // need_prologue //);
  36. VMThread::execute(&op1);
  37. }
  38. if (JvmtiExport::should_post_data_dump()) {
  39. JvmtiExport::post_data_dump();
  40. }
  41. break;
  42. }
  43. default: {
  44. // Dispatch the signal to java
  45. HandleMark hm(THREAD);
  46. klassOop k = SystemDictionary::resolve_or_null(vmSymbolHandles::sun_misc_Signal(), THREAD);
  47. KlassHandle klass (THREAD, k);
  48. if (klass.not_null()) {
  49. JavaValue result(T_VOID);
  50. JavaCallArguments args;
  51. args.push_int(sig);
  52. JavaCalls::call_static(
  53. &result,
  54. klass,
  55. vmSymbolHandles::dispatch_name(),
  56. vmSymbolHandles::int_void_signature(),
  57. &args,
  58. THREAD
  59. );
  60. }
  61. if (HAS_PENDING_EXCEPTION) {
  62. // tty is initialized early so we don't expect it to be null, but
  63. // if it is we can't risk doing an initialization that might
  64. // trigger additional out-of-memory conditions
  65. if (tty != NULL) {
  66. char klass_name[256];
  67. char tmp_sig_name[16];
  68. const char/* sig_name = "UNKNOWN";
  69. instanceKlass::cast(PENDING_EXCEPTION->klass())->
  70. name()->as_klass_external_name(klass_name, 256);
  71. if (os::exception_name(sig, tmp_sig_name, 16) != NULL)
  72. sig_name = tmp_sig_name;
  73. warning("Exception %s occurred dispatching signal %s to handler"
  74. "- the VM may need to be forcibly terminated",
  75. klass_name, sig_name );
  76. }
  77. CLEAR_PENDING_EXCEPTION;
  78. }
  79. }
  80. }
  81. }
  82. }

可以看到通过os::signal_wait();等待信号,而在linux里是通过sem_wait()来实现,接受到SIGBREAK(linux 中的QUIT)信号的时候(关于信号处理请参考笔者的另一篇博客:java 中关于信号的处理在linux下的实现),第一次通过调用 AttachListener::is_init_trigger()初始化attach listener线程,详细见2.Attach Listener 线程。

  1. 第一次收到信号,会开始初始化,当初始化成功,将会直接返回,而且不返回任何线程stack的信息(通过socket file的操作返回),并且第二次将不在需要初始化。如果初始化不成功,将直接在控制台的outputstream中打印线程栈信息。
  2. 第二次收到信号,如果已经初始化过,将直接在控制台中打印线程的栈信息。如果没有初始化,继续初始化,走和第一次相同的流程。

2. Attach Listener 线程

Attach Listener 线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。在jvm启动的时候,如果没有指定+StartAttachListener,该线程是不会启动的,刚才我们讨论到了在接受到quit信号之后,会调用 AttachListener::is_init_trigger()通过调用用AttachListener::init()启动了Attach Listener 线程,同时在不同的操作系统下初始化,在linux中 是在attachListener_Linux.cpp文件中实现的。

在linux中如果发现文件.attach_pid/#pid存在,才会启动attach listener线程,同时初始化了socket 文件,也就是通常jmap,jstack tool干的事情,先创立attach_pid/#pid文件,然后发quit信号,通过这种方式暗式的启动了Attach Listener线程(见博客:http://blog.csdn.net/raintungli/article/details/7023092)。

线程的实现在 attach_listener_thread_entry 方法体中实现 [cpp] view plaincopy

  1. static void attach_listener_thread_entry(JavaThread/* thread, TRAPS) {
  2. os::set_priority(thread, NearMaxPriority);
  3. if (AttachListener::pd_init() != 0) {
  4. return;
  5. }
  6. AttachListener::set_initialized();
  7. for (;;) {
  8. AttachOperation/* op = AttachListener::dequeue();
  9. if (op == NULL) {
  10. return; // dequeue failed or shutdown
  11. }
  12. ResourceMark rm;
  13. bufferedStream st;
  14. jint res = JNI_OK;
  15. // handle special detachall operation
  16. if (strcmp(op->name(), AttachOperation::detachall_operation_name()) == 0) {
  17. AttachListener::detachall();
  18. } else {
  19. // find the function to dispatch too
  20. AttachOperationFunctionInfo/* info = NULL;
  21. for (int i=0; funcs[i].name != NULL; i++) {
  22. const char/* name = funcs[i].name;
  23. assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");
  24. if (strcmp(op->name(), name) == 0) {
  25. info = &(funcs[i]);
  26. break;
  27. }
  28. }
  29. // check for platform dependent attach operation
  30. if (info == NULL) {
  31. info = AttachListener::pd_find_operation(op->name());
  32. }
  33. if (info != NULL) {
  34. // dispatch to the function that implements this operation
  35. res = (info->func)(op, &st);
  36. } else {
  37. st.print("Operation %s not recognized!", op->name());
  38. res = JNI_ERR;
  39. }
  40. }
  41. // operation complete - send result and output to client
  42. op->complete(res, &st);
  43. }
  44. }

在AttachListener::dequeue(); 在liunx里的实现就是监听刚才创建的socket的文件,如果有请求进来,找到请求对应的操作,调用操作得到结果并把结果写到这个socket的文件,如果你把socket的文件删除,jstack/jmap会出现错误信息 unable to open socket file:........

我们经常使用 kill -3 pid的操作打印出线程栈信息,我们可以看到具体的实现是在Signal Dispatcher 线程中完成的,因为kill -3 pid 并不会创建.attach_pid/#pid文件,所以一直初始化不成功,从而线程的栈信息被打印到控制台中。 来源: [http://blog.csdn.net/raintungli/article/details/7034005](http://blog.csdn.net/raintungli/article/details/7034005)

Java 工具(jmap,jstack)在linux上的源码分析(三)执行的线程vm thread

在前面的博客中(http://blog.csdn.net/raintungli/article/details/7034005)所提到的信号转发线程,Attach Listener 线程都只是操作socket文件,并没有去执行比如stack 分析,或者heap的分析,真正的工作线程其实是vm thread.

(一)启动vm thread [cpp] view plaincopy

  1. jint Threads::create_vm(JavaVMInitArgs/ args, bool/ canTryAgain) {
  2. ...
  3. // Create the VMThread
  4. { TraceTime timer("Start VMThread", TraceStartupTime);
  5. VMThread::create();
  6. Thread/* vmthread = VMThread::vm_thread();
  7. if (!os::create_thread(vmthread, os::vm_thread))
  8. vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");
  9. // Wait for the VM thread to become ready, and VMThread::run to initialize
  10. // Monitors can have spurious returns, must always check another state flag
  11. {
  12. MutexLocker ml(Notify_lock);
  13. os::start_thread(vmthread);
  14. while (vmthread->active_handles() == NULL) {
  15. Notify_lock->wait();
  16. }
  17. }
  18. }
  19. ...
  20. }

我们可以看到,在thread.cpp里启动了线程vm thread,在这里我们同时也稍微的略带的讲一下jvm在linux里如何启动线程的。

通常在linux中启动线程,是调用 [cpp] view plaincopy

  1. int pthread_create((pthread_t /thread, const pthread_attr_t /attr,void /(/start_routine) (void /), void /__arg));

而在java里却增加了os:create_thread --初始化线程 和os:start_thread--启动线程

我们去看一下jvm里面是如何在linux里做到的

在os_linux.cpp中来看create_thread的方法 [cpp] view plaincopy

  1. bool os::create_thread(Thread/* thread, ThreadType thr_type, size_t stack_size) {
  2. ....
  3. int ret = pthread_create(&tid, &attr, (void/ (/)(void/*)) java_start, thread);
  4. ....
  5. }

继续看java_start方法

[cpp] view plaincopy

  1. static void /java_start(Thread /thread) {
  2. ....
  3. // handshaking with parent thread
  4. {
  5. MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);
  6. // notify parent thread
  7. osthread->set_state(INITIALIZED);
  8. sync->notify_all();
  9. // wait until os::start_thread()
  10. while (osthread->get_state() == INITIALIZED) {
  11. sync->wait(Mutex::_no_safepoint_check_flag);
  12. }
  13. }
  14. // call one more level start routine
  15. thread->run();
  16. return 0;
  17. }

首先jvm先设置了当前线程的状态是Initialized, 然后notify所有的线程,

while (osthread->get_state() == INITIALIZED) { sync->wait(Mutex::_no_safepoint_check_flag); }

不停的查看线程的当前状态是不是Initialized, 如果是的话,调用了sync->wait()的方法等待。

来看os:start_thread的方法 os.cpp [cpp] view plaincopy

  1. void os::start_thread(Thread/* thread) {
  2. // guard suspend/resume
  3. MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  4. OSThread/* osthread = thread->osthread();
  5. osthread->set_state(RUNNABLE);
  6. pd_start_thread(thread);
  7. }

这时候设置了线程的状态为runnable,但没有notify线程

在 pd_start_thread(thread)中, os_linux.cpp中 [cpp] view plaincopy

  1. void os::pd_start_thread(Thread/* thread) {
  2. OSThread /* osthread = thread->osthread();
  3. assert(osthread->get_state() != INITIALIZED, "just checking");
  4. Monitor/* sync_with_child = osthread->startThread_lock();
  5. MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
  6. sync_with_child->notify();
  7. }

这时候我们看到了notify 线程的操作

也就是这时候notify了线程,因为这时候的线程的状态是RUNNABLE, 方法java_start继续往下执行,于是调用了thread->run()的方法

对于线程vm Thread 也就是调用了vmthread::run方法

vmThread.cpp [cpp] view plaincopy

  1. void VMThread::run() {
  2. ...
  3. this->loop();
  4. ...
  5. }

调用了loop函数,处理了VM_Operation 的queue 关于queue的级别和优先级处理算法:可以参考 另一篇博客:http://blog.csdn.net/raintungli/article/details/6553337

(二)Jstack 运行在vm thread里的VM_Operation

jstack 处理也就是在前面博客所提到的attach Listener 线程所做的 operation [cpp] view plaincopy

  1. static jint thread_dump(AttachOperation/ op, outputStream/ out) {
  2. bool print_concurrent_locks = false;
  3. if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {
  4. print_concurrent_locks = true;
  5. }
  6. // thread stacks
  7. VM_PrintThreads op1(out, print_concurrent_locks);
  8. VMThread::execute(&op1);
  9. // JNI global handles
  10. VM_PrintJNI op2(out);
  11. VMThread::execute(&op2);
  12. // Deadlock detection
  13. VM_FindDeadlocks op3(out);
  14. VMThread::execute(&op3);
  15. return JNI_OK;
  16. }

简单看一下类VM_PrintThreads 它 继承了VM_Operation

[cpp] view plaincopy

  1. class VM_PrintThreads: public VM_Operation {
  2. private:
  3. outputStream/* _out;
  4. bool _print_concurrent_locks;
  5. public:
  6. VM_PrintThreads() { _out = tty; _print_concurrent_locks = PrintConcurrentLocks; }
  7. VM_PrintThreads(outputStream/* out, bool print_concurrent_locks) { _out = out; _print_concurrent_locks = print_concurrent_locks; }
  8. VMOp_Type type() const { return VMOp_PrintThreads; }
  9. void doit();
  10. bool doit_prologue();
  11. void doit_epilogue();
  12. };

当调用VMThread::execute()也就是将VM_PrintThreads 放入了_vm_queue中,交给vm thread 处理,对vm thread来说取出queue里的VM_Operation,并且调用doit方法。

在jstack里,attach listener 的线程产生了VM_PrintThreads,VM_PrintJNI,VM_FindDeadlocks 3个operations,交给了vm thread 的线程处理。 来源: [http://blog.csdn.net/raintungli/article/details/7045024](http://blog.csdn.net/raintungli/article/details/7045024) Java 工具(jmap,jstack)在linux上的源码分析(四)safe point

safe point 顾明思意,就是安全点,当需要jvm做一些操作的时候,需要把当前正在运行的线程进入一个安全点的状态(也可以说停止状态),这样才能做一些安全的操作,比如线程的dump,堆栈的信息。

在jvm里面通常vm_thread(我们一直在谈论的做一些属于vm 份内事情的线程) 和cms_thread(内存回收的线程)做的操作,是需要将其他的线程通过调用SafepointSynchronize::begin 和 SafepointSynchronize:end来实现让其他的线程进入或者退出safe point 的状态。

通常safepoint 的有三种状态

_not_synchronized 说明没有任何打断现在所有线程运行的操作,也就是vm thread, cms thread 没有接到操作的指令 _synchronizing vm thread,cms thread 接到操作指令,正在等待所有线程进入safe point _synchronized 所有线程进入safe point, vm thread, cms thread 可以开始指令操作

Java线程的状态

通常在java 进程中的Java 的线程有几个不同的状态,如何让这些线程进入safepoint 的状态中,jvm是采用不同的方式

a. 正在解释执行

由于java是解释性语言,而线程在解释java 字节码的时候,需要dispatch table,记录方法地址进行跳转的,那么这样让线程进入停止状态就比较容易了,只要替换掉dispatch table 就可以了,让线程知道当前进入softpoint 状态。

java里会设置3个DispatchTable, _active_table, _normal_table, _safept_table

_active_table 正在解释运行的线程使用的dispatch table

_normal_table 就是正常运行的初始化的dispatch table

_safept_table safe point需要的dispatch table

解释运行的线程一直都在使用_active_table,关键处就是在进入saftpoint 的时候,用_safept_table替换_active_table, 在退出saftpoint 的时候,使用_normal_table来替换_active_table

具体实现可以查看源码 [cpp] view plaincopy

  1. void TemplateInterpreter::notice_safepoints() {
  2. if (!_notice_safepoints) {
  3. // switch to safepoint dispatch table
  4. _notice_safepoints = true;
  5. copy_table((address/)&_safept_table, (address/)&_active_table, sizeof(_active_table) / sizeof(address));
  6. }
  7. }
  8. // switch from the dispatch table which notices safepoints back to the
  9. // normal dispatch table. So that we can notice single stepping points,
  10. // keep the safepoint dispatch table if we are single stepping in JVMTI.
  11. // Note that the should_post_single_step test is exactly as fast as the
  12. // JvmtiExport::_enabled test and covers both cases.
  13. void TemplateInterpreter::ignore_safepoints() {
  14. if (_notice_safepoints) {
  15. if (!JvmtiExport::should_post_single_step()) {
  16. // switch to normal dispatch table
  17. _notice_safepoints = false;
  18. copy_table((address/)&_normal_table, (address/)&_active_table, sizeof(_active_table) / sizeof(address));
  19. }
  20. }
  21. }

b. 运行在native code

如果线程运行在native code的时候,vm thread 是不需要等待线程执行完的,只需要在从native code 返回的时候去判断一下 _state 的状态就可以了。

在方法体里就是前面博客也出现过的 SafepointSynchronize::do_call_back() [cpp] view plaincopy

  1. inline static bool do_call_back() {
  2. return (_state != _not_synchronized);
  3. }

判断了_state 不是_not_synchronized状态

为了能让线程从native code 回到java 的时候为了能读到/设置正确线程的状态,通常的解决方法使用memory barrier,java 使用OrderAccess::fence(); 在汇编里使用asm volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory"); 保证从内存里读到正确的值,但是这种方法严重影响系统的性能,于是java使用了每个线程都有独立的内存页来设置状态。通过使用使用参数-XX:+UseMembar 参数使用memory barrier,默认是不打开的,也就是使用独立的内存页来设置状态。

c. 运行编译的代码

1. Poling page 页面

Poling page是在jvm初始化启动的时候会初始化的一个单独的内存页面,这个页面是让运行的编译过的代码的线程进入停止状态的关键。

在linux里面使用了mmap初始化,源码如下 [cpp] view plaincopy

  1. address polling_page = (address) ::mmap(NULL, Linux::page_size(), PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

  1. 编译

java 的JIT 会直接编译一些热门的源码到机器码,直接执行而不需要在解释执行从而提高效率,在编译的代码中,当函数或者方法块返回的时候会去访问一个内存poling页面.

x86架构下 [cpp] view plaincopy

  1. void LIR_Assembler::return_op(LIR_Opr result) {
  2. assert(result->is_illegal() || !result->is_single_cpu() || result->as_register() == rax, "word returns are in rax,");
  3. if (!result->is_illegal() && result->is_float_kind() && !result->is_xmm_register()) {
  4. assert(result->fpu() == 0, "result must already be on TOS");
  5. }
  6. // Pop the stack before the safepoint code
  7. __ remove_frame(initial_frame_size_in_bytes());
  8. bool result_is_oop = result->is_valid() ? result->is_oop() : false;
  9. // Note: we do not need to round double result; float result has the right precision
  10. // the poll sets the condition code, but no data registers
  11. AddressLiteral polling_page(os::get_polling_page() + (SafepointPollOffset % os::vm_page_size()),
  12. relocInfo::poll_return_type);
  13. // NOTE: the requires that the polling page be reachable else the reloc
  14. // goes to the movq that loads the address and not the faulting instruction
  15. // which breaks the signal handler code
  16. __ test32(rax, polling_page);
  17. __ ret(0);
  18. }

在前面提到的SafepointSynchronize::begin 函数源码中

[cpp] view plaincopy

  1. if (UseCompilerSafepoints && DeferPollingPageLoopCount < 0) {
  2. // Make polling safepoint aware
  3. guarantee (PageArmed == 0, "invariant") ;
  4. PageArmed = 1 ;
  5. os::make_polling_page_unreadable();
  6. }

这里提到了2个参数 UseCompilerSafepoints 和 DeferPollingPageLoopCount ,在默认的情况下这2个参数是true和-1

函数体将会调用os:make_polling_page_unreadable();在linux os 下具体实现是调用了mprotect(bottom,size,prot) 使polling 内存页变成不可读。

3. 信号

到当编译好的程序尝试在去访问这个不可读的polling页面的时候,在系统级别会产生一个错误信号SIGSEGV, 可以参考笔者的一篇博客中曾经讲过java 的信号处理,可以知道信号SIGSEGV的处理函数在x86体系下见下源码: [cpp] view plaincopy

  1. JVM_handle_linux_signal(int sig,
  2. siginfo_t/* info,
  3. void/* ucVoid,
  4. int abort_if_unrecognized){
  5. ....
  6. if (sig == SIGSEGV && os::is_poll_address((address)info->si_addr)) {
  7. stub = SharedRuntime::get_poll_stub(pc);
  8. }
  9. ....
  10. }

在linux x86,64 bit的体系中,poll stub 的地址 就是 SafepointSynchronize::handle_polling_page_exception 详细程序可见shareRuntime_x86_64.cpp

回到safepoint.cpp中,SafepointSynchronize::handle_polling_page_exception通过取出线程的safepoint_stat,调用函数void ThreadSafepointState::handle_polling_page_exception,最后通过调用SafepointSynchronize::block(thread()); 来block当前线程。

d. block 状态

当线程进入block状态的时候,继续保持block状态。 来源: [http://blog.csdn.net/raintungli/article/details/7162468](http://blog.csdn.net/raintungli/article/details/7162468)Java 工具(jmap,jstack)在linux上的源码分析(五) -F 参数的bug

当使用jmap,jstack是用-F参数的时候,是通过调用系统调用ptrace来取的寄存器的信息,关于linux下的ptrace实现可以参考我的博客(http://blog.csdn.net/raintungli/article/details/6563867

在jdk6u23版本之前你会发现,当你使用jstack -F的时候 经常在logger 里面 看到错误信息,直接抛出异常,根本无法看到堆栈信息。

Thread 26724: (state = BLOCKED) Error occurred during stack walking: sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: get_thread_regs failed for a lwp at sun.jvm.hotspot.debugger.linux.LinuxDebuggerLocal$LinuxDebuggerLocalWorkerThread.execute(LinuxDebuggerLocal.java:152) at sun.jvm.hotspot.debugger.....

通过查看源码,最后调用的函数是process_get_lwp_regs /ps_proc.c

[cpp] view plaincopy

  1. static bool process_get_lwp_regs(struct ps_prochandle/ ph, pid_t pid, struct user_regs_struct /user) {
  2. // we have already attached to all thread 'pid's, just use ptrace call
  3. // to get regset now. Note that we don't cache regset upfront for processes.
  4. // Linux on x86 and sparc are different. On x86 ptrace(PTRACE_GETREGS, ...)
  5. // uses pointer from 4th argument and ignores 3rd argument. On sparc it uses
  6. // pointer from 3rd argument and ignores 4th argument
  7. /#if defined(sparc) || defined(sparcv9)
  8. /#define ptrace_getregs(request, pid, addr, data) ptrace(request, pid, addr, data)
  9. /#else
  10. /#define ptrace_getregs(request, pid, addr, data) ptrace(request, pid, data, addr)
  11. /#endif
  12. /#ifdef _LP64
  13. /#ifdef PTRACE_GETREGS64
  14. /#define PTRACE_GETREGS_REQ PTRACE_GETREGS64
  15. /#endif
  16. /#else
  17. /#if defined(PTRACE_GETREGS) || defined(PT_GETREGS)
  18. /#define PTRACE_GETREGS_REQ PTRACE_GETREGS
  19. /#endif
  20. /#endif // _LP64 //
  21. /#ifdef PTRACE_GETREGS_REQ
  22. if (ptrace_getregs(PTRACE_GETREGS_REQ, pid, user, NULL) < 0) {
  23. print_debug("ptrace(PTRACE_GETREGS, ...) failed for lwp %d\n", pid);
  24. return false;
  25. }
  26. return true;
  27. /#else
  28. print_debug("ptrace(PTRACE_GETREGS, ...) not supported\n");
  29. return false;
  30. /#endif
  31. }

无法判断究竟是否是因为没有定义参数PTRACE_GETREGS_REQ,还是因为ptrace的调用参数错误所导致的,这样就必须打开print_debug,查看打印的信息。

通过源码,可以查到print_debug函数是通过环境变量LIBSAPROC_DEBUG来控制

设置

export LIBSAPROC_DEBUG=1

运行

jstack -F processid

我们能看到错误中多了一行

[cpp] view plaincopy

  1. Thread 26724: (state = BLOCKED)
  2. libsaproc DEBUG: ptrace(PTRACE_GETREGS, ...) not supported

产生的原因就非常清楚了,bug主要是因为宏定义PTRACE_GETREGS_REQ缺失,查看源码

[cpp] view plaincopy

  1. /#ifdef _LP64
  2. /#ifdef PTRACE_GETREGS64
  3. /#define PTRACE_GETREGS_REQ PTRACE_GETREGS64
  4. /#endif
  5. /#else
  6. /#if defined(PTRACE_GETREGS) || defined(PT_GETREGS)
  7. /#define PTRACE_GETREGS_REQ PTRACE_GETREGS
  8. /#endif
  9. /#endif // _LP64 //

_LP64 是64位机器的宏定义,而对ptrace的参数PTRACE_GETREGS64,显然Linux kernel 2.6.35里面并没有支持,导致了没有宏定义PTRACE_GETREGS_REQ,这里明显是jvm没有考虑到的情况。

解决办法

a. 因为这是jvm 编译级别的bug,除非你重现修改编译libsaproc.so,覆盖目录/jdk1.6.0_23/jre/lib/amd64

笔者自己编译了这个lib,可以在csdn上下载(http://download.csdn.net/detail/raintungli/4065304),笔者编译的jdk版本是1.6.23 build(19.0)

b. 建议升级jvm到1.6.30版本,该版本已经测试过,已经修复该bug.

后话:

[cpp] view plaincopy

  1. if (ptrace_getregs(PTRACE_GETREGS_REQ, pid, user, NULL) < 0) {
  2. print_debug("ptrace(PTRACE_GETREGS, ...) failed for lwp %d\n", pid);
  3. return false;
  4. }

jvm可以在此处更清楚点,不是简单的判断<0,而是在判断<0的时候把errno打印出来,能更容易的判断出是什么原因无法ptrace 上。

来源: [http://blog.csdn.net/raintungli/article/details/7245709](http://blog.csdn.net/raintungli/article/details/7245709)Java 工具(jmap,jstack)在linux上的源码分析(六) -F 参数 读取动态链接共享库文件中的符号表

通常我们使用jmap,jstack 去检查堆栈信息的时候,是不会使用-f参数的,但有的时候系统在无法打印出堆栈信息的时候,会建议你使用参数-F。

关于-F参数与非-F参数的区别笔者已经在前面的博客中讲述(http://blog.csdn.net/raintungli/article/details/7023092),简单的说也就是一种是让jvm进程自己打印出堆栈信息,另有一种是直接访问jvm的堆栈区通过固定的结构找出我们需要的信息。

1. Linux-F参数的实现

在linux中可以使用ptrace的系统调用去访问运行中的进程的内存信息,具体如何实现可以参考笔者的博客(http://blog.csdn.net/raintungli/article/details/6563867

在java中使用动态加载的方式加载jvm自己的链接共享库,jvm的核心链接共享库是libjvm.so,linux中如何动态加载可以参考(http://www.ibm.com/developerworks/cn/linux/l-dynamic-libraries//#dynamiclinking

因为是动态共享库,当想查找具体的参数的值,内存的信息的时候,就需要计算出正确的参数或者函数的地址。

2. 共享库中的符号相对地址偏移

可运行程序,共享库使用ELF格式,当运行一个程序的时候,内核会把ELF加载到用户空间,里面记录了程序的函数和数据的地址和内容,elf文件格式就不具体描述了。

在linux 中可以使用结构体ELF_EHDR,ELF_PHDR,ELF_SHDR读出elf 的program header, section header, section data.

在jvm中源码具体实现请参考 /hotspot/agent/os/linux/salibelf.c

在linux中本身就自带一个读取elf格式的工具,readelf 你可以使用不同的参数读取不同的内容。

[cpp] view plaincopy

  1. readelf -s libjvm.so

显示共享库中的方法参数的虚拟地址,类型,名字

[cpp] view plaincopy

  1. readelf -l libjvm.so

读取program headers,其中出现2个LOAD的类型,第一个是程序的指令虚拟的起始地址,另一个是程序数据的起始地址

通过2个地址我们就能找到共享库中的参数,函数的相对地址的偏移

3. 进程中的符号地址

在第二章节中,得到的只是相对的地址偏移,并不是真实运行中的进程的符号地址,如何得到真实的地址在linux中就相对比较简单。

[cpp] view plaincopy

  1. cat /proc/$processid/maps
    在maps里详细记录了进程的堆栈分配的地址,包括共享库的地址,那么起始地址就是这个库分配的最小地址 [cpp] view plaincopy

  2. 进程中共享库分配的最小地址+相对地址的偏移 =真实的进程中该函数或变量的真实地址

4. Java tool 保存的符号表

在jmap/jstack 中,为了提高读取符号地址的性能,避免每一次要找符号的地址从elf文件中查找,只是在初始话的时候将符号表保存成哈希表,其中key是符号的名字,内容是符号的地址,长度。

具体实现可以参考 /hotspot/src/os/linux/symtab.c build_symtab_internal 函数

来源: [http://blog.csdn.net/raintungli/article/details/7289639](http://blog.csdn.net/raintungli/article/details/7289639)

jstack命令(Java Stack Trace)

Posted on

jstack命令(Java Stack Trace)

JDK内置工具使用

一、javah命令(C Header and Stub File Generator)

二、jps命令(Java Virtual Machine Process Status Tool)

三、jstack命令(Java Stack Trace)

四、jstat命令(Java Virtual Machine Statistics Monitoring Tool)

五、jmap命令(Java Memory Map)

六、jinfo命令(Java Configuration Info)

七、jconsole命令(Java Monitoring and Management Console)

八、jvisualvm命令(Java Virtual Machine Monitoring, Troubleshooting, and Profiling Tool)

九、jhat命令(Java Heap Analyse Tool)

十、Jdb命令(The Java Debugger)

1、介绍

jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式:

jstack [-l] pid

如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。

2、命令格式 jstack [ option ] pid jstack [ option ] executable core jstack [ option ] [server-id@]remote-hostname-or-IP

3、常用参数说明

1)、options:

executable Java executable from which the core dump was produced.

(可能是产生core dump的java可执行程序)

core 将被打印信息的core dump文件

remote-hostname-or-IP 远程debug服务的主机名或ip

server-id 唯一id,假如一台主机上多个远程debug服务

2)、基本参数:

-F当’jstack [-l] pid’没有相应的时候强制打印栈信息

-l长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.

-m打印java和native c/c++框架的所有栈信息.

-h | -help打印帮助信息

pid 需要被打印配置信息的java进程id,可以用jps查询.

4、使用示例

来源: [http://blog.csdn.net/fenglibing/article/details/6411940](http://blog.csdn.net/fenglibing/article/details/6411940)

Jackson 框架,轻易转换JSON

Posted on

Jackson 框架,轻易转换JSON

hoojo 学习在于积累:滴水可以石穿! 学而不思则罔,思而不学则殆! 博客园 首页 博问 闪存 新随笔 联系 订阅订阅 管理

随笔-139 评论-674 文章-3 trackbacks-0

Jackson 框架,轻易转换JSON

Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json、xml转换成Java对象。

前面有介绍过json-lib这个框架,在线博文:http://www.cnblogs.com/hoojo/archive/2011/04/21/2023805.html

相比json-lib框架,Jackson所依赖的jar包较少,简单易用并且性能也要相对高些。而且Jackson社区相对比较活跃,更新速度也比较快。

一、**准备工作**

1、 下载依赖库jar包

Jackson的jar all下载地址:http://jackson.codehaus.org/1.7.6/jackson-all-1.7.6.jar

然后在工程中导入这个jar包即可开始工作

官方示例:http://wiki.fasterxml.com/JacksonInFiveMinutes

因为下面的程序是用junit测试用例运行的,所以还得添加junit的jar包。版本是junit-4.2.8

如果你需要转换xml,那么还需要stax2-api.jar

2、 测试类基本代码如下 package com.hoo.test; import java.io.IOException;import java.io.StringWriter;import java.util.ArrayList;import java.util.HashMap;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import java.util.Set;import org.codehaus.jackson.JsonEncoding;import org.codehaus.jackson.JsonGenerationException;import org.codehaus.jackson.JsonGenerator;import org.codehaus.jackson.JsonParseException;import org.codehaus.jackson.map.JsonMappingException;import org.codehaus.jackson.map.ObjectMapper;import org.codehaus.jackson.node.JsonNodeFactory;import org.codehaus.jackson.xml.XmlMapper;import org.junit.After;import org.junit.Before;import org.junit.Test;import com.hoo.entity.AccountBean; //// function:Jackson 将java对象转换成JSON字符串,也可以将JSON字符串转换成java对象/ jar-lib-version: jackson-all-1.6.2/ jettison-1.0.1/ @author hoojo/ @createDate 2010-11-23 下午04:54:53/ @file JacksonTest.java/ @package com.hoo.test/ @project Spring3/ @blog http://blog.csdn.net/IBM_hoojo/ @email hoojo@126.com/ @version 1.0//@SuppressWarnings("unchecked")public class JacksonTest {private JsonGenerator jsonGenerator = null;private ObjectMapper objectMapper = null;private AccountBean bean = null;@Beforepublic void init() {bean = new AccountBean();bean.setAddress("china-Guangzhou");bean.setEmail("hoojo@126.com");bean.setId(1);bean.setName("hoojo");objectMapper = new ObjectMapper();try {jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(System.out, JsonEncoding.UTF8);} catch (IOException e) {e.printStackTrace();}}@Afterpublic void destory() {try {if (jsonGenerator != null) {jsonGenerator.flush();}if (!jsonGenerator.isClosed()) {jsonGenerator.close();}jsonGenerator = null;objectMapper = null;bean = null;System.gc();} catch (IOException e) {e.printStackTrace();}}}

3、 所需要的JavaEntity

package com.hoo.entity; public class AccountBean {private int id;private String name;private String email;private String address;private Birthday birthday;//getter、setter@Overridepublic String toString() {return this.name + "/#" + this.id + "/#" + this.address + "/#" + this.birthday + "/#" + this.email;}}

Birthday

package com.hoo.entity; public class Birthday {private String birthday;public Birthday(String birthday) {super();this.birthday = birthday;} //getter、setter public Birthday() {}@Overridepublic String toString() {return this.birthday;}}

二、**Java对象转换成JSON**

1、 JavaBean(Entity/Model)转换成JSON //// function:将java对象转换成json字符串/ @author hoojo/ @createDate 2010-11-23 下午06:01:10//@Testpublic void writeEntityJSON() {try {System.out.println("jsonGenerator");//writeObject可以转换java对象,eg:JavaBean/Map/List/Array等jsonGenerator.writeObject(bean);System.out.println();System.out.println("ObjectMapper");//writeValue具有和writeObject相同的功能objectMapper.writeValue(System.out, bean);} catch (IOException e) {e.printStackTrace();}}

运行后结果如下:

jsonGenerator{"address":"china-Guangzhou","name":"hoojo","id":1,"birthday":null,"email":"hoojo@126.com"}ObjectMapper{"address":"china-Guangzhou","name":"hoojo","id":1,"birthday":null,"email":"hoojo@126.com"}

上面分别利用JsonGenerator的writeObject方法和ObjectMapper的writeValue方法完成对Java对象的转换,二者传递的参数及构造的方式不同;JsonGenerator的创建依赖于ObjectMapper对象。也就是说如果你要使用JsonGenerator来转换JSON,那么你必须创建一个ObjectMapper。但是你用ObjectMapper来转换JSON,则不需要JSONGenerator。

objectMapper的writeValue方法可以将一个Java对象转换成JSON。这个方法的参数一,需要提供一个输出流,转换后可以通过这个流来输出转换后的内容。或是提供一个File,将转换后的内容写入到File中。当然,这个参数也可以接收一个JSONGenerator,然后通过JSONGenerator来输出转换后的信息。第二个参数是将要被转换的Java对象。如果用三个参数的方法,那么是一个Config。这个config可以提供一些转换时的规则,过指定的Java对象的某些属性进行过滤或转换等。

2、 将Map集合转换成Json字符串 //// function:将map转换成json字符串/ @author hoojo/ @createDate 2010-11-23 下午06:05:26//@Testpublic void writeMapJSON() {try {Map map = new HashMap();map.put("name", bean.getName());map.put("account", bean);bean = new AccountBean();bean.setAddress("china-Beijin");bean.setEmail("hoojo@qq.com");map.put("account2", bean);System.out.println("jsonGenerator");jsonGenerator.writeObject(map);System.out.println("");System.out.println("objectMapper");objectMapper.writeValue(System.out, map);} catch (IOException e) {e.printStackTrace();}}

转换后结果如下:

jsonGenerator{"account2":{"address":"china-Beijin","name":null,"id":0,"birthday":null,"email":"hoojo@qq.com"},"name":"hoojo","account":{"address":"china-Guangzhou","name":"hoojo","id":1,"birthday":null,"email":"hoojo@126.com"}}objectMapper{"account2":{"address":"china-Beijin","name":null,"id":0,"birthday":null,"email":"hoojo@qq.com"},"name":"hoojo","account":{"address":"china-Guangzhou","name":"hoojo","id":1,"birthday":null,"email":"hoojo@126.com"}}

3、 将List集合转换成json

//// function:将list集合转换成json字符串/ @author hoojo/ @createDate 2010-11-23 下午06:05:59//@Testpublic void writeListJSON() {try {List list = new ArrayList();list.add(bean);bean = new AccountBean();bean.setId(2);bean.setAddress("address2");bean.setEmail("email2");bean.setName("haha2");list.add(bean);System.out.println("jsonGenerator");//list转换成JSON字符串jsonGenerator.writeObject(list);System.out.println();System.out.println("ObjectMapper");//用objectMapper直接返回list转换成的JSON字符串System.out.println("1/#/#/#" + objectMapper.writeValueAsString(list));System.out.print("2/#/#/#");//objectMapper list转换成JSON字符串objectMapper.writeValue(System.out, list);} catch (IOException e) {e.printStackTrace();}}

结果如下:

jsonGenerator[{"address":"china-Guangzhou","name":"hoojo","id":1,"birthday":null,"email":"hoojo@126.com"},{"address":"address2","name":"haha2","id":2,"birthday":null,"email":"email2"}]ObjectMapper1/#/#/#[{"address":"china-Guangzhou","name":"hoojo","id":1,"birthday":null,"email":"hoojo@126.com"},{"address":"address2","name":"haha2","id":2,"birthday":null,"email":"email2"}]2/#/#/#[{"address":"china-Guangzhou","name":"hoojo","id":1,"birthday":null,"email":"hoojo_@126.com"},{"address":"address2","name":"haha2","id":2,"birthday":null,"email":"email2"}]

外面就是多了个[]中括号;同样Array也可以转换,转换的JSON和上面的结果是一样的,这里就不再转换了。~.~

4、下面来看看jackson提供的一些类型,用这些类型完成json转换;如果你使用这些类型转换JSON的话,那么你即使没有JavaBean(Entity)也可以完成复杂的Java类型的JSON转换。下面用到这些类型构建一个复杂的Java对象,并完成JSON转换。 @Testpublic void writeOthersJSON() {try {String[] arr = { "a", "b", "c" };System.out.println("jsonGenerator");String str = "hello world jackson!";//bytejsonGenerator.writeBinary(str.getBytes());//booleanjsonGenerator.writeBoolean(true);//nulljsonGenerator.writeNull();//floatjsonGenerator.writeNumber(2.2f);//charjsonGenerator.writeRaw("c");//StringjsonGenerator.writeRaw(str, 5, 10);//StringjsonGenerator.writeRawValue(str, 5, 5);//StringjsonGenerator.writeString(str);jsonGenerator.writeTree(JsonNodeFactory.instance.POJONode(str));System.out.println();//ObjectjsonGenerator.writeStartObject();//{jsonGenerator.writeObjectFieldStart("user");//user:{jsonGenerator.writeStringField("name", "jackson");//name:jacksonjsonGenerator.writeBooleanField("sex", true);//sex:truejsonGenerator.writeNumberField("age", 22);//age:22jsonGenerator.writeEndObject();//}jsonGenerator.writeArrayFieldStart("infos");//infos:[jsonGenerator.writeNumber(22);//22jsonGenerator.writeString("this is array");//this is arrayjsonGenerator.writeEndArray();//]jsonGenerator.writeEndObject();//}AccountBean bean = new AccountBean();bean.setAddress("address");bean.setEmail("email");bean.setId(1);bean.setName("haha");//complex ObjectjsonGenerator.writeStartObject();//{jsonGenerator.writeObjectField("user", bean);//user:{bean}jsonGenerator.writeObjectField("infos", arr);//infos:[array]jsonGenerator.writeEndObject();//}} catch (Exception e) {e.printStackTrace();}}

运行后,结果如下:

jsonGenerator"aGVsbG8gd29ybGQgamFja3NvbiE=" true null 2.2c world jac worl "hello world jackson!" "hello world jackson!"{"user":{"name":"jackson","sex":true,"age":22},"infos":[22,"this is array"]}{"user":{"address":"address","name":"haha","id":1,"birthday":null,"email":"email"},"infos":["a","b","c"]}

怎么样?构造的json字符串和输出的结果是一致的吧。关键看懂用JSONGenerator提供的方法,完成一个Object的构建。

三、**JSON转换成Java对象**

1、 将json字符串转换成JavaBean对象 @Testpublic void readJson2Entity() {String json = "{\"address\":\"address\",\"name\":\"haha\",\"id\":1,\"email\":\"email\"}";try {AccountBean acc = objectMapper.readValue(json, AccountBean.class);System.out.println(acc.getName());System.out.println(acc);} catch (JsonParseException e) {e.printStackTrace();} catch (JsonMappingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

很简单,用到了ObjectMapper这个对象的readValue这个方法,这个方法需要提供2个参数。第一个参数就是解析的JSON字符串,第二个参数是即将将这个JSON解析吃什么Java对象,Java对象的类型。当然,还有其他相同签名方法,如果你有兴趣可以一一尝试使用方法,当然使用的方法和当前使用的方法大同小异。运行后,结果如下:

hahahaha/#1/#address/#null/#email

2、 将json字符串转换成List集合

//// function:json字符串转换成list/ @author hoojo/ @createDate 2010-11-23 下午06:12:01//@Testpublic void readJson2List() {String json = "[{\"address\": \"address2\",\"name\":\"haha2\",\"id\":2,\"email\":\"email2\"},"+"{\"address\":\"address\",\"name\":\"haha\",\"id\":1,\"email\":\"email\"}]";try {List> list = objectMapper.readValue(json, List.class);System.out.println(list.size());for (int i = 0; i < list.size(); i++) {Map map = list.get(i);Set set = map.keySet();for (Iterator it = set.iterator();it.hasNext();) {String key = it.next();System.out.println(key + ":" + map.get(key));}}} catch (JsonParseException e) {e.printStackTrace();} catch (JsonMappingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

尝试过将上面的JSON转换成List,然后List中存放AccountBean,但结果失败了。但是支持Map集合。因为你转成List.class,但是不知道List存放何种类型。只好默然Map类型。因为所有的对象都可以转换成Map结合,运行后结果如下:

2address:address2name:haha2id:2email:email2address:addressname:hahaid:1email:email

3、 Json字符串转换成Array数组,由于上面的泛型转换不能识别到集合中的对象类型。所有这里用对象数组,可以解决这个问题。只不过它不再是集合,而是一个数组。当然这个不重要,你可以用Arrays.asList将其转换成List即可。

//// function:json字符串转换成Array/ @author hoojo/ @createDate 2010-11-23 下午06:14:01//@Testpublic void readJson2Array() {String json = "[{\"address\": \"address2\",\"name\":\"haha2\",\"id\":2,\"email\":\"email2\"},"+"{\"address\":\"address\",\"name\":\"haha\",\"id\":1,\"email\":\"email\"}]";try {AccountBean[] arr = objectMapper.readValue(json, AccountBean[].class);System.out.println(arr.length);for (int i = 0; i < arr.length; i++) {System.out.println(arr[i]);}} catch (JsonParseException e) {e.printStackTrace();} catch (JsonMappingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

运行后的结果:

2haha2/#2/#address2/#null/#email2haha/#1/#address/#null/#email

4、 Json字符串转换成Map集合

//// function:json字符串转换Map集合/ @author hoojo/ @createDate Nov 27, 2010 3:00:06 PM//@Testpublic void readJson2Map() {String json = "{\"success\":true,\"A\":{\"address\": \"address2\",\"name\":\"haha2\",\"id\":2,\"email\":\"email2\"},"+"\"B\":{\"address\":\"address\",\"name\":\"haha\",\"id\":1,\"email\":\"email\"}}";try {Map> maps = objectMapper.readValue(json, Map.class);System.out.println(maps.size());Set key = maps.keySet();Iterator iter = key.iterator();while (iter.hasNext()) {String field = iter.next();System.out.println(field + ":" + maps.get(field));}} catch (JsonParseException e) {e.printStackTrace();} catch (JsonMappingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

运行后结果如下:

3success:trueA:{address=address2, name=haha2, id=2, email=email2}B:{address=address, name=haha, id=1, email=email}

四、**JacksonXML的支持**

Jackson也可以完成java对象到xml的转换,转换后的结果要比json-lib更直观,不过它依赖于stax2-api.jar这个jar包。 //// function:java对象转换成xml文档/ 需要额外的jar包 stax2-api.jar/ @author hoojo/ @createDate 2010-11-23 下午06:11:21/*/@Testpublic void writeObject2Xml() {//stax2-api-3.0.2.jarSystem.out.println("XmlMapper");XmlMapper xml = new XmlMapper();try {//javaBean转换成xml//xml.writeValue(System.out, bean);StringWriter sw = new StringWriter();xml.writeValue(sw, bean);System.out.println(sw.toString());//List转换成xmlList list = new ArrayList();list.add(bean);list.add(bean);System.out.println(xml.writeValueAsString(list));//Map转换xml文档Map map = new HashMap();map.put("A", bean);map.put("B", bean);System.out.println(xml.writeValueAsString(map));} catch (JsonGenerationException e) {e.printStackTrace();} catch (JsonMappingException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

运行上面的方法,结果如下:

XmlMapper

china-Guangzhou
hoojo1hoojo@126.com
china-Guangzhou
hoojo1hoojo
@126.com
china-Guangzhou
hoojo1hoojo@126.com
china-Guangzhou
hoojo1hoojo
@126.com
china-Guangzhou
hoojo1hoojo_@126.com

看结果,根节点都是unknown 这个问题还没有解决,由于根节点没有转换出来,所有导致解析xml到Java对象,也无法完成。

Top

收藏 关注

评论 分类: JavaEE, JavaSE, Others, JSON&XML

标签: JavaEE, JavaSE, JSON, XML转换, JSON转换 绿色通道: 好文要顶 关注我 收藏该文与我联系

hoojo 关注 - 1 粉丝 - 475

+加关注

7

0 (请您对文章做出评价)

« 上一篇:JSON-lib框架,转换JSON、XML不再困难 » 下一篇:xStream完美转换XML、JSON

posted on 2011-04-22 10:45 hoojo 阅读(37975) 评论(16) 编辑 收藏

评论:

/#1楼 2011-04-22 13:29 | Dawnxu 这个很不错,可惜android下用不上..

支持(0)反对(0)

/#2楼[楼主] 2011-04-22 13:50 | hoojo @Dawnxu 怎么用不上,可以将转换后的字符串当做参数传递过去,这样在接收的地方再进行反序列化。应该是可以的!

支持(0)反对(0) http://pic.cnitblog.com/face/u151517.jpg

/#3楼 2011-12-27 09:31 | dearxiaofan 很不错的文章,我们android项目数据获取的框架用的就是这个。学习了。~

支持(0)反对(0)

/#4楼 2012-02-28 15:25 | 7-up 文章写得很详细,但我还是不太理解java对象要怎么设计 比如下面的一段json: {"error":0,"data":{"name":"ABC","age":20,"phone":{"home":"abc","mobile":"def"},"friends":[{"name":"DEF","phone":{"home":"hij","mobile":"klm"}},{"name":"GHI","phone":{"home":"nop","mobile":"qrs"}}]},"other":{"nickname":[]}} 我要怎样才能将其反序列? 再问一个问题对于一个未知的json有没有通用方法将其反序列,还是说一定要自己构造相应的java对象,才能实现反序列化呢?

支持(0)反对(0)

/#5楼[楼主] 2012-03-01 09:20 | hoojo @7-up String json = {"error":0,"data":{"name":"ABC","age":20,"phone":{"home":"abc","mobile":"def"},"friends":[{"name":"DEF","phone":{"home":"hij","mobile":"klm"}},{"name":"GHI","phone":{"home":"nop","mobile":"qrs"}}]},"other":{"nickname":[]}} Map> maps = objectMapper.readValue(json, Map.class); 这样就可以反序列化了,是一个map对象,map对象中存放的是LinkHashMap、LinkList对象,不一定要封装成Java对象。

支持(0)反对(0) http://pic.cnitblog.com/face/u151517.jpg
/#6楼 2012-03-03 15:21 | 7-up @hoojo 谢谢楼主帮忙,问题已解决

支持(0)反对(0)

/#7楼 2012-03-16 13:33 | fanjifeng 你好!楼主! 我看你的博文,但是对于Jackson对XML的支持其中需要使用stax2-api-3.0.2.jar包,我下载该包后,发现没有org.codehaus.jackson.xml.XmlMapper;,能否请楼主给个下载地址,或者发下包,我邮箱fjfzsm@sina.com

支持(0)反对(0)

/#8楼[楼主] 2012-03-20 09:52 | hoojo @fanjifeng 是否在jackson-all-1.7.6.jar这个包中

支持(0)反对(0) http://pic.cnitblog.com/face/u151517.jpg

/#9楼 2012-03-20 22:37 | fanjifeng 我查看了jackson-all-1.7.6.jar, jackson-all-1.9.5.jar 都没有org.codehaus.jackson.xml.XmlMapper类,而且没有org.codehaus.jackson.xml包路径。

支持(0)反对(0)

/#10楼[楼主] 2012-03-21 09:12 | hoojo @fanjifeng jackson-all-1.6.2.jar有这个类,新版本的可能去掉了

支持(0)反对(0) http://pic.cnitblog.com/face/u151517.jpg

/#11楼 2012-07-19 11:38 | 梦里六月的飞雪 这篇博客很不错,而且在很多地方也看到相同文章,我找到这篇文章,下载了,jackson-all- 1.7.6.jar 实现了,实现了,除转换为xml之外的其他所有功能,但是转换为xml一直不成功,很是 纠结,一直找不懂XmlMapper这个类,第一天下午没搞定,然后第二天上午还是在google上查资料, 终于 在 http://www.cowtowncoder.com/blog/archives/2012/03/entry_467.html 上面找到了些头 绪 ,jackson-dataformat-xml https://github.com/FasterXML/jackson-dataformat-xml ,在这 个页面上下载了 jackson-dataformat-xml-2.0.2.jar, 终于找到 XmlMapper 了,但是 放到 classpath下怎么也不能导入,后来才知道,他依赖的jar是 jackson-core-2.0.4.jar、jackson- databind-2.0.4.jar、jackson-annotations-2.0.4.jar ,然后 在加上 stax2-api-3.1.1.jar , 终于测试成功了,原来的 jackson-all-1.7.6.jar ,从classpath移除,加入新的包后,全部测试通 过,博主的案例将的很好,很详细,如果还解决不了 ,fmx35699@163.com ,我可以测试通过的源码 给大家,

支持(1)反对(0)

/#12楼 2012-07-19 12:36 | 梦里六月的飞雪 @fanjifeng 照我的解决方案试下,就可以了,jackson一直更新,而且更新很快,现在的最新2.04了,换下jar试试

支持(0)反对(0)

/#13楼 2012-08-26 22:38 | 余波可 @梦里六月的飞雪 我遇到和你一样的问题,XmlMapper这个类没有找到。其他的测试都通过了 。我按你的解决方案 “原来的 jackson-all-1.7.6.jar ,从classpath移除,加入新的包jackson-core-2.0.4.jar、jackson-databind-2.0.4.jar、jackson-annotations-2.0.4.jar后,stax2-api-3.1.1.jar 也加入了, 之前测试通过的反而测试失败了。特求一份源码。 我的邮箱:timyuheng@163.com 谢谢先

支持(0)反对(0)

/#14楼 2013-02-16 01:46 | kingdelee 博主你好哈~ 我自己尝试了一下用jackson2和1都没有成功,能否在您的博客中提供一份工程源码下载参考一下或者电邮给我呢,感激不尽啊!!! kingdelee@gmail.com

支持(0)反对(0)

/#15楼 2013-04-12 15:14 | 苏城 好文,学习了。。

支持(0)反对(0)

/#16楼27053592013/6/15 11:55:09 2013-06-15 11:55 | 章小慢 没有发现umknow 情况

支持(0)反对(0)

刷新评论刷新页面返回顶部

注册用户登录后才能发表评论,请 登录注册访问网站首页。 博客园首页博问新闻闪存程序员招聘知识库

最新IT新闻: · 消息称金庸将联合完美和畅游对大陆市场侵权游戏展开维权 · Google 主义 vs 苹果主义 · 移动开发者爱金钱,更爱成就感 · “厕所休闲网站”Backlabel · 蒂法竟然不是第一 细数玩家心中20位红粉知己 » 更多新闻...

最新知识库文章: · 我现在是这样编程的 · 如何做到 jQuery-free? · 如何成为一位优秀的创业CEO · 十年前的Java企业应用开发世界 · 专注做好一件事 » 更多知识库文章...

About Me

网名:hoojo E-mail:hoojo_@126.com 专注于Java,现从事电警卡口、智能交通、电子警察、数字城市等应用开发,擅长JavaEE、Flex、ActionScript及Web前端HTML、CSS、JavaScript、ExtJS、jQuery、Mootools等开发。对常用开源框架有一定的认识和见解。

版权声明

hoojo 所有文章遵循创作共用版权协议,要求署名、非商业、保持一致。在满足创作共用版权协议的基础上可以转载,但请以超链接形式注明出处。

访客统计

Powered By Clicki.cn

订阅统计

订阅我的博客

订阅文章

抓虾 google reader netvibes my yahoo bloglines 鲜果 哪吒 有道 QQ邮箱 订阅内容到手机

订阅历史

IT 新闻

+加关注

<2011年4月>日一二三四五六27282930311234567891011121314151617181920212223242526272829301234567

搜索

常用链接

最新随笔

我的标签

随笔分类(387)

随笔档案(139)

文章档案(3)

相册

关注博客

我的链接

积分与排名

  • 积分 - 189629
  • 排名 - 523

最新评论

阅读排行榜

评论排行榜

推荐排行榜

Powered by: 博客园 模板提供:沪江博客 Copyright ©2013 hoojo

redis源码分析

Posted on

redis源码分析-多集合求交集算法

源码redis源码过程中学习了它内部多个set求交集的算法,觉得挺不错的,实现也相对简单。

不过要发挥这个算法的效果,前提是保证集合是散列型的,比如hash,set。 对链表或者数组等集合求交集方法后面介绍。

先来看看redis中多个set求交集的主要部分。 // Sort sets from the smallest to largest, this will improve our / algorithm's performace // qsort(dv,setsnum,sizeof(dict/),qsortCompareSetsByCardinality);

di= dictGetIterator(dv[0]); while((de = dictNext(di)) != NULL) {

robj/ele; for (j = 1; j < setsnum; j++) if (dictFind(dv[j],dictGetEntryKey(de)) == NULL) break; if (j != setsnum) continue; // at least one set does not contain the member /*/

ele= dictGetEntryKey(de); if (!dstkey) {

addReplyBulk(c,ele);

cardinality++; } else {

dictAdd(dstset->ptr,ele,NULL);

incrRefCount(ele); } }

看代码比较难理解,redis里的set数据结构本质就是个dict(hash表), 所以可以利用本身就做过hash这点,交集算法主要步骤u如下:

1、对多个set集合根据集合大小排序,最小的排最前面,下标0,

2、得到最小set集合的迭代器,后面需要

3、遍历第二步得到的迭代器,本质上就是遍历最小的set集合中所有的元素,然后把每一步遍历得到的元素去其他set集合里查看是否存在,只要发现在一个集合中不存在就直接跳出,因为对其他集合(下标0到N)比较是按前面排序的顺序,即从第二小的集合比较,到最大集合。这样可以最大程度的提前跳出比较。当这个元素在其他集合都存在的时候,说明这个元素是交集中的值。

redis求交的大致思路如此。但是如果我们的集合是数组类的,那么要使用这种算法,需要先把所有数组集合的元素都计算hash值,这样做根本没必要,反而得不偿失。

我对这类集合求交算法如下(java代码):

Collections.sort(sort_list); //第一步和redis的交集算法一样,也是根据集合大小从小到大的排序

// hashMap 求交

HashMap hashMap = new HashMap();// 我需要再创建一个hashMap

boolean first = true; //是否是最小集合

List list = null;

for (int i = 0; i < sort_list.size(); i++) {

int index = sort_list.get(i).getIndex();// 得到集合的下标

list = values[index].getLongList();//真正的多个集合是通过values[]保存的,这里根据上面的下标就得到了这个集合

for (int j = 0; j < list.size(); j++) {//遍历这个集合

if (first) {//如果是最小的集合,直接将元素插入到前面的hashmap中,value值为1,表示出现一次

hashMap.put(list.get(j), 1);

} else {//当不是最小集合的时候,取出元素先判断hashmap里是否存在,不存在的话就根本不需要做无谓的插入,这里的插入前提是之前一直有同样的数据存在,但hashmap存在这个值,并不表示是有效值,比如第一次出现了,但是后面的集合没有出现,所以需要一个value的计数器。

if (hashMap.containsKey(list.get(j))) {

int temp = hashMap.get(list.get(j));

hashMap.put(list.get(j), temp + 1);//如果存在这个元素,则给对应的value+1

}

}

}

first = false;//第一次遍历后,就标记为false

}

///

/* 得到交集后的结果,这个需要遍历之前的hashmap,判断value值等于集合的数量就表示这个是交集中的值

/*/

List result = new ArrayList();

Entry entry = null;

for (Iterator iterator = hashMap.entrySet().iterator(); iterator

.hasNext();) {

entry = (Entry) iterator.next();

if (entry.getValue() >= keyN.length) {// 多个集合的交集

result.add(entry.getKey());//把结果保存到结果集中

}

} 上面的算法其实没有必要对集合做排序,只需要得出最小的集合就可以了。

两个算法的相似处是对比较都做了优化。redis的交集算法,从最小集合开始比较,使判断提前跳出,不做无谓的比较。

第二个算法也是做了排序,从最小集合开始,首先就把这个hashmap的大小限制住了,后面不管有多大的集合,都不可能超过hashmap的范围。其次减少了无谓的数据插入,只要在之前有这个元素才做插入。

用哪种交集算法还要考虑自己的数据特点。单论两种算法,不管是在空间和时间上,前者都比后者强。但是一般的集合并不是散列类型的数据,那么还是用第二种吧,再根据自己的数据特点做优化。 来源: [http://www.yiihsia.com/2011/04/redis%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90-%e5%a4%9a%e9%9b%86%e5%90%88%e6%b1%82%e4%ba%a4%e9%9b%86%e7%ae%97%e6%b3%95/](http://www.yiihsia.com/2011/04/redis%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90-%e5%a4%9a%e9%9b%86%e5%90%88%e6%b1%82%e4%ba%a4%e9%9b%86%e7%ae%97%e6%b3%95/)