通过jps -lv 获取到本地的一个JVM实例进程。再通过jstack pid  > thread.txt ,把stack trace输出到thread.txt文件中。

2012-08-28 23:16:39

  1. 2012-08-28 23:16:39
  2. Full thread dump OpenJDK Client VM (19.0-b09 mixed mode, sharing):
  3. "Attach Listener" daemon prio=10 tid=0xb3da5400 nid=0x13ea waiting on condition [0x00000000]
  4. java.lang.Thread.State: RUNNABLE
  5. "Worker-15" prio=10 tid=0x09b7b400 nid=0x12f2 in Object.wait() [0xb30f9000]
  6. java.lang.Thread.State: TIMED_WAITING (on object monitor)
  7. at java.lang.Object.wait(Native Method)
  8. at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)
  9. - locked <0x6e5f87c0> (a org.eclipse.core.internal.jobs.WorkerPool)
  10. at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)
  11. at org.eclipse.core.internal.jobs.Worker.run(Worker.java:50)
  12. "Worker-12" prio=10 tid=0x09910c00 nid=0x12a0 in Object.wait() [0xb4026000]
  13. java.lang.Thread.State: TIMED_WAITING (on object monitor)
  14. at java.lang.Object.wait(Native Method)
  15. at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)
  16. - locked <0x6e5f87c0> (a org.eclipse.core.internal.jobs.WorkerPool)
  17. at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)
  18. at org.eclipse.core.internal.jobs.Worker.run(Worker.java:50)
  19. .......省略
  20. "Low Memory Detector" daemon prio=10 tid=0x097eb000 nid=0xa9f runnable [0x00000000]
  21. java.lang.Thread.State: RUNNABLE
  22. "CompilerThread0" daemon prio=10 tid=0x097e9000 nid=0xa9e waiting on condition [0x00000000]
  23. java.lang.Thread.State: RUNNABLE
  24. "Signal Dispatcher" daemon prio=10 tid=0x097e7400 nid=0xa9d runnable [0x00000000]
  25. java.lang.Thread.State: RUNNABLE
  26. "Finalizer" daemon prio=10 tid=0x097e4000 nid=0xa9c in Object.wait() [0xb4dd1000]
  27. java.lang.Thread.State: WAITING (on object monitor)
  28. at java.lang.Object.wait(Native Method)
  29. at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:133)
  30. - locked <0x6e2e02f8> (a java.lang.ref.ReferenceQueue$Lock)
  31. at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:149)
  32. at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)
  33. "Reference Handler" daemon prio=10 tid=0x097df400 nid=0xa9b in Object.wait() [0xb4e22000]
  34. java.lang.Thread.State: WAITING (on object monitor)
  35. at java.lang.Object.wait(Native Method)
  36. at java.lang.Object.wait(Object.java:502)
  37. at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
  38. - locked <0x6e2e0098> (a java.lang.ref.Reference$Lock)
  39. "main" prio=10 tid=0x097b0400 nid=0xa99 runnable [0xbf8b9000]
  40. java.lang.Thread.State: RUNNABLE
  41. at org.eclipse.swt.internal.gtk.OS.Call(Native Method)
  42. at org.eclipse.swt.widgets.Display.sleep(Display.java:4069)
  43. at org.eclipse.ui.application.WorkbenchAdvisor.eventLoopIdle(WorkbenchAdvisor.java:364)
  44. at org.eclipse.ui.internal.ide.application.IDEWorkbenchAdvisor.eventLoopIdle(IDEWorkbenchAdvisor.java:917)
  45. at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2697)
  46. at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2660)
  47. at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2494)
  48. at org.eclipse.ui.internal.Workbench$7.run(Workbench.java:674)
  49. at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
  50. at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:667)
  51. at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149)
  52. at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:123)
  53. at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
  54. at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
  55. at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
  56. at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:344)
  57. at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
  58. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  59. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  60. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  61. at java.lang.reflect.Method.invoke(Method.java:616)
  62. at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:622)
  63. at org.eclipse.equinox.launcher.Main.basicRun(Main.java:577)
  64. at org.eclipse.equinox.launcher.Main.run(Main.java:1410)
  65. "VM Thread" prio=10 tid=0x097dd800 nid=0xa9a runnable
  66. "GC task thread#0 (ParallelGC)" prio=10 tid=0x08726800 nid=0x1e30 runnable
  67. "GC task thread#1 (ParallelGC)" prio=10 tid=0x08727c00 nid=0x1e31 runnable
  68. "VM Periodic Task Thread" prio=10 tid=0x097ed400 nid=0xaa0 waiting on condition
  69. JNI global references: 2131

 

上面的stack trace一共可以拆解成5个部分。

第一部分: Full thread dump identifier

  1. 2012-08-28 23:16:39
  2. Full thread dump OpenJDK Client VM (19.0-b09 mixed mode, sharing):

可以看到dump时间,虚拟机的信息。

 

第二部分: Java EE middleware, third party & custom application Threads

  1. "Worker-15" prio=10 tid=0x09b7b400 nid=0x12f2 in Object.wait() [0xb30f9000]
  2. java.lang.Thread.State: TIMED_WAITING (on object monitor)
  3. at java.lang.Object.wait(Native Method)
  4. at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)
  5. - locked <0x6e5f87c0> (a org.eclipse.core.internal.jobs.WorkerPool)
  6. at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)
  7. at org.eclipse.core.internal.jobs.Worker.run(Worker.java:50)

此处是我们最为关心的地方,也是通过stack trace查找问题的地方,每个item的具体含义是

-Thread name: "Worker-15"

-线程优先级: prio=10

-java线程的identifier:tid=0x09b7b400

-native线程的identifier: nid=0x12f2

-线程的状态: in Object.wait()  java.lang.Thread.State: TIMED_WAITING (on object monitor)

-线程栈起始地址:[0xb30f9000]

 

Java线程和Native线程,也就是tid和nid的区别?

VM是加载并执行字节码的机器,这个处理不对应具体硬件,它完全由软件来实现。虚拟机并不是直接跑在宿主的cpu上,中间还隔着宿主的操作系统。

虚拟机的职责是:翻译字节码,把它翻译成一系列的动作(内存操作)和系统调用(比如输入输出)。举个例子:当在java里请求与某台远程机器建立socket连接时,java虚拟机本身是不能完成这一工作的(协议栈是在操作系统内实现的),所以它需要系统调用。要明白这个,还需要知道JRE和JVM的关系。JRE是JVM的环境,一个应用程序就是一个JVM实例。一个机器上一般同时只有一个JRE环境,但是可以同时有多个JVM实例。不同的操作系统和CPU架构有不同的JRE。JRE包含了最基本的类库,当然包括和操作系统打交道的Native库,比如windows下的dll文件,linux下的so文件。JVM实例解析字节码的成JVM中的指令集,再通过Native方法进行操作系统层面的系统调用。下面的图能比较清楚的说明问题。

JVM线程如何创建

当然是通过Native库的 native方法。JVM的线程最终是通过操作系统的线程来完成系统调用的。针对Java开发者来说,发号施令的是JVM 线程,具体干藏活累活的却是操作系统线程。不同操作系统上,JVM创建本地线程依赖的native库也不同。具体可参考下面的的文章。http://jackyhongvip.iteye.com/blog/1338610

 

JVM线程状态

状态有Runnable、Wait on condition、Waiting for monitor entry 和 in Object.wait()。

Runnable:不解释

Wait on condition:该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。最常见的情况是线程在等待网络的读写。如果网络数据没准备好,线程就等待在那里。另外一种出现 Wait on condition的常见情况是该线程在 sleep,等待 sleep的时间到了时候,将被唤醒。

Waiting for monitor entry 和 in Object.wait():这两种情况在多线程的情况下经常出现。Java是通过Monitor来实现线程互斥和协作(有些把Monitor直译成锁,我认为不妥,不是还有Lock嘛)。具体Monitor的深入理解见 http://www.cnblogs.com/tomsheep/archive/2010/06/09/1754419.html

但是可以理解成Monitor是一个对象或者class所拥有的锁,每个对象和class有且仅有一个。见下图.

每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。

在 “ Entry Set”里面的线程都等待拿到Monitor,拿到了线程就成为了Runnable线程,否则就会一直处于处于 “waiting for monitor entry”。一段代码作为例子

  1. public class MyThread implements Runnable{
  2. public void run() {
  3. synchronized(this) {
  4. for (int i = 0; i < 1; i--) {
  5. System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
  6. }
  7. }
  8. }
  9. public static void main(String[] args) {
  10. MyThread t1 = new MyThread();
  11. Thread ta = new Thread(t1, "A");
  12. Thread tb = new Thread(t1, "B");
  13. ta.start();
  14. tb.start();
  15. }
  16. }

大家一看就知道,B线程肯定是“千年老二“,永远拿不到Monitor了。

对应的stack:

  1. "B" prio=10 tid=0x0969a000 nid=0x11d6 waiting for monitor entry [0x8bb22000]
  2. java.lang.Thread.State: BLOCKED (on object monitor)
  3. at org.marshal.MyThread.run(MyThread.java:7)
  4. - waiting to lock <0x94757078> (a org.marshal.MyThread)
  5. at java.lang.Thread.run(Thread.java:636)
  6. "A" prio=10 tid=0x09698800 nid=0x11d5 runnable [0x8bb73000]
  7. java.lang.Thread.State: RUNNABLE
  8. at java.io.FileOutputStream.writeBytes(Native Method)
  9. at java.io.FileOutputStream.write(FileOutputStream.java:297)
  10. at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
  11. at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
  12. - locked <0x947571b0> (a java.io.BufferedOutputStream)
  13. at java.io.PrintStream.write(PrintStream.java:449)
  14. - locked <0x94757190> (a java.io.PrintStream)
  15. at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:220)
  16. at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:290)
  17. at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:103)
  18. - locked <0x947572a0> (a java.io.OutputStreamWriter)
  19. at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
  20. at java.io.PrintStream.write(PrintStream.java:494)
  21. - locked <0x94757190> (a java.io.PrintStream)
  22. at java.io.PrintStream.print(PrintStream.java:636)
  23. at java.io.PrintStream.println(PrintStream.java:773)
  24. - locked <0x94757190> (a java.io.PrintStream)
  25. at org.marshal.MyThread.run(MyThread.java:8)
  26. - locked <0x94757078> (a org.marshal.MyThread)
  27. at java.lang.Thread.run(Thread.java:636)

<0x94757078> 就是两个线程争夺的Monitor。

在 “Wait Set”里面的线程都如饥似渴地等待拿到Monitor。他们是怎么进入到“Wait Set”的呢?当一个线程拿到了Monitor,但是在其他资源没有到位的情况下,调用同步锁对象(一般是synchronized()内的对象)的 wait() 方法,放弃了 Monitor,它就进入到了 “Wait Set”队列。只有当其他线程通过notify() 或者 notifyAll(),释放了同步锁后,这个线程才会有机会重新去竞争Monitor。在stack中,它表现的状态是in Object.wait()。修改上面的代码public class WaitThread implements Runnable{

  1. public void run() {
  2. synchronized(this) {
  3. try {
  4. this.wait();
  5. } catch (InterruptedException e) {
  6. // TODO Auto-generated catch block
  7. e.printStackTrace();
  8. }
  9. }
  10. }
  11. public static void main(String[] args) {
  12. WaitThread t1 = new WaitThread();
  13. Thread ta = new Thread(t1, "A");
  14. Thread tb = new Thread(t1, "B");
  15. ta.start();
  16. tb.start();
  17. }

对应的stack:

  1. "B" prio=10 tid=0x08173000 nid=0x1304 in Object.wait() [0x8baf2000]
  2. java.lang.Thread.State: WAITING (on object monitor)
  3. at java.lang.Object.wait(Native Method)
  4. - waiting on <0xa9cb50e0> (a org.marshal.WaitThread)
  5. at java.lang.Object.wait(Object.java:502)
  6. at org.marshal.WaitThread.run(WaitThread.java:8)
  7. - locked <0xa9cb50e0> (a org.marshal.WaitThread)
  8. at java.lang.Thread.run(Thread.java:636)
  9. "A" prio=10 tid=0x08171c00 nid=0x1303 in Object.wait() [0x8bb43000]
  10. java.lang.Thread.State: WAITING (on object monitor)
  11. at java.lang.Object.wait(Native Method)
  12. - waiting on <0xa9cb50e0> (a org.marshal.WaitThread)
  13. at java.lang.Object.wait(Object.java:502)
  14. at org.marshal.WaitThread.run(WaitThread.java:8)
  15. - locked <0xa9cb50e0> (a org.marshal.WaitThread)
  16. at java.lang.Thread.run(Thread.java:636)

A和B线程都进入了”wait set“。B线程也拿到过这个Monitor,因为A线程释放过了,这也验证上面的话,他们都在等待得而复失的<0xa9cb50e0>。

基于我们经常讨论到的死锁问题,构造一段代码如下

  1. public class DeadThread implements Runnable{
  2. private Object monitor_A = new Object();
  3. private Object monitor_B = new Object();
  4. public void  method_A(){
  5. synchronized(monitor_A) {
  6. synchronized(monitor_B) {
  7. System.out.println(Thread.currentThread().getName()+" invoke method A");
  8. }
  9. }
  10. }
  11. public void  method_B(){
  12. synchronized(monitor_B) {
  13. synchronized(monitor_A) {
  14. System.out.println(Thread.currentThread().getName()+" invoke method B");
  15. }
  16. }
  17. }
  18. public void run() {
  19. for(int i=0;i<1;i--){
  20. method_A();
  21. method_B();
  22. }
  23. }
  24. public static void main(String[] args) {
  25. DeadThread t1 = new DeadThread();
  26. Thread ta = new Thread(t1, "A");
  27. Thread tb = new Thread(t1, "B");
  28. ta.start();
  29. tb.start();
  30. }
  31. }

对应的stack:

  1. "B" prio=10 tid=0x0898d000 nid=0x269a waiting for monitor entry [0x8baa2000]
  2. java.lang.Thread.State: BLOCKED (on object monitor)
  3. at org.marshal.DeadThread.method_A(DeadThread.java:11)
  4. - waiting to lock <0xaa4d6f88> (a java.lang.Object)
  5. - locked <0xaa4d6f80> (a java.lang.Object)
  6. at org.marshal.DeadThread.run(DeadThread.java:28)
  7. at java.lang.Thread.run(Thread.java:636)
  8. "A" prio=10 tid=0x0898b800 nid=0x2699 waiting for monitor entry [0x8baf3000]
  9. java.lang.Thread.State: BLOCKED (on object monitor)
  10. at org.marshal.DeadThread.method_B(DeadThread.java:19)
  11. - waiting to lock <0xaa4d6f80> (a java.lang.Object)
  12. - locked <0xaa4d6f88> (a java.lang.Object)
  13. at org.marshal.DeadThread.run(DeadThread.java:29)
  14. at java.lang.Thread.run(Thread.java:636)

同时注意到,在stack trace尾部信息

  1. Found one Java-level deadlock:
  2. =============================
  3. "B":
  4. waiting to lock monitor 0x089615d8 (object 0xaa4d6f88, a java.lang.Object),
  5. which is held by "A"
  6. "A":
  7. waiting to lock monitor 0x08962258 (object 0xaa4d6f80, a java.lang.Object),
  8. which is held by "B"
  9. Java stack information for the threads listed above:
  10. ===================================================
  11. "B":
  12. at org.marshal.DeadThread.method_A(DeadThread.java:11)
  13. - waiting to lock <0xaa4d6f88> (a java.lang.Object)
  14. - locked <0xaa4d6f80> (a java.lang.Object)
  15. at org.marshal.DeadThread.run(DeadThread.java:28)
  16. at java.lang.Thread.run(Thread.java:636)
  17. "A":
  18. at org.marshal.DeadThread.method_B(DeadThread.java:19)
  19. - waiting to lock <0xaa4d6f80> (a java.lang.Object)
  20. - locked <0xaa4d6f88> (a java.lang.Object)
  21. at org.marshal.DeadThread.run(DeadThread.java:29)
  22. at java.lang.Thread.run(Thread.java:636)
  23. Found 1 deadlock.

stack中直接报告了Java级别的死锁,够智能吧。

 

第三部分:HotSpot VM Thread

  1. "Low Memory Detector" daemon prio=10 tid=0x097eb000 nid=0xa9f runnable [0x00000000]
  2. java.lang.Thread.State: RUNNABLE
  3. "CompilerThread0" daemon prio=10 tid=0x097e9000 nid=0xa9e waiting on condition [0x00000000]
  4. java.lang.Thread.State: RUNNABLE

JVM管理的内部线程,这些内部线程都是来执行native操作的,可以不必太关注。

 

第四部分: HotSpot GC Thread

  1. "GC task thread#0 (ParallelGC)" prio=10 tid=0x08726800 nid=0x1e30 runnable
  2. "GC task thread#1 (ParallelGC)" prio=10 tid=0x08727c00 nid=0x1e31 runnable

当虚拟机打开了parallel GC,这些线程就会定期地GC清理。

 

第五部分:JNI global references count

  1. JNI global references: 2131

JNI (Java Native Interface)全局引用也是受垃圾回收器管理。它的作用是防止回收到那些在native代码上还是被引用的,但是在java代码中却没有存活必要的引用。对于防止因为应用代码中直接使用native库或者第三方的一些监控工具的内存泄漏有非常大的帮助。

虚拟机stack全分析的更多相关文章

  1. jstack Dump

    jstack Dump 日志文件中的线程状态 dump 文件里,值得关注的线程状态有: 死锁,Deadlock(重点关注)  执行中,Runnable 等待资源,Waiting on conditio ...

  2. top命令查看进程下线程信息以及jstack的使用

    转自:https://www.cnblogs.com/shengulong/p/8513652.html top -Hp pid可以查看某个进程的线程信息 -H 显示线程信息,-p指定pid jsta ...

  3. 三个实例演示 Java Thread Dump 日志分析

    原文地址: http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html jstack Dump 日志文件中的线程 ...

  4. 三个实例演示 Java Thread Dump 日志分析(转)

    原文链接:http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html 转来当笔记^_^ jstack Dump ...

  5. jstack 结果查看

    首先可以用jstack -l pid >sample.dump把java进程的运行栈dump出来. 还可以用grep java.lang.Thread.State sample.dump | a ...

  6. top命令查看线程信息和jstack使用介绍

    top -Hp pid可以查看某个进程的线程信息 -H 显示线程信息,-p指定pid jstack 线程ID 可以查看某个线程的堆栈情况,特别对于hung挂死的线程,可以使用选项-F强制打印dump信 ...

  7. Java Thread dump 日志分析

    jstack Dump 日志文件中的线程状态 dump 文件里,值得关注的线程状态有: 死锁,Deadlock(重点关注)  执行中,Runnable 等待资源,Waiting on conditio ...

  8. 面试之JVM

    目录 Java虚拟机 类的加载方式 Java内存模型 程序计数器(PC) Java虚拟机栈(Stack) 本地方法栈 元空间(MetaSpace) Java堆(Heap) 内存分配策略 垃圾回收(GC ...

  9. register based 和 stack based虚拟机的区别

    其实其核心的差异,就是Dalvik 虚拟机架构是 register-based,与 Sun JDK 的 stack-based 不同,也就是架构上的差异.我先摘录几段网上可以找到的资料,重新整理和排版 ...

随机推荐

  1. MySQL的冷备份和热备份概念理解(转)

    一.冷备份(off, 慢, 时间点上恢复) 冷备份发生在数据库已经正常关闭的情况下,当正常关闭时会提供给我们一个完整的数据库.冷备份是将关键性文件拷贝到另外位置的一种说法.对于备份数据库信息而言,冷备 ...

  2. PostgreSQL增强版命令行客户端(pgcli)

    效果: 安装: https://www.pgcli.com/install 官网: https://www.pgcli.com/

  3. HDU 4720 Naive and Silly Muggles (简单计算几何)

    Naive and Silly Muggles Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/ ...

  4. apache如何支持asp.net

    Apache是目前广泛使用的一种网络服务器程序,不仅在UNIX/LINUX平台上被大量使用,而且在Windows平台上也有许多站点放弃了IIS而转向Apache..NET是微软推出的功能强大的开发技术 ...

  5. 深入理解 Java中的 流 (Stream)

    首先,流是什么? 流是个抽象的概念.是对输入输出设备的抽象,Java程序中,对于数据的输入/输出操作都是以"流"的方式进行.设备能够是文件,网络,内存等. 流具有方向性,至于是输入 ...

  6. XCL-Charts画曲线图(CurveChart) 例2

    还有一个横向的曲线图(CurveChart) 效果: 代码: //图基类 chart = new CurveChart(); //图所占范围大小 chart.setChartRange(0, 0, t ...

  7. sql的嵌套查询,把一次查询的结果做为表继续进一步查询;内联视图

    Mysql的嵌套表查询 嵌套SELECT语句也叫子查询,一个 SELECT 语句的查询结果能够作为另一个语句的输入值.子查询可以: 出现在Where子句中, 出现在from子句中,作为一个临时表使用, ...

  8. 【BZOJ】【2741】【FOTILE模拟赛】L

    可持久化Trie+分块 神题……Orz zyf & lyd 首先我们先将整个序列搞个前缀异或和,那么某一段的异或和,就变成了两个数的异或和,所以我们就将询问[某个区间中最大的区间异或和]改变成 ...

  9. UNIX 域套接字——UNIX domain socket

    /*********************程序相关信息********************* * 程序编号:015 * 程序编写起始日期:2013.11.30 * 程序编写完成日期:2013.1 ...

  10. php解决乱码问题时,出现Cannot modify header information问题的解决方法

    设置页面编码使用: header("Content-Type:text/html;charset=utf-8"); 会出现:Cannot modify header informa ...