導航:首頁 > 源碼編譯 > 枚舉量過多jvm編譯失敗

枚舉量過多jvm編譯失敗

發布時間:2023-02-04 07:14:05

A. jvm 性能調優工具之 jstat 命令詳解

Jstat名稱:java Virtual Machine statistics monitoring tool

功能描述:

Jstat是JDK自帶的一個輕量級小工具。它位於java的bin目錄下,主要利用JVM內建的指令對Java應用程序的資源和性能進行實時的命令行的監控,包括了對Heap size和垃圾回收狀況的監控。

命令用法:jstat [-命令選項] [vmid] [間隔時間/毫秒] [查詢次數]
注意:使用的jdk版本是jdk8。

C:\Users\Administrator>jstat -helpUsage: jstat -help|-options jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]] Definitions: <option> An option reported by the -options option <vmid> Virtual Machine Identifier. A vmid takes the following form: <lvmid>[@<hostname>[:<port>]] Where <lvmid> is the local vm identifier for the target Java virtual machine, typically a process id; <hostname> is the name of the host running the target Java virtual machine; and <port> is the port number for the rmiregistry on the target host. See the jvmstat documentation for a more complete description of the Virtual Machine Identifier. <lines> Number of samples between header lines. <interval> Sampling interval. The following forms are allowed: <n>["ms"|"s"] Where <n> is an integer and the suffix specifies the units as milliseconds("ms") or seconds("s"). The default units are "ms". <count> Number of samples to take before terminating. -J<flag> Pass <flag> directly to the runtime system.
option:參數選項
-t:可以在列印的列加上Timestamp列,用於顯示系統運行的時間
-h:可以在周期性數據輸出的時候,指定輸出多少行以後輸出一次表頭
vmid:Virtual Machine ID( 進程的 pid)
interval:執行每次的間隔時間,單位為毫秒
count:用於指定輸出多少次記錄,預設則會一直列印
option 可以從下面參數中選擇

jstat -options

-class 用於查看類載入情況的統計
-compiler 用於查看HotSpot中即時編譯器編譯情況的統計
-gc 用於查看JVM中堆的垃圾收集情況的統計
-gccapacity 用於查看新生代、老生代及持久代的存儲容量情況
-gcmetacapacity 顯示metaspace的大小
-gcnew 用於查看新生代垃圾收集的情況
-gcnewcapacity 用於查看新生代存儲容量的情況
-gcold 用於查看老生代及持久代垃圾收集的情況
-gcoldcapacity 用於查看老生代的容量
-gcutil 顯示垃圾收集信息
-gccause 顯示垃圾回收的相關信息(通-gcutil),同時顯示最後一次僅當前正在發生的垃圾收集的原因
-printcompilation 輸出JIT編譯的方法信息
示例:

1.-class 類載入統計

[root@hadoop ~]# jps #先通過jps獲取到java進程號(這里是一個zookeeper進程)3346 QuorumPeerMain7063 Jps[root@hadoop ~]# jstat -class 3346 #統計JVM中載入的類的數量與sizeLoaded Bytes Unloaded Bytes Time 1527 2842.7 0 0.0 1.02
Loaded:載入類的數量
Bytes:載入類的size,單位為Byte
Unloaded:卸載類的數目
Bytes:卸載類的size,單位為Byte
Time:載入與卸載類花費的時間
2.-compiler 編譯統計

[root@hadoop ~]# jstat -compiler 3346 #用於查看HotSpot中即時編譯器編譯情況的統計Compiled Failed Invalid Time FailedType FailedMethod 404 0 0 0.19 0
Compiled:編譯任務執行數量
Failed:編譯任務執行失敗數量
Invalid:編譯任務執行失效數量
Time:編譯任務消耗時間
FailedType:最後一個編譯失敗任務的類型
FailedMethod:最後一個編譯失敗任務所在的類及方法
3.-gc 垃圾回收統計

[root@hadoop ~]# jstat -gc 3346 #用於查看JVM中堆的垃圾收集情況的統計 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 128.0 128.0 0.0 128.0 1024.0 919.8 15104.0 2042.4 8448.0 8130.4 1024.0 996.0 7 0.019 0 0.000 0.019
S0C:年輕代中第一個survivor(倖存區)的容量 (位元組)
S1C:年輕代中第二個survivor(倖存區)的容量 (位元組)
S0U:年輕代中第一個survivor(倖存區)目前已使用空間 (位元組)
S1U:年輕代中第二個survivor(倖存區)目前已使用空間 (位元組)
EC:年輕代中Eden(伊甸園)的容量 (位元組)
EU:年輕代中Eden(伊甸園)目前已使用空間 (位元組)
OC:Old代的容量 (位元組)
OU:Old代目前已使用空間 (位元組)
MC:metaspace(元空間)的容量 (位元組)
MU:metaspace(元空間)目前已使用空間 (位元組)
CCSC:當前壓縮類空間的容量 (位元組)
CCSU:當前壓縮類空間目前已使用空間 (位元組)
YGC:從應用程序啟動到采樣時年輕代中gc次數
YGCT:從應用程序啟動到采樣時年輕代中gc所用時間(s)
FGC:從應用程序啟動到采樣時old代(全gc)gc次數
FGCT:從應用程序啟動到采樣時old代(全gc)gc所用時間(s)
GCT:從應用程序啟動到采樣時gc用的總時間(s)
4.-gccapacity 堆內存統計

[root@hadoop ~]# jstat -gccapacity 3346 #用於查看新生代、老生代及持久代的存儲容量情況 NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0[root@hadoop ~]# jstat -gccapacity -h5 3346 1000 #-h5:每5行顯示一次表頭 1000:每1秒鍾顯示一次,單位為毫秒 NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0 1280.0 83264.0 1280.0 128.0 128.0 1024.0 15104.0 166592.0 15104.0 15104.0 0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 7 0
NGCMN:年輕代(young)中初始化(最小)的大小(位元組)
NGCMX:年輕代(young)的最大容量 (位元組)
NGC:年輕代(young)中當前的容量 (位元組)
S0C:年輕代中第一個survivor(倖存區)的容量 (位元組)
S1C:年輕代中第二個survivor(倖存區)的容量 (位元組)
EC:年輕代中Eden(伊甸園)的容量 (位元組)
OGCMN:old代中初始化(最小)的大小 (位元組)
OGCMX:old代的最大容量(位元組)
OGC:old代當前新生成的容量 (位元組)
OC:Old代的容量 (位元組)
MCMN:metaspace(元空間)中初始化(最小)的大小 (位元組)
MCMX:metaspace(元空間)的最大容量 (位元組)
MC:metaspace(元空間)當前新生成的容量 (位元組)
CCSMN:最小壓縮類空間大小
CCSMX:最大壓縮類空間大小
CCSC:當前壓縮類空間大小
YGC:從應用程序啟動到采樣時年輕代中gc次數
FGC:從應用程序啟動到采樣時old代(全gc)gc次數
5.-gcmetacapacity 元數據空間統計

[root@hadoop ~]# jstat -gcmetacapacity 3346 #顯示元數據空間的大小MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC FGCT GCT0.0 1056768.0 8448.0 0.0 1048576.0 1024.0 8 0 0.000 0.020
MCMN:最小元數據容量
MCMX:最大元數據容量
MC:當前元數據空間大小
CCSMN:最小壓縮類空間大小
CCSMX:最大壓縮類空間大小
CCSC:當前壓縮類空間大小
YGC:從應用程序啟動到采樣時年輕代中gc次數
FGC:從應用程序啟動到采樣時old代(全gc)gc次數
FGCT:從應用程序啟動到采樣時old代(全gc)gc所用時間(s)
GCT:從應用程序啟動到采樣時gc用的總時間(s)
6.-gcnew 新生代垃圾回收統計

[root@hadoop ~]# jstat -gcnew 3346 #用於查看新生代垃圾收集的情況S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT128.0 128.0 67.8 0.0 1 15 64.0 1024.0 362.2 8 0.020
S0C:年輕代中第一個survivor(倖存區)的容量 (位元組)
S1C:年輕代中第二個survivor(倖存區)的容量 (位元組)
S0U:年輕代中第一個survivor(倖存區)目前已使用空間 (位元組)
S1U:年輕代中第二個survivor(倖存區)目前已使用空間 (位元組)
TT:持有次數限制
MTT:最大持有次數限制
DSS:期望的倖存區大小
EC:年輕代中Eden(伊甸園)的容量 (位元組)
EU:年輕代中Eden(伊甸園)目前已使用空間 (位元組)
YGC:從應用程序啟動到采樣時年輕代中gc次數
YGCT:從應用程序啟動到采樣時年輕代中gc所用時間(s)
7.-gcnewcapacity 新生代內存統計

[root@hadoop ~]# jstat -gcnewcapacity 3346 #用於查看新生代存儲容量的情況NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC1280.0 83264.0 1280.0 8320.0 128.0 8320.0 128.0 66624.0 1024.0 8 0
NGCMN:年輕代(young)中初始化(最小)的大小(位元組)
NGCMX:年輕代(young)的最大容量 (位元組)
NGC:年輕代(young)中當前的容量 (位元組)
S0CMX:年輕代中第一個survivor(倖存區)的最大容量 (位元組)
S0C:年輕代中第一個survivor(倖存區)的容量 (位元組)
S1CMX:年輕代中第二個survivor(倖存區)的最大容量 (位元組)
S1C:年輕代中第二個survivor(倖存區)的容量 (位元組)
ECMX:年輕代中Eden(伊甸園)的最大容量 (位元組)
EC:年輕代中Eden(伊甸園)的容量 (位元組)
YGC:從應用程序啟動到采樣時年輕代中gc次數
FGC:從應用程序啟動到采樣時old代(全gc)gc次數
8.-gcold 老年代垃圾回收統計

[root@hadoop ~]# jstat -gcold 3346 #用於查看老年代及持久代垃圾收集的情況MC MU CCSC CCSU OC OU YGC FGC FGCT GCT8448.0 8227.5 1024.0 1003.7 15104.0 2102.2 8 0 0.000 0.020
MC:metaspace(元空間)的容量 (位元組)
MU:metaspace(元空間)目前已使用空間 (位元組)
CCSC:壓縮類空間大小
CCSU:壓縮類空間使用大小
OC:Old代的容量 (位元組)
OU:Old代目前已使用空間 (位元組)
YGC:從應用程序啟動到采樣時年輕代中gc次數
FGC:從應用程序啟動到采樣時old代(全gc)gc次數
FGCT:從應用程序啟動到采樣時old代(全gc)gc所用時間(s)
GCT:從應用程序啟動到采樣時gc用的總時間(s)
9.-gcoldcapacity 老年代內存統計

[root@hadoop ~]# jstat -gcoldcapacity 3346 #用於查看老年代的容量OGCMN OGCMX OGC OC YGC FGC FGCT GCT15104.0 166592.0 15104.0 15104.0 8 0 0.000 0.020
OGCMN:old代中初始化(最小)的大小 (位元組)OGCMX:old代的最大容量(位元組)OGC:old代當前新生成的容量 (位元組)OC:Old代的容量 (位元組)YGC:從應用程序啟動到采樣時年輕代中gc次數FGC:從應用程序啟動到采樣時old代(全gc)gc次數FGCT:從應用程序啟動到采樣時old代(全gc)gc所用時間(s)GCT:從應用程序啟動到采樣時gc用的總時間(s) 在此我向大家推薦一個架構學習交流圈。交流學習指導偽鑫:1253431195(裡面有大量的面試題及答案)裡面會分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高並發、高性能、分布式、微服務架構的原理,JVM性能優化、分布式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多
10.-gcutil 垃圾回收統計

[root@hadoop ~]# jstat -gcutil 3346 #顯示垃圾收集信息S0 S1 E O M CCS YGC YGCT FGC FGCT GCT52.97 0.00 42.10 13.92 97.39 98.02 8 0.020 0 0.000 0.020
S0:年輕代中第一個survivor(倖存區)已使用的占當前容量百分比
S1:年輕代中第二個survivor(倖存區)已使用的占當前容量百分比
E:年輕代中Eden(伊甸園)已使用的占當前容量百分比
O:old代已使用的占當前容量百分比
M:元數據區已使用的占當前容量百分比
CCS:壓縮類空間已使用的占當前容量百分比
YGC :從應用程序啟動到采樣時年輕代中gc次數
YGCT :從應用程序啟動到采樣時年輕代中gc所用時間(s)
FGC :從應用程序啟動到采樣時old代(全gc)gc次數
FGCT :從應用程序啟動到采樣時old代(全gc)gc所用時間(s)
GCT:從應用程序啟動到采樣時gc用的總時間(s)
11.-gccause

[root@hadoop ~]# jstat -gccause 3346 #顯示垃圾回收的相關信息(通-gcutil),同時顯示最後一次或當前正在發生的垃圾回收的誘因S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC52.97 0.00 46.09 13.92 97.39 98.02 8 0.020 0 0.000 0.020 Allocation Failure No GC
LGCC:最後一次GC原因
GCC:當前GC原因(No GC 為當前沒有執行GC)
12.-printcompilation JVM編譯方法統計

[root@hadoop ~]# jstat -printcompilation 3346 #輸出JIT編譯的方法信息Compiled Size Type Method421 60 1 sun/nio/ch/Util$2 clear
Compiled:編譯任務的數目
Size:方法生成的位元組碼的大小
Type:編譯類型
Method:類名和方法名用來標識編譯的方法。類名使用/做為一個命名空間分隔符。方法名是給定類中的方法。上述格式是由-XX:+PrintComplation選項進行設置的
遠程監控

與jps一樣,jstat也支持遠程監控,同樣也需要開啟安全授權,方法參照jps。

C:\Users\Administrator>jps 192.168.146.1283346 QuorumPeerMain3475 JstatdC:\Users\Administrator>jstat -gcutil [email protected] S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 52.97 0.00 65.15 13.92 97.39 98.02 8 0.020 0 0.000 0.020

B. 為什麼編譯Java程序的時候會出現Error: could not find a JVM.

安裝了JDK後,環境變數設置的不對吧!

C. java 枚舉可以大量用嗎

首先你要了解枚舉(enum)的概念,java.lang.Enum是JDK5.0版本新加入的類,我們所編寫的枚舉其實都是隱式的繼承自它,既然它是類,因此會有類型安全性、編譯期檢查以及可將它用在變數聲明中的能力。這可以把使用數值或字元串來模擬含義的常量徹底打敗,枚舉中的各個實例(值)都是被隱式聲明為public static final的,而且你也不能將這些聲明手動賦予給實例(值),編譯器會自己處理。
枚舉的應用場合:在你需要一個僅允許特定數據類型值的有限集合,在實際問題中,有些變數的取值被限定在一個有限的范圍內。例如,一個星期內只有七天,一年只有十二個月,一個班每周有六門課程等等。如果把這些量說明為整型,字元型或其它類型顯然是不妥當的。
如果搞懂了這些問題,你就應該自己可以判斷你的程序里需要不需要它,大量應用枚舉這件事本身並不會引發什麼(性能問題),要看你應用的場景是否合適,sun搞了enum必然有其道理,原則上講JDK5以後都是為了簡化開發,enum也是一樣的,只要你認為使用了enum會為你編程帶來便捷,那麼你還考慮什麼,程序員不要被業界那些條條框框所束縛,一動手就一大堆的面向對象思想,設計模式的,打個比方,你天天要去一個地方,建議你多試幾條路,這樣你就知道哪條最近了。

D. 如何解決Unsupported major.minor version 52.0問題

一:要解決的問題

我們在嘗鮮 JDK1.5 的時候,相信不少人遇到過 Unsupported major.minor version 49.0 錯誤,當時定會茫然不知所措。因為剛開始那會兒,網上與此相關的中文資料還不多,現在好了,網上一找就知道是如何解決,大多會告訴你要使用 JDK 1.4 重新編譯。那麼至於為什麼,那個 major.minor 究竟為何物呢?這就是本篇來講的內容,以使未錯而先知。

我覺得我是比較幸運的,因為在遇到那個錯誤之前已研讀過《深入 Java 虛擬機》第二版,英文原書名為《Inside the Java Virtual Machine》( Second Edition),看時已知曉 major.minor 藏匿於何處,但沒有切身體會,待到與 Unsupported major.minor version 49.0 真正會面試,正好是給我驗證了一個事實。

首先我們要對 Unsupported major.minor version 49.0 建立的直接感覺是:JDK1.5 編譯出來的類不能在 JVM 1.4 下運行,必須編譯成 JVM 1.4 下能運行的類。(當然,也許你用的還是 JVM 1.3 或 JVM 1.2,那麼就要編譯成目標 JVM 能認可的類)。這也解決問題的方向。

二:major.minor 棲身於何處

何謂 major.minor,且又居身於何處呢?先感性認識並找到 major.minor 來。

寫一個 Java Hello World! 代碼,然後用 JDK 1.5 的編譯器編譯成,HelloWorld.java

package com.unmi;

public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello, World!");
}
}
package com.unmi;public class HelloWorld{ public static void main(String[] args) { System.out.println("Hello, World!"); }}

用 JDK 1.5 的 javac -d . HelloWorld.java 編譯出來的位元組碼 HelloWorld.class 用 UltraEdit 打開來的內容如圖所示:

從上圖中我們看出來了什麼是 major.minor version 了,它相當於一個軟體的主次版本號,只是在這里是標識的一個 Java Class 的主版本號和次版本號,同時我們看到 minor_version 為 0x0000,major_version 為 0x0031,轉換為十制數分別為0 和 49,即 major.minor 就是 49.0 了。

三:何謂 major.minor 以及何用

Class 文件的第 5-8 位元組為 minor_version 和 major_version。Java class 文件格式可能會加入新特性。class 文件格式一旦發生變化,版本號也會隨之變化。對於 JVM 來說,版本號確定了特定的 class 文件格式,通常只有給定主版本號和一系列次版本號後,JVM 才能夠讀取 class 文件。如果 class 文件的版本號超出了 JVM 所能處理的有效范圍,JVM 將不會處理該 class 文件。

在 Sun 的 JDK 1.0.2 發布版中,JVM 實現支持從 45.0 到 45.3 的 class 文件格式。在所有 JDK 1.1 發布版中的 JVM 都能夠支持版本從 45.0 到 45.65535 的 class 文件格式。在 Sun 的 1.2 版本的 SDK 中,JVM 能夠支持從版本 45.0 到46.0 的 class 文件格式。

1.0 或 1.2 版本的編譯器能夠產生版本號為 45.3 的 class 文件。在 Sun 的 1.2 版本 SDK 中,Javac 編譯器默認產生版本號為 45.3 的 class 文件。但如果在 javac 命令行中指定了 -target 1.2 標志,1.2 版本的編譯器將產生版本號為 46.0 的 class 文件。1.0 或 1.1 版本的 JVM 上不能運行使用-target 1.2 標志所產生的 class 文件。

JVM 實現的 第二版中修改了對 class 文件主版本號和次版本號的解釋。對於第二版而言,class 文件的主版本號與 Java 平台主發布版的版本號保持一致(例如:在 Java 2 平台發布版上,主版本號從 45 升至 46),次版本號與特定主平台發布版的各個發布版相關。因此,盡管不同的 class 文件格式可以由不同的版本號表示,但版本號不一樣並不代表 class 文件格式不同。版本號不同的原因可能只是因為 class 文件由不同發布版本的 java 平台產生,可能 class 文件的格式並沒有改變。

上面三段節選自《深入 Java 虛擬機》,啰嗦一堆,JDK 1.2 開啟了 Java 2 的時代,但那個年代仍然離我們很遠,我們當中很多少直接跳在 JDK 1.4 上的,我也差不多,只是項目要求不得不在一段時間里委屈在 JDK 1.3 上。不過大致我們可以得到的信息就是每個版本的 JDK 編譯器編譯出的 class 文件中都帶有一個版本號,不同的 JVM 能接受一個范圍 class 版本號,超出范圍則要出錯。不過一般都是能向後兼容的,知道 Sun 在做 Solaris 的一句口號嗎?保持對先前版本的 100% 二進制兼容性,這也是對客戶的投資保護。

四:其他確定 class 的 major.minor version 辦法

1)Eclipse 中查看

Eclipse 3.3 加入的新特徵,當某個類沒有關聯到源代碼,打開它會顯示比較詳細的類信息,當然還未到源碼級別了,看下圖是打開 2.0 spring.jar 中 .class 顯示的信息

2)命令 javap -verbose

對於編譯出的 class 文件用 javap -verbose 能顯示出類的 major.minor 版本,見下圖:

3) MANIFEST 文件

把 class 打成的 JAR 包中都會有文件 META-INF\MANIFEST,這個文件一般會有編譯器的信息,下面列幾個包的 META-INF\MANIFEST 文件內容大家看看

·Velocity-1.5.jar 的 META-INFO\MANIFEST 部份內容

Manifest-Version: 1.0

Ant-Version: Apache Ant 1.7.0

Created-By: Apache Ant

Package: org.apache.velocity

Build-Jdk: 1.4.2_08

Extension-Name: velocity

我們看到是用 ant 打包,構建用的JDK是 1.4.2_08,用 1.4 編譯的類在 1.4 JVM 中當然能運行。如果那人用 1.5 的 JDK 來編譯,然後用 JDK 1.4+ANT 來打包就太無聊了。

·2.0 spring.jar 的 META-INFO\MANIFEST 部份內容

Manifest-Version: 1.0

Ant-Version: Apache Ant 1.6.5

Created-By: 1.5.0_08-b03 (Sun Microsystems Inc.)

Implementation-Title: Spring Framework

這下要注意啦,它是用的 JDK 1.5 來編譯的,那麼它是否帶了 -target 1.4 或 -target 1.3 來編譯的呢?確實是的,可以查看類的二進制文件,這是最保險的。所在 spring-2.0.jar 也可以在 1.4 JVM 中載入執行。

·自已一個項目中用 ant 打的 jar 包的 META-INFO\MANIFEST

Manifest-Version: 1.0

Ant-Version: Apache Ant 1.7.0

Created-By: 1.4.2-b28 (Sun Microsystems Inc.)

用的是 JDK 1.4 構建打包的。

第一第二種辦法能明確知道 major.minor version,而第三種方法應該也沒問題,但是碰到變態構建就難說了,比如誰把那個 META-INFO\MANIFEST 打包後換了也未可知。直接查看類的二進制文件的方法可以萬分保證,准確無誤,就是工具篡改我也認了。

五:編譯器比較及症節之所在

現在不妨從 JDK 1.1 到 JDK 1.7 編譯器編譯出的 class 的默認 minor.major version 吧。(又走到 Sun 的網站上翻騰出我從來都沒用過的古董來)

JDK 編譯器版本 target 參數 十六進制 minor.major 十進制 minor.major
jdk1.1.8 不能帶 target 參數 00 03 00 2D 45.3
jdk1.2.2 不帶(默認為 -target 1.1) 00 03 00 2D 45.3
jdk1.2.2 -target 1.2 00 00 00 2E 46.0
jdk1.3.1_19 不帶(默認為 -target 1.1) 00 03 00 2D 45.3
jdk1.3.1_19 -target 1.3 00 00 00 2F 47.0
j2sdk1.4.2_10 不帶(默認為 -target 1.2) 00 00 00 2E 46.0
j2sdk1.4.2_10 -target 1.4 00 00 00 30 48.0
jdk1.5.0_11 不帶(默認為 -target 1.5) 00 00 00 31 49.0
jdk1.5.0_11 -target 1.4 -source 1.4 00 00 00 30 48.0
jdk1.6.0_01 不帶(默認為 -target 1.6) 00 00 00 32 50.0
jdk1.6.0_01 -target 1.5 00 00 00 31 49.0
jdk1.6.0_01 -target 1.4 -source 1.4 00 00 00 30 48.0
jdk1.7.0 不帶(默認為 -target 1.6) 00 00 00 32 50.0
jdk1.7.0 -target 1.7 00 00 00 33 51.0
jdk1.7.0 -target 1.4 -source 1.4 00 00 00 30 48.0
Apache Harmony 5.0M3 不帶(默認為 -target 1.2) 00 00 00 2E 46.0
Apache Harmony 5.0M3 -target 1.4 00 00 00 30 48.0
上面比較是 Windows 平台下的 JDK 編譯器的情況,我們可以此作些總結:

1) -target 1.1 時 有次版本號,target 為 1.2 及以後都只用主版本號了,次版本號為 0

2) 從 1.1 到 1.4 語言差異比較小,所以 1.2 到 1.4 默認的 target 都不是自身相對應版本

3) 1.5 語法變動很大,所以直接默認 target 就是 1.5。也因為如此用 1.5 的 JDK 要生成目標為 1.4 的代碼,光有 -target 1.4 不夠,必須同時帶上 -source 1.4,指定源碼的兼容性,1.6/1.7 JDk 生成目標為 1.4 的代碼也如此。

4) 1.6 編譯器顯得較為激進,默認參數就為 -target 1.6。因為 1.6 和 1.5 的語法無差異,所以用 -target 1.5 時無需跟著 -source 1.5。

5) 注意 1.7 編譯的默認 target 為 1.6

6) 其他第三方的 JDK 生成的 Class 文件格式版本號同對應 Sun 版本 JDK

7) 最後一點最重要的,某個版本的 JVM 能接受 class 文件的最大主版本號不能超過對應 JDK 帶相應 target 參數編譯出來的 class 文件的版本號。

上面那句話有點長,一口氣讀過去不是很好理解,舉個例子:1.4 的 JVM 能接受最大的 class 文件的主版本號不能超過用 1.4 JDK 帶參數 -target 1.4 時編譯出的 class 文件的主版本號,也就是 48。

因為 1.5 JDK 編譯時默認 target 為 1.5,出來的位元組碼 major.minor version 是 49.0,所以 1.4 的 JVM 是無法接受的,只有拋出錯誤。

那麼又為什麼從 1.1 到 1.2、從 1.2 到 1.3 或者從 1.3 到 1.4 的 JDK 升級不會發生 Unsupported major.minor version 的錯誤呢,那是因為 1.2/1.3/1.4 都保持了很好的二進制兼容性,看看 1.2/1.3/1.4 的默認 target 分別為 1.1/1.1/1.2 就知道了,也就是默認情況下1.4 JDK 編譯出的 class 文件在 JVM 1.2 下都能載入執行,何況於 JVM 1.3 呢?(當然要去除使用了新版本擴充的 API 的因素)

六:找到問題解決的方法

那麼現在如果碰到這種問題該知道如何解決了吧,還會像我所見到有些兄弟那樣,去找個 1.4 的 JDK 下載安裝,然後用其重新編譯所有的代碼嗎?其實大可不必如此費神,我們一定還記得 javac 還有個 -target 參數,對啦,可以繼續使用 1.5 JDK,編譯時帶上參數 -target 1.4 -source 1.4 就 OK 啦,不過你一定要對哪些 API 是 1.5 JDK 加入進來的了如指掌,不能你的 class 文件拿到 JVM 1.4 下就會 method not found。目標 JVM 是 1.3 的話,編譯選項就用 -target 1.3 -source 1.3 了。

相應的如果使用 ant ,它的 javac 任務也可對應的選擇 target 和 source

<javac target="1.4" source="1.4" ............................/>

如果是在開發中,可以肯定的是現在真正算得上是 JAVA IDE 對於工程也都有編譯選項設置目標代碼的。例如 Eclipse 的項目屬性中的 Java Compiler 設置,如圖

自已設定編譯選項,你會看到選擇不同的 compiler compliance level 是,Generated class files compatibility 和 Source compatibility 也在變,你也可以手動調整那兩項,手動設置後你就不用很在乎用的什麼版本的編譯器了,只要求他生成我們希望的位元組碼就行了,再引申一下就是即使源代碼是用 VB 寫的,只要能編譯成 JVM 能執行的位元組碼都不打緊。在其他的 IDE 也能找到相應的設置對話框的。

其他時候,你一定要知道當前的 JVM 是什麼版本,能接受的位元組碼主版本號是多少(可對照前面那個表)。獲息當前 JVM 版本有兩種途徑:

第一:如果你是直接用 java 命令在控制台執行程序,可以用 java -version 查看當前的 JVM 版本,然後確定能接受的 class 文件版本

第二:如果是在容器中執行,而不能明確知道會使用哪個 JVM,那麼可以在容器中執行的程序中加入代碼System.getProperty("java.runtime.version"); 或 System.getProperty("java.class.version"),獲得 JVM 版本和能接受的 class 的版本號。

最後一絕招,如果你不想針對低版本的 JVM 用 target 參數重新編譯所有代碼;如果你仍然想繼續在代碼中用新的 API 的話;更有甚者,你還用了 JDK 1.5 的新特性,譬如泛型、自動拆裝箱、枚舉等的話,那你用 -target 1.4 -source 1.4 就沒法編譯通過,不得不重新整理代碼。那麼告訴你最後一招,不需要再從源代碼著手,直接轉換你所正常編譯出的位元組碼,繼續享用那些新的特性,新的 API,那就是:請參考之前的一篇日誌:Retrotranslator讓你用JDK1.5的特性寫出的代碼能在JVM1.4中運行 ,我就是這么用的,做好測試就不會有問題的。

七:再議一個實際發生的相關問題

這是一個因為拷貝 Tomcat 而產生的 Unsupported major.minor version 49.0 錯誤。情景是:我本地安裝的是 JDK 1.5,然後在網上找了一個 EXE 的 Tomcat 安裝文件安裝了並且可用。後來同事要一個 Tomcat,不想下載或安裝,於是根據我以往的經驗是把我的 Tomcat 整個目錄拷給他應該就行了,結果是拿到他那裡瀏覽 jsp 文件都出現 Unsupported major.minor version 49.0 錯誤,可以確定的是他安裝的是 1.4 的 JDK,但我還是有些納悶,先前對這個問題還頗有信心的我傻眼了。慣性思維是編譯好的 class 文件拿到低版本的 JVM 會出現如是異常,可現並沒有用已 JDK 1.5 編譯好的類要執行啊。

後來仔細看異常信息,終於發現了 %TOMCAT_HOME%\common\lib\tools.jar 這一眉目,因為 jsp 文件需要依賴它來編譯,打來這個 tools.jar 中的一個 class 文件來看看,49.0,很快我就明白原來這個文件是在我的機器上安裝 Tomcat 時由 Tomcat 安裝程序從 %JDK1.5%\lib 目錄拷到 Tomcat 的 lib 目錄去的,造成在同事機器上編譯 JSP 時是 1.4 的 JVM 配搭著 49.0 的 tools.jar,那能不出錯,於是找來 1.4 JDK 的 tools.jar 替換了 Tomcat 的就 OK 啦。

八:小結

其實理解 major.minor 就像是我們可以這么想像,同樣是微軟體的程序,32 位的應用程序不能拿到 16 位系統中執行那樣。

如果我們發布前了解到目標 JVM 版本,知道怎麼從 java class 文件中看出 major.minor 版本來,就不用等到伺服器報出異常才著手去解決,也就能預知到可能發生的問題。

其他時候遇到這個問題應具體解決,總之問題的根由是低版本的 JVM 無法載入高版本的 class 文件造成的,找到高版本的 class 文件處理一下就行了

E. java中常見的幾種異常

1、空指針異常類:NullPointerException

調用了未經初始化的對象或者是不存在的對象。經常出現在創建圖片,調用數組這些操作中,比如圖片未經初始化,或者圖片創建時的路徑錯誤等等。對數組操作中出現空指針, 即把數組的初始化和數組元素的初始化混淆起來了。

數組的初始化是對數組分配需要的空間,而初始化後的數組,其中的元素並沒有實例化, 依然是空的,所以還需要對每個元素都進行初始化(如果要調用的話)。

2、數據類型轉換異常:java.lang.ClassCastException

當試圖將對某個對象強制執行向下轉型,但該對象又不可轉換又不可轉換為其子類的實例時將引發該異常,如下列代碼。

Object obj=newInteger(0);

String str = obj;

3、沒有訪問許可權:java.lang.IllegalAccessException

當應用程序要調用一個類,但當前的方法即沒有對該類的訪問許可權便會出現這個異常。對程序中用了Package的情況下要注意這個異常。

4、方法的參數錯誤:java.lang.IllegalArgumentException

比如g.setColor(int red,int green,int blue)這個方法中的三個值,如果有超過255的也會出現這個異常,因此一旦發現這個異常,我們要做的,就是趕緊去檢查一下方法調用中的參數傳遞是不是出現了錯誤。

5、數組下標越界異常:java.lang.IndexOutOfBoundsException

查看調用的數組或者字元串的下標值是不是超出了數組的范圍,一般來說,顯示(即直接用常數當下標)調用不太容易出這樣的錯,但隱式(即用變數表示下標)調用就經常出錯了。

還有一種情況,是程序中定義的數組的長度是通過某些特定方法決定的,不是事先聲明的,這個時候先查看一下數組的length,以免出現這個異常。

6、文件已結束異常:EOFException

當程序在輸入的過程中遇到文件或流的結尾時,引發異常。因此該異常用於檢查是否達到文件或流的結尾

7、文件未找到異常:FileNotFoundException

當程序試圖打開一個不存在的文件進行讀寫時將會引發該異常。該異常由FileInputStream,FileOutputStream,RandomAccessFile的構造器聲明拋出,即使被操作的文件存在,但是由於某些原因不可訪問,比如打開一個只讀文件進行寫入,這些構造方法仍然會引發異常。

8、字元串轉換為數字異常:NumberFormatException

當試圖將一個String轉換為指定的數字類型,而該字元串確不滿足數字類型要求的格式時,拋出該異常.如現在講字元型的數據「123456」轉換為數值型數據時,是允許的。

但是如果字元型數據中包含了非數字型的字元,如123#56,此時轉換為數值型時就會出現異常。系統就會捕捉到這個異常,並進行處理。

9、指定的類不存在:java.lang.ClassNotFoundException

這里主要考慮一下類的名稱和路徑是否正確即可,通常都是程序試圖通過字元串來載入某個類時可能引發異常。比如:調用Class.forName;或者調用ClassLoad的finaSystemClass;或者LoadClass;

10、實例化異常:java.lang.InstantiationException

當試圖通過Class的newInstance方法創建某個類的實例,但程序無法通過該構造器來創建該對象時引發。Class對象表示一個抽象類,介面,數組類,基本類型 。該Class表示的類沒有對應的構造器。

F. c語言,在一個頭文件定義一個枚舉變數,有兩個C文件用#include包含了這個頭文件,編譯錯誤:重復聲明成員

頭文件里加防止重復定義的宏定義

#ifndef XXX_H
#define XXX_H
你的枚舉;
其他定義和聲明;
#endif

G. java動態鏈接庫連接失敗 jvm.dll載入失敗

項目中用到 Jpcap 庫,這個庫引用到一個 C 的鏈接庫文件

鏈接庫文件放到 /usr/lib 下面

以前在別的Linux系統下都運行的好好的

今天部署到一個 Centos 機器上就報錯:java.lang.NoClassDefFoundError: Could not initialize class jpcap.JpcapCaptor 和 java.lang.unsatisfiedlinkerror

看了下 JpcapCaptor 類的源碼發現裡面有載入動態鏈接庫的代碼

肯定就是沒找到 動態鏈接庫文件了。

1.重新編譯庫文件 2.把庫文件放到項目根目錄,等等幾番折騰

最後都要絕望的時候了,突然想起 /etc/ld.so.conf

vi 一看還真沒有庫目錄

加上兩行
/usr/lib
/usr/local/lib

保存 執行 ldconfig命令 生效

問題就這樣解決了。

H. 關於JAVA編譯中的錯誤問題

Java虛擬機(JVM)是可運行Java代碼的假想計算機。只要根據JVM規格描述將解釋器移植到特定的計算機上,就能保證經過編譯的任何Java代碼能夠在該系統上運行。本文首先簡要介紹從Java文件的編譯到最終執行的過程,隨後對JVM規格描述作一說明。

一.Java源文件的編譯、下載、解釋和執行
Java應用程序的開發周期包括編譯、下載、解釋和執行幾個部分。Java編譯程序將Java源程序翻譯為JVM可執行代碼?位元組碼。這一編譯過程同C/C++的編譯有些不同。當C編譯器編譯生成一個對象的代碼時,該代碼是為在某一特定硬體平台運行而產生的。因此,在編譯過程中,編譯程序通過查表將所有對符號的引用轉換為特定的內存偏移量,以保證程序運行。Java編譯器卻不將對變數和方法的引用編譯為數值引用,也不確定程序執行過程中的內存布局,而是將這些符號引用信息保留在位元組碼中,由解釋器在運行過程中創立內存布局,然後再通過查表來確定一個方法所在的地址。這樣就有效的保證了Java的可移植性和安全性。

運行JVM位元組碼的工作是由解釋器來完成的。解釋執行過程分三部進行:代碼的裝入、代碼的校驗和代碼的執行。裝入代碼的工作由"類裝載器"(class loader)完成。類裝載器負責裝入運行一個程序需要的所有代碼,這也包括程序代碼中的類所繼承的類和被其調用的類。當類裝載器裝入一個類時,該類被放在自己的名字空間中。除了通過符號引用自己名字空間以外的類,類之間沒有其他辦法可以影響其他類。在本台計算機上的所有類都在同一地址空間內,而所有從外部引進的類,都有一個自己獨立的名字空間。這使得本地類通過共享相同的名字空間獲得較高的運行效率,同時又保證它們與從外部引進的類不會相互影響。當裝入了運行程序需要的所有類後,解釋器便可確定整個可執行程序的內存布局。解釋器為符號引用同特定的地址空間建立對應關系及查詢表。通過在這一階段確定代碼的內存布局,Java很好地解決了由超類改變而使子類崩潰的問題,同時也防止了代碼對地址的非法訪問。

隨後,被裝入的代碼由位元組碼校驗器進行檢查。校驗器可發現操作數棧溢出,非法數據類型轉化等多種錯誤。通過校驗後,代碼便開始執行了。

Java位元組碼的執行有兩種方式:
1.即時編譯方式:解釋器先將位元組碼編譯成機器碼,然後再執行該機器碼。
2.解釋執行方式:解釋器通過每次解釋並執行一小段代碼來完成Java位元組碼程 序的所有操作。
通常採用的是第二種方法。由於JVM規格描述具有足夠的靈活性,這使得將位元組碼翻譯為機器代碼的工作

具有較高的效率。對於那些對運行速度要求較高的應用程序,解釋器可將Java位元組碼即時編譯為機器碼,從而很好地保證了Java代碼的可移植性和高性能。

二.JVM規格描述
JVM的設計目標是提供一個基於抽象規格描述的計算機模型,為解釋程序開發人員提很好的靈活性,同時也確保Java代碼可在符合該規范的任何系統上運行。JVM對其實現的某些方面給出了具體的定義,特別是對Java可執行代碼,即位元組碼(Bytecode)的格式給出了明確的規格。這一規格包括操作碼和操作數的語法和數值、標識符的數值表示方式、以及Java類文件中的Java對象、常量緩沖池在JVM的存儲映象。這些定義為JVM解釋器開發人員提供了所需的信息和開發環境。Java的設計者希望給開發人員以隨心所欲使用Java的自由。

JVM定義了控制Java代碼解釋執行和具體實現的五種規格,它們是:
JVM指令系統
JVM寄存器
JVM棧結構
JVM碎片回收堆
JVM存儲區

2.1JVM指令系統

JVM指令系統同其他計算機的指令系統極其相似。Java指令也是由 操作碼和操作數兩部分組成。操作碼為8位二進制數,操作數進緊隨在操作碼的後面,其長度根據需要而不同。操作碼用於指定一條指令操作的性質(在這里我們採用匯編符號的形式進行說明),如iload表示從存儲器中裝入一個整數,anewarray表示為一個新數組分配空間,iand表示兩個整數的"與",ret用於流程式控制制,表示從對某一方法的調用中返回。當長度大於8位時,操作數被分為兩個以上位元組存放。JVM採用了"big endian"的編碼方式來處理這種情況,即高位bits存放在低位元組中。這同 Motorola及其他的RISC CPU採用的編碼方式是一致的,而與Intel採用的"little endian "的編碼方式即低位bits存放在低位位元組的方法不同。

Java指令系統是以Java語言的實現為目的設計的,其中包含了用於調用方法和監視多先程系統的指令。Java的8位操作碼的長度使得JVM最多有256種指令,目前已使用了160多種操作碼。

2.2JVM指令系統

所有的CPU均包含用於保存系統狀態和處理器所需信息的寄存器組。如果虛擬機定義較多的寄存器,便可以從中得到更多的信息而不必對棧或內存進行訪問,這有利於提高運行速度。然而,如果虛擬機中的寄存器比實際CPU的寄存器多,在實現虛擬機時就會佔用處理器大量的時間來用常規存儲器模擬寄存器,這反而會降低虛擬機的效率。針對這種情況,JVM只設置了4個最為常用的寄存器。它們是:
pc程序計數器
optop操作數棧頂指針
frame當前執行環境指針
vars指向當前執行環境中第一個局部變數的指針
所有寄存器均為32位。pc用於記錄程序的執行。optop,frame和vars用於記錄指向Java棧區的指針。

2.3JVM棧結構

作為基於棧結構的計算機,Java棧是JVM存儲信息的主要方法。當JVM得到一個Java位元組碼應用程序後,便為該代碼中一個類的每一個方法創建一個棧框架,以保存該方法的狀態信息。每個棧框架包括以下三類信息:
局部變數
執行環境
操作數棧

局部變數用於存儲一個類的方法中所用到的局部變數。vars寄存器指向該變數表中的第一個局部變數。
執行環境用於保存解釋器對Java位元組碼進行解釋過程中所需的信息。它們是:上次調用的方法、局部變數指針和操作數棧的棧頂和棧底指針。執行環境是一個執行一個方法的控制中心。例如:如果解釋器要執行iadd(整數加法),首先要從frame寄存器中找到當前執行環境,而後便從執行環境中找到操作數棧,從棧頂彈出兩個整數進行加法運算,最後將結果壓入棧頂。
操作數棧用於存儲運算所需操作數及運算的結果。

2.4JVM碎片回收堆

Java類的實例所需的存儲空間是在堆上分配的。解釋器具體承擔為類實例分配空間的工作。解釋器在為一個實例分配完存儲空間後,便開始記錄對該實例所佔用的內存區域的使用。一旦對象使用完畢,便將其回收到堆中。
在Java語言中,除了new語句外沒有其他方法為一對象申請和釋放內存。對內存進行釋放和回收的工作是由Java運行系統承擔的。這允許Java運行系統的設計者自己決定碎片回收的方法。在SUN公司開發的Java解釋器和Hot Java環境中,碎片回收用後台線程的方式來執行。這不但為運行系統提供了良好的性能,而且使程序設計人員擺脫了自己控制內存使用的風險。

2.5JVM存儲區

JVM有兩類存儲區:常量緩沖池和方法區。常量緩沖池用於存儲類名稱、方法和欄位名稱以及串常量。方法區則用於存儲Java方法的位元組碼。對於這兩種存儲區域具體實現方式在JVM規格中沒有明確規定。這使得Java應用程序的存儲布局必須在運行過程中確定,依賴於具體平台的實現方式。

JVM是為Java位元組碼定義的一種獨立於具體平台的規格描述,是Java平台獨立性的基礎。目前的JVM還存在一些限制和不足,有待於進一步的完善,但無論如何,JVM的思想是成功的。

對比分析:如果把Java原程序想像成我們的C++原程序,Java原程序編譯後生成的位元組碼就相當於C++原程序編譯後的80x86的機器碼(二進製程序文件),JVM虛擬機相當於80x86計算機系統,Java解釋器相當於80x86CPU。在80x86CPU上運行的是機器碼,在Java解釋器上運行的是Java位元組碼。

Java解釋器相當於運行Java位元組碼的「CPU」,但該「CPU」不是通過硬體實現的,而是用軟體實現的。Java解釋器實際上就是特定的平台下的一個應用程序。只要實現了特定平台下的解釋器程序,Java位元組碼就能通過解釋器程序在該平台下運行,這是Java跨平台的根本。當前,並不是在所有的平台下都有相應Java解釋器程序,這也是Java並不能在所有的平台下都能運行的原因,它只能在已實現了Java解釋器程序的平台下運行。

I. 集合已修改;枚舉操作可能不會執行。

C#(C-Sharp)是Microsoft的新編程語言,被譽為「C/C++家族中第一種面向組件的語言」。然而,不管它自己宣稱的是什麼,許多人認為C#更像是Java的一種克隆,或者是Microsoft用來替代Java的產品。事實是否是這樣的呢?

本文的比較結果表明,C#不止是Java的同胞那麼簡單。如果你是一個Java開發者,想要學習C#或者了解更多有關C#的知識,那麼本文就是你必須把最初10分鍾投入於其中的所在。

一、C#、C++和Java
C#的語言規范由Microsoft的Anders Hejlsberg與Scott Wiltamuth編寫。在當前Microsoft天花亂墜的宣傳中,對C#和C++、Java作一番比較總是很有趣的。考慮到當前IT媒體的輿論傾向,如果你早就知道C#更接近Java而不是C++,事情也不值得大驚小怪。對於剛剛加入這場討論的讀者,下面的表1讓你自己作出判斷。顯然,結論應該是:Java和C#雖然不是孿生子,但C#最主要的特色卻更接近Java而不是C++。

表1:比較C#、C++和Java最重要的功能
功能 C# C++ Java
繼承 允許繼承單個類,允許實現多個介面 允許從多個類繼承 允許繼承單個類,允許實現多個介面
介面實現 通過「interface」關鍵詞 通過抽象類 通過「interface」關鍵詞
內存管理 由運行時環境管理,使用垃圾收集器 需要手工管理 由運行時環境管理,使用垃圾收集器
指針 支持,但只在很少使用的非安全模式下才支持。通常以引用取代指針 支持,一種很常用的功能。 完全不支持。代之以引用。
源代碼編譯後的形式 .NET中間語言(IL) 可執行代碼 位元組碼
單一的公共基類 是 否 是
異常處理 異常處理 返回錯誤 異常處理。

了解表1總結的重要語言功能之後,請繼續往下閱讀,了解C#和Java的一些重要區別。

二、語言規范的比較
2.1、簡單數據類型
簡單數據類型(Primitive)在C#中稱為值類型,C#預定義的簡單數據類型比Java多。例如,C#有unit,即無符號整數。表2列出了所有C#的預定義數據類型:

表2:C#中的值類型
類型 說明
object 所有類型的最終極的基類
string 字元串類型;字元串是一個Unicode字元的序列
sbyte 8位帶符號整數
short 16位帶符號整數
int 32位帶符號整數
long 64位帶符號整數
byte 8位無符號整數
ushort 16位無符號整數
uint 32位無符號整數
ulong 64位無符號整數
float 單精度浮點數類型
double 雙精度浮點數類型
bool 布爾類型;bool值或者是true,或者是false
char 字元類型;一個char值即是一個Unicode字元
decimal 有28位有效數字的高精度小數類型

2.2、常量
忘掉Java中的static final修飾符。在C#中,常量可以用const關鍵詞聲明。

public const int x = 55;

此外,C#的設計者還增加了readonly關鍵詞。如果編譯器編譯時未能確定常量值,你可以使用readonly關鍵詞。readonly域只能通過初始化器或類的構造函數設置。

2.3、公用類的入口點
在Java中,公用類的入口點是一個名為main的公用靜態方法。main方法的參數是String對象數組,它沒有返回值。在C#中,main方法變成了公用靜態方法Main(大寫的M),Main方法的參數也是一個String對象數組,而且也沒有返回值,如下面的原型聲明所示:

public static void Main(String[] args)

但是,C#的Main方法不局限於此。如果不向Main方法傳遞任何參數,你可以使用上述Main方法的一個重載版本,即不帶參數列表的版本。也就是說,下面的Main方法也是一個合法的入口點:

public static void Main()

另外,如果你認為有必要的話,Main方法還可以返回一個int。例如,下面代碼中的Main方法返回1:

using System;
public class Hello {
public static int Main() {
Console.WriteLine("Done");
return 1;
}
}

與此相對,在Java中重載main方法是不合法的。

2.4、switch語句
在Java中,switch語句只能處理整數。但C#中的switch語句不同,它還能夠處理字元變數。請考慮下面用switch語句處理字元串變數的C#代碼:

using System;
public class Hello {
public static void Main(String[] args) {
switch (args[0]) {
case "老闆":
Console.WriteLine("早上好!我們隨時准備為您效勞!");
break;
case "雇員":
Console.WriteLine("早上好!你可以開始工作了!");
break;
default:
Console.WriteLine("早上好!祝你好運!");
break;
}
}
}

與Java中的switch不同,C#的switch語句要求每一個case塊或者在塊的末尾提供一個break語句,或者用goto轉到switch內的其他case標簽。

2.5、foreach語句
foreach語句枚舉集合中的各個元素,為集合中的每一個元素執行一次代碼塊。請參見下面的例子。

using System;
public class Hello {
public static void Main(String[] args) {
foreach (String arg in args)
Console.WriteLine(arg);
}
}

如果在運行這個執行文件的時候指定了參數,比如「Hello Peter Kevin Richard」,則程序的輸出將是下面幾行文字:

Peter
Kevin
Richard

2.6、C#沒有>>>移位操作符
C#支持uint和ulong之類的無符號變數類型。因此,在C#中,右移操作符(即「>>」)對於無符號變數類型和帶符號變數類型(比如int和long)的處理方式不同。右移uint和ulong丟棄低位並把空出的高位設置為零;但對於int和long類型的變數,「>>」操作符丟棄低位,同時,只有當變數值是正數時,「>>」才把空出的高位設置成零;如果「>>」操作的是一個負數,空出的高位被設置成為1。

Java中不存在無符號的變數類型。因此,我們用「>>>」操作符在右移時引入負號位;否則,使用「>>」操作符。

2.7、goto關鍵詞
Java不用goto關鍵詞。在C#中,goto允許你轉到指定的標簽。不過,C#以特別謹慎的態度對待goto,比如它不允許goto轉入到語句塊的內部。在Java中,你可以用帶標簽的語句加上break或continue取代C#中的goto。

2.8、聲明數組
在Java中,數組的聲明方法非常靈活,實際上有許多種聲明方法都屬於合法的方法。例如,下面的幾行代碼是等價的:

int[] x = { 0, 1, 2, 3 };
int x[] = { 0, 1, 2, 3 };

但在C#中,只有第一行代碼合法,[]不能放到變數名字之後。

2.9、包
在C#中,包(Package)被稱為名稱空間。把名稱空間引入C#程序的關鍵詞是「using」。例如,「using System;」這個語句引入了System名稱空間。

然而,與Java不同的是,C#允許為名稱空間或者名稱空間中的類指定別名:

using TheConsole = System.Console;
public class Hello {
public static void Main() {
TheConsole.WriteLine("使用別名");
}
}

雖然從概念上看,Java的包類似於.NET的名稱空間。然而,兩者的實現方式不同。在Java中,包的名字同時也是實際存在的實體,它決定了放置.java文件的目錄結構。在C#校�錮淼陌�吐嘸�拿�浦�涫峭耆�擲氳模�簿褪撬擔��瓶占淶拿�植換岫暈錮淼拇虯�絞講��魏斡跋臁T贑#中,每一個源代碼文件可以從屬於多個名稱空間,而且它可以容納多個公共類。

.NET中包的實體稱為程序集(Assembly)。每一個程序集包含一個manifest結構。manifest列舉程序集所包含的文件,控制哪些類型和資源被顯露到程序集之外,並把對這些類型和資源的引用映射到包含這些類型與資源的文件。程序集是自包含的,一個程序集可以放置到單一的文件之內,也可以分割成多個文件。.NET的這種封裝機制解決了DLL文件所面臨的問題,即臭名昭著的DLL Hell問題。

2.10、默認包
在Java中,java.lang包是默認的包,它無需顯式導入就已經自動包含。例如,要把一些文本輸出到控制台,你可以使用下面的代碼:

System.out.println("Hello world from Java");

C#中不存在默認的包。如果要向控制台輸出文本,你使用System名稱空間Console對象的WriteLine方法。但是,你必須顯式導入所有的類。代碼如下:

using System;
public class Hello {
public static void Main() {
Console.WriteLine("Hello world from C#");
}
}

2.11、面向對象
Java和C#都是完全面向對象的語言。在面向對象編程的三大原則方面,這兩種語言接近得不能再接近。

繼承:這兩種語言都支持類的單一繼承,但類可以實現多個介面。所有類都從一個公共的基類繼承。
封裝與可見性:無論是在Java還是C#中,你都可以決定類成員是否可見。除了C#的internal訪問修飾符之外,兩者的可見性機制非常相似。
多態性:Java和C#都支持某些形式的多態性機制,且兩者實現方法非常類似。
2.12、可訪問性
類的每個成員都有特定類型的可訪問性。C#中的訪問修飾符與Java中的基本對應,但多出了一個internal。簡而言之,C#有5種類型的可訪問性,如下所示:

public:成員可以從任何代碼訪問。
protected:成員只能從派生類訪問。
internal:成員只能從同一程序集的內部訪問。
protected internal:成員只能從同一程序集內的派生類訪問。
private:成員只能在當前類的內部訪問。
2.13、派生類
在Java中,我們用關鍵詞「extends」實現繼承。C#採用了C++的類派生語法。例如,下面的代碼顯示了如何派生父類Control從而創建出新類Button:

public class Button: Control { . . }

2.14、最終類
由於C#中不存在final關鍵詞,如果想要某個類不再被派生,你可以使用sealed關鍵詞,如下例所示:

sealed class FinalClass { . . }

2.15、介面
介面這個概念在C#和Java中非常相似。介面的關鍵詞是interface,一個介面可以擴展一個或者多個其他介面。按照慣例,介面的名字以大寫字母「I」開頭。下面的代碼是C#介面的一個例子,它與Java中的介面完全一樣:

interface IShape { void Draw(); }

擴展介面的語法與擴展類的語法一樣。例如,下例的IRectangularShape介面擴展IShape介面(即,從IShape介面派生出IRectangularShape介面)。

interface IRectangularShape: IShape { int GetWidth(); }

如果你從兩個或者兩個以上的介面派生,父介面的名字列表用逗號分隔,如下面的代碼所示:

interface INewInterface: IParent1, IParent2 { }

然而,與Java不同,C#中的介面不能包含域(Field)。

另外還要注意,在C#中,介面內的所有方法默認都是公用方法。在Java中,方法聲明可以帶有public修飾符(即使這並非必要),但在C#中,顯式為介面的方法指定public修飾符是非法的。例如,下面的C#介面將產生一個編譯錯誤。

interface IShape { public void Draw(); }

2.16、is和as操作符
C#中的is操作符與Java中的instanceof操作符一樣,兩者都可以用來測試某個對象的實例是否屬於特定的類型。在Java中沒有與C#中的as操作符等價的操作符。as操作符與is操作符非常相似,但它更富有「進取心」:如果類型正確的話,as操作符會嘗試把被測試的對象引用轉換成目標類型;否則,它把變數引用設置成null。

為正確理解as操作符,首先請考慮下面這個例子中is操作符的運用。這個例子包含一個IShape介面,以及兩個實現了IShape介面的類Rectangle和Circle。

using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
if (shape is Rectangle) {
Rectangle rectangle = (Rectangle) shape;
Console.WriteLine("Width : " + rectangle.GetWidth());
}
if (shape is Circle) {
Circle circle = (Circle) shape;
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}

編譯好代碼之後,用戶可以輸入「rectangle」或者「circle」作為Main方法的參數。如果用戶輸入的是「circle」,則shape被實例化成為一個Circle類型的對象;反之,如果用戶輸入的是「rectangle」,則shape被實例化成為Rectangle類型的對象。隨後,程序用is操作符測試shape的變數類型:如果shape是一個矩形,則shape被轉換成為Rectangle對象,我們調用它的GetWidth方法;如果shape是一個圓,則shape被轉換成為一個Circle對象,我們調用它的GetRadius方法。

如果使用as操作符,則上述代碼可以改成如下形式:

using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
Rectangle rectangle = shape as Rectangle;
if (rectangle != null) {
Console.WriteLine("Width : " + rectangle.GetWidth());
}
else {
Circle circle = shape as Circle;
if (circle != null)
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}

在上面代碼的粗體部分中,我們在沒有測試shape對象類型的情況下,就用as操作符把shape轉換成Rectangle類型的對象。如果shape正好是一個Rectangle,則shape被轉換成為Rectangle類型的對象並保存到rectangle變數,然後我們調用它的GetWidth方法。如果這種轉換失敗,則我們進行第二次嘗試。這一次,shape被轉換成為Circle類型的對象並保存到circle變數。如果shape確實是一個Circle對象,則circle現在引用了一個Circle對象,我們調用它的GetRadius方法。

2.17、庫
C#沒有自己的類庫。但是,C#共享了.NET的類庫。當然,.NET類庫也可以用於其他.NET語言,比如VB.NET或者JScript.NET。值得一提的是StringBuilder類,它是對String類的補充。StringBuilder類與Java的StringBuffer類非常相似。

2.18、垃圾收集
C++已經讓我們認識到手工管理內存是多麼缺乏效率和浪費時間。當你在C++中創建了一個對象,你就必須手工地拆除這個對象。代碼越復雜,這個任務也越困難。Java用垃圾收集器來解決這個問題,由垃圾收集器搜集不再使用的對象並釋放內存。C#同樣採用了這種方法。應該說,如果你也在開發一種新的OOP語言,追隨這條道路是一種非常自然的選擇。C#仍舊保留了C++的內存手工管理方法,它適合在速度極端重要的場合使用,而在Java中這是不允許的。

2.19、異常處理
如果你聽說C#使用與Java相似的異常處理機制,你不會為此而驚訝,對吧?在C#中,所有的異常都從一個名為Exception的類派生(聽起來很熟悉?)另外,正如在Java中一樣,你還有熟悉的try和catch語句。Exception類屬於.NET System名稱空間的一部分。

三、Java沒有的功能
C#出生在Java成熟之後,因此,C#擁有一些Java(目前)還沒有的絕妙功能也就不足為奇。

3.1、枚舉器
枚舉器即enum類型(Enumerator,或稱為計數器),它是一個相關常量的集合。精確地說,enum類型聲明為一組相關的符號常量定義了一個類型名字。例如,你可以創建一個名為Fruit(水果)的枚舉器,把它作為一個變數值的類型使用,從而把變數可能的取值范圍限制為枚舉器中出現的值。

public class Demo {
public enum Fruit {
Apple, Banana, Cherry, Durian
}
public void Process(Fruit fruit) {
switch (fruit) {
case Fruit.Apple:
...
break;
case Fruit.Banana:
...
break;
case Fruit.Cherry:
...
break;
case Fruit.Durian:
...
break;
}
}
}

在上例的Process方法中,雖然你可以用int作為myVar變數的類型,但是,使用枚舉器Fruit之後,變數的取值范圍限制到了Applet、Banana、Cherry和Durian這幾個值之內。與int相比,enum的可讀性更好,自我說明能力更強。

3.2、結構
結構(Struct)與類很相似。然而,類是作為一種引用類型在堆中創建,而結構是一種值類型,它存儲在棧中或者是嵌入式的。因此,只要謹慎運用,結構要比類快。結構可以實現介面,可以象類一樣擁有成員,但結構不支持繼承。

然而,簡單地用結構來取代類可能導致慘重損失。這是因為,結構是以值的方式傳遞,由於這種傳遞方式要把值復制到新的位置,所以傳遞一個「肥胖的」結構需要較大的開銷。而對於類,傳遞的時候只需傳遞它的引用。

下面是一個結構的例子。注意它與類非常相似,只要把單詞「struct」替換成「class」,你就得到了一個類。

struct Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

3.3、屬性
C#類除了可以擁有域(Field)之外,它還可以擁有屬性(Property)。屬性是一個與類或對象關聯的命名的特徵。屬性是域的一種自然擴展――兩者都是有類型、有名字的類成員。然而,和域不同的是,屬性不表示存儲位置;相反,屬性擁有存取器(accessor),存取器定義了讀取或者寫入屬性值時必須執行的代碼。因此,屬性提供了一種把動作和讀取、寫入對象屬性值的操作關聯起來的機制,而且它們允許屬性值通過計算得到。

在C#中,屬性通過屬性聲明語法定義。屬性聲明語法的第一部分與域聲明很相似,第二部分包括一個set過程和/或一個get過程。例如,在下面的例子中,PropertyDemo類定義了一個Prop屬性。

public class PropertyDemo {
private string prop;
public string Prop {
get {
return prop;
}
set {
prop = value;
}
}
}

如果屬性既允許讀取也允許寫入,如PropertyDemo類的Prop屬性,則它同時擁有get和set存取過程。當我們讀取屬性的值時,get存取過程被調用;當我們寫入屬性值時,set存取過程被調用。在set存取過程中,屬性的新值在一個隱含的value參數中給出。

與讀取和寫入域的方法一樣,屬性也可以用同樣的語法讀取和寫入。例如,下面的代碼實例化了一個PropertyDemo類,然後寫入、讀取它的Prop屬性。

PropertyDemo pd = new PropertyDemo();
pd.Prop = "123"; // set
string s = pd.Prop; // get

3.4、以引用方式傳遞簡單數據類型的參數
在Java中,當你把一個簡單數據類型的值作為參數傳遞給方法時,參數總是以值的方式傳遞――即,系統將為被調用的方法創建一個參數值的副本。在C#中,你可以用引用的方式傳遞一個簡單數據類型的值。此時,被調用的方法將直接使用傳遞給它的那個值――也就是說,如果在被調用方法內部修改了參數的值,則原來的變數值也隨之改變。

在C#中以引用方式傳遞值時,我們使用ref關鍵詞。例如,如果編譯並運行下面的代碼,你將在控制台上看到輸出結果16。注意i值被傳遞給ProcessNumber之後是如何被改變的。

using System;
public class PassByReference {
public static void Main(String[] args) {
int i = 8;
ProcessNumber(ref i);
Console.WriteLine(i);
}
public static void ProcessNumber(ref int j) {
j = 16;
}
}

C#中還有一個允許以引用方式傳遞參數的關鍵詞out,它與ref相似。但是,使用out時,作為參數傳遞的變數在傳遞之前不必具有已知的值。在上例中,如果整數i在傳遞給ProcessNumber方法之前沒有初始化,則代碼將出錯。如果用out來取代ref,你就可以傳遞一個未經初始化的值,如下面這個修改後的例子所示。

using System;
public class PassByReference {
public static void Main(String[] args) {
int i;
ProcessNumber(out i);
Console.WriteLine(i);
}
public static void ProcessNumber(out int j) {
j = 16;
}
}

經過修改之後,雖然i值在傳遞給ProcessNumber方法之前沒有初始化,但PassByReference類能夠順利通過編譯。

3.5、C#保留了指針
對於那些覺得自己能夠恰到好處地運用指針並樂意手工進行內存管理的開發者來說,在C#中,他們仍舊可以用既不安全也不容易使用的「古老的」指針來提高程序的性能。C#提供了支持「不安全」(unsafe)代碼的能力,這種代碼能夠直接操作指針,能夠「固定」對象以便臨時地阻止垃圾收集器移動對象。無論從開發者還是用戶的眼光來看,這種對「不安全」代碼的支持其實是一種安全功能。「不安全」的代碼必須用unsafe關鍵詞顯式地標明,因此開發者不可能在無意之中使用「不安全」的代碼。同時,C#編譯器又和執行引擎協作,保證了「不安全」的代碼不能偽裝成為安全代碼。

using System;
class UsePointer {
unsafe static void PointerDemo(byte[] arr) {
.
.
}
}

C#中的unsafe代碼適合在下列情形下使用:當速度極端重要時,或者當對象需要與現有的軟體(比如COM對象或者DLL形式的C代碼)交互時。

3.6、代理
代理(delegate)可以看作C++或者其他語言中的函數指針。然而,與函數指針不同的是,C#中的代理是面向對象的、類型安全的、可靠的。而且,函數指針只能用來引用靜態函數,但代理既能夠引用靜態方法,也能夠引用實例方法。代理用來封裝可調用方法。你可以在類裡面編寫方法並在該方法上創建代理,此後這個代理就可以被傳遞到第二個方法。這樣,第二個方法就可以調用第一個方法。

代理是從公共基類System.Delegate派生的引用類型。定義和使用代理包括三個步驟:聲明,創建實例,調用。代理用delegate聲明語法聲明。例如,一個不需要參數且沒有返回值的代理可以用如下代碼聲明:

delegate void TheDelegate();

創建代理實例的語法是:使用new關鍵詞,並引用一個實例或類方法,該方法必須符合代理指定的特徵。一旦創建了代理的實例,我們就可以用調用方法的語法調用它。

3.7、包裝和解除包裝
在面向對象的編程語言中,我們通常使用的是對象。但為了提高速度,C#也提供了簡單數據類型。因此,C#程序既包含一大堆的對象,又有大量的值。在這種環境下,讓這兩者協同工作始終是一個不可迴避的問題,你必須要有一種讓引用和值進行通信的方法。

在C#以及.NET運行時環境中,這個「通信」問題通過包裝(Boxing)和解除包裝(Unboxing)解決。包裝是一種讓值類型看起來象引用類型的處理過程。當一個值類型(簡單數據類型)被用於一個要求或者可以使用對象的場合時,包裝操作自動進行。包裝一個value-type值的步驟包括:分配一個對象實例,然後把value-type值復制到對象實例。

解除包裝所執行的動作與包裝相反,它把一個引用類型轉換成值類型。解除包裝操作的步驟包括:首先檢查並確認對象實例確實是給定value-type的一個經過包裝的值,然後從對象實例復制出值。

Java對該問題的處理方式略有不同。Java為每一種簡單數據類型提供了一個對應的類封裝器。例如,用Integer類封裝int類型,用Byte類封裝byte類型。

【結束語】本文為你比較了C#和Java。這兩種語言很相似,然而,說C#是Java的克隆或許已經大大地言過其實。面向對象、中間語言這類概念並不是什麼新東西。如果你准備設計一種面向對象的新語言,而且它必須在一個受管理的安全環境內運行,你難道不會搞出與C#差不多的東西嗎?

閱讀全文

與枚舉量過多jvm編譯失敗相關的資料

熱點內容
直播完整源碼java 瀏覽:542
二戰蘇日電影 瀏覽:37
台灣販賣婦女的電影 瀏覽:66
監控網頁端監控源碼 瀏覽:888
蘋果m1晶元前端開發編程問題 瀏覽:578
無法訪問共享文件夾找不到路徑 瀏覽:243
李恩美主演電影全集 瀏覽:665
主角是科技天才被國家保護的小說 瀏覽:703
電影院和好友老公親熱 瀏覽:883
長在脖子上像睾丸的電影 瀏覽:914
男主人公是醫生的一部韓國電影 瀏覽:336
投原子彈的電影 瀏覽:708
日本電影自行車反轉有個女的坐在上面 瀏覽:125
java瘋狂講義3 瀏覽:198
古裝4級片 瀏覽:386
清朝十部頂級電影 瀏覽:987
泰國鬼片哪兒看 瀏覽:587
電影睡起你的墳墓 瀏覽:968
有關動物的真人版英文電影 瀏覽:47
怎麼寫演算法設計 瀏覽:44