Java命令

Java虚拟机的工作是运行Java应用程序。和其他类型的应用程序一样,Java应用程序也需要一个入口点,这个入口点就是我们熟知的main()方法。如果一个类包含main()方法,这个类就可以用来启动Java应用程序,我们把这个类叫作主类。最简单的Java程序是只有一个main()方法的类,如著名的HelloWorld程序。

  1. public class HelloWorld {
  2.  
  3. public static void main(String[] args) {
  4. System.out.println("Hello, world!");
  5. }
  6.  
  7. }

  

那么Java虚拟机如何知道我们要从哪个类启动应用程序呢?对此,Java虚拟机规范没有明确规定。也就是说,是由虚拟机实现自行决定的。比如Oracle的Java虚拟机实现是通过java命令来启动的,主类名由命令行参数指定。java命令有如下4种形式:

  1. java [-options] class [args]
  2. java [-options] -jar jarfile [args]
  3. javaw [-options] class [args]
  4. javaw [-options] -jar jarfile [args]

  

可以向java命令传递三组参数:选项、主类名(或者JAR文件名)。

和main()方法参数。选项由减号“-”开头。通常,第一个非选项参数给出主类的完全限定名(fully qualified class name)。但是如果用户提供了–jar选项,则第一个非选项参数表示JAR文件名,java命令必须从这个JAR文件中寻找主类。javaw命令和java命令几乎一样,唯一的差别在于,javaw命令不显示命令行窗口,因此特别适合用于启动GUI(图形用户界面)应用程序。

选项可以分为两类:标准选项和非标准选项。标准选项比较稳定,不会轻易变动。非标准选项以-X开头。非标准选项中有一部分是高级选项,以-XX开头。表1-1列出了java命令常用的选项及其用途。

表1-1   java命令常用选项及其用途
选项 用途
-version 输出版本信息,然后退出
-?/-help 输出帮助信息,然后退出
-cp/-classpath 指定用户类路径
-Dproperty=value 设置Java系统属性
-Xms<size> 设置初始堆空间大小
-Xmx<size> 设置最大堆空间大小
-Xss<size> 设置线程栈空间大小

编写命令行工具

我们将以Go语言来阐述Java虚拟机,先定义一个结构体来表示命令行选项和参数。在ch01目录下创建cmd.go文件,用你喜欢的文本编辑器打开它,然后在其中定义Cmd结构体,代码如下:

  1. package main
  2.  
  3. import "flag"
  4. import "fmt"
  5. import "os"
  6.  
  7. type Cmd struct {
  8. helpFlag bool
  9. versionFlag bool
  10. cpOption string
  11. class string
  12. args []string
  13. }

  

在Java语言中,API一般以类库的形式提供。在Go语言中,API则是以包(package)的形式提供。包可以向用户提供常量、变量、结构体以及函数等。Java内置了丰富的类库,Go也同样内置了功能强大的包,如:fmt、os和flag包。

os包定义了一个Args变量,其中存放传递给命令行的全部参数。如果直接处理os.Args变量,需要写很多代码。还好Go语言内置了flag包,这个包可以帮助我们处理命令行选项。有了flag包,我们的工作就简单了很多。继续编辑cmd.go文件,在其中定义parseCmd()函数 ,代码如下:

  1. func parseCmd() *Cmd {
  2. cmd := &Cmd{}
  3. flag.Usage = printUsage
  4. flag.BoolVar(&cmd.helpFlag, "help", false, "print help message")
  5. flag.BoolVar(&cmd.helpFlag, "?", false, "print help message")
  6. flag.BoolVar(&cmd.versionFlag, "version", false, "print version and exit")
  7. flag.StringVar(&cmd.cpOption, "classpath", "", "classpath")
  8. flag.StringVar(&cmd.cpOption, "cp", "", "classpath")
  9. flag.Parse()
  10. args := flag.Args()
  11. if len(args) > 0 {
  12. cmd.class = args[0]
  13. cmd.args = args[1:]
  14. }
  15.  
  16. return cmd
  17. }

  

首先设置flag.Usage变量,把printUsage()函数赋值给它;然后调用flag包提供的各种Var()函数设置需要解析的选项;接着调用Parse()函数解析选项。如果Parse()函数解析失败,它就调用printUsage()函数把命令的用法打印到控制台。printUsage()函数的代码如下:

  1. func printUsage() {
  2. fmt.Printf("Usage: %s [-options] class [args...]\n", os.Args[0])
  3. }

  

如果解析成功,调用flag.Args()函数可以捕获其他没有被解析的参数。其中第一个参数就是主类名,剩下的是要传递给主类的参数。这样,用了不到40行代码,我们的命令行工具就编写完了。下面来测试它。

在ch01目录下创建main.go文件,然后输入下面的代码:

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func main() {
  6. cmd := parseCmd()
  7.  
  8. if cmd.versionFlag {
  9. fmt.Println("version 0.0.1")
  10. } else if cmd.helpFlag || cmd.class == "" {
  11. printUsage()
  12. } else {
  13. startJVM(cmd)
  14. }
  15. }
  16.  
  17. func startJVM(cmd *Cmd) {
  18. fmt.Printf("classpath:%s class:%s args:%v\n",
  19. cmd.cpOption, cmd.class, cmd.args)
  20. }

  

注意,与cmd.go文件一样,main.go文件的包名也是main。在Go语言中,main是一个特殊的包,这个包所在的目录(可以叫作任何名字)会被编译为可执行文件。Go程序的入口也是main()函数,但是不接收任何参数,也不能有返回值。main()函数先调用ParseCommand()函数解析命令行参数,如果一切正常,则调用startJVM()函数启动Java虚拟机。如果解析出现错误,或者用户输入了-help选项,则调用PrintUsage()函数打印出帮助信息。如果用户输入了-version选项,则输出版本信息。

打开命令行窗口,执行下面的命令:

  1. go install jvmgo\ch01

  

打开命令行窗口,执行命令go install jvmgo\ch01命令执行完毕后,如果没有看到任何输出就证明编译成功了,此时在{GOPATH}\bin目录下会出现ch01.exe文件。现在,可以用各种参数进行测试:

  1. D:\go_space\bin>ch01.exe
  2. Usage: ch01.exe [-options] class [args...]
  3.  
  4. D:\go_space\bin>ch01.exe -version
  5. version 0.0.1
  6.  
  7. D:\go_space\bin>ch01.exe -help
  8. Usage: ch01.exe [-options] class [args...]
  9.  
  10. D:\go_space\bin>ch01.exe -version
  11. version 0.0.1
  12.  
  13. D:\go_space\bin>ch01.exe -cp foo/bar MyApp arg1 arg2
  14. classpath:foo/bar class:MyApp args:[arg1 arg2] 

类路径

之前我们曾经介绍过HelloWorld类,加载HelloWorld类之前,首先要加载它的超类,也就是java.lang.Object。在调用main()方法之前,因为虚拟机需要准备好参数数组,所以需要加载java.lang.String和java.lang.String[]类。把字符串打印到控制台还需要加载java.lang.System类,等等。那么,Java虚拟机从哪里寻找这些类呢?

Java虚拟机规范并没有规定虚拟机应该从哪里寻找类,因此不同的虚拟机实现可以采用不同的方法。Oracle的Java虚拟机实现根据类路径(class path)来搜索类。按照搜索的先后顺序,类路径可以分为以下3个部分:

  • 启动类路径(bootstrap classpath)
  • 扩展类路径(extension classpath)
  • 用户类路径(user classpath)

启动类路径默认对应jre\lib目录,Java标准库(大部分在rt.jar里)位于该路径。扩展类路径默认对应jre\lib\ext目录,使用Java扩展机制的类位于这个路径。我们自己实现的类,以及第三方类库则位于用户类路径。可以通过-Xbootclasspath选项修改启动类路径,不过通常并不需要这样做。

用户类路径的默认值是当前目录,也就是“.”。可以设置CLASSPATH环境变量来修改用户类路径,但是这样做不够灵活,所以不推荐使用。更好的办法是给java命令传递-classpath(或简写为-cp)选项。-classpath/-cp选项的优先级更高,可以覆盖CLASSPATH环境变量设置。-classpath/-cp选项既可以指定目录,也可以指定JAR文件或者ZIP文件,如下:

  1. java -cp path\to\classes ...
  2. java -cp path\to\lib1.jar ...
  3. java -cp path\to\lib2.zip ...

  

还可以同时指定多个目录或文件,用分隔符分开即可。分隔符因操作系统而异。在Windows系统下是分号,在类UNIX(包括Linux、Mac OS X等)系统下是冒号。例如在Windows下:

  1. java -cp path\to\classes;lib\a.jar;lib\b.jar;lib\c.zip ...

  

从Java 6开始,还可以使用通配符(*)指定某个目录下的所有JAR文件,格式如下:

  1. java -cp classes;lib\* ...

  

把ch01目录复制一份,然后改名为ch02。因为要创建的源文件都在classpath包中,所以在ch02目录中创建一个classpath子目录。现在目录结构看起来应该是这样:

  1. ${GOPATH}\src
  2. |-jvmgo
  3. |-ch01
  4. |-ch02
  5. |-classpath
  6. |-cmd.go
  7. |-main.go

  

我们的Java虚拟机将使用JDK的启动类路径来寻找和加载Java标准库中的类,因此需要某种方式指定jre目录的位置。命令行选项是个不错的选择,所以增加一个非标准选项-Xjre。打开ch02\cmd.go,修改Cmd结构体,添加XjreOption字段,代码如下:

  1. type Cmd struct {
  2. helpFlag bool
  3. versionFlag bool
  4. cpOption string
  5. XjreOption string
  6. class string
  7. args []string
  8. }

  

parseCmd()函数也要相应修改,代码如下:

  1. func parseCmd() *Cmd {
  2. cmd := &Cmd{}
  3.  
  4. flag.Usage = printUsage
  5. flag.BoolVar(&cmd.helpFlag, "help", false, "print help message")
  6. flag.BoolVar(&cmd.helpFlag, "?", false, "print help message")
  7. flag.BoolVar(&cmd.versionFlag, "version", false, "print version and exit")
  8. flag.StringVar(&cmd.cpOption, "classpath", "", "classpath")
  9. flag.StringVar(&cmd.cpOption, "cp", "", "classpath")
  10. flag.StringVar(&cmd.XjreOption, "Xjre", "", "path to jre")
  11. flag.Parse()
  12.  
  13. args := flag.Args()
  14. if len(args) > 0 {
  15. cmd.class = args[0]
  16. cmd.args = args[1:]
  17. }
  18.  
  19. return cmd
  20. }

  

实现类路径

可以把类路径想象成一个大的整体,它由启动类路径、扩展类路径和用户类路径三个小路径构成。三个小路径又分别由更小的路径构成,这很像组合模式(composite pattern)。

Entry接口

先定义一个接口来表示类路径项。在ch02\classpath目录下创建entry.go文件,在其中定义Entry接口,代码如下:

  1. package classpath
  2.  
  3. import "os"
  4. import "strings"
  5.  
  6. const pathListSeparator = string(os.PathListSeparator)
  7.  
  8. type Entry interface {
  9. readClass(className string) ([]byte, Entry, error)
  10. String() string
  11. }
  12.  
  13. func newEntry(path string) Entry {……}

  

常量pathListSeparator是string类型,存放路径分隔符,后面会用到。Entry接口中有两个方法。readClass()方法负责寻找和加载class文件;String()方法的作用相当于Java中的toString(),用于返回变量的字符串表示。

readClass()方法的参数是class文件的相对路径,路径之间用斜线(/)分隔,文件名有.class后缀。比如要读取java.lang.Object类,传入的参数应该是java/lang/Object.class。返回值是读取到的字节数据、最终定位到class文件的Entry,以及错误信息。Go的函数或方法允许返回多个值,按照惯例,可以使用最后一个返回值作为错误信息。

newEntry()函数根据参数创建不同类型的Entry实例,代码如下:

  1. func newEntry(path string) Entry {
  2. if strings.Contains(path, pathListSeparator) {
  3. return newCompositeEntry(path)
  4. }
  5.  
  6. if strings.HasSuffix(path, "*") {
  7. return newWildcardEntry(path)
  8. }
  9.  
  10. if strings.HasSuffix(path, ".jar") || strings.HasSuffix(path, ".JAR") ||
  11. strings.HasSuffix(path, ".zip") || strings.HasSuffix(path, ".ZIP") {
  12.  
  13. return newZipEntry(path)
  14. }
  15.  
  16. return newDirEntry(path)
  17. }

  

Entry接口有4个实现,分别是DirEntry、ZipEntry、CompositeEntry和WildcardEntry。下面分别介绍每一种实现。

DirEntry

在4种实现中,DirEntry相对简单一些,表示目录形式的类路径。在ch02\classpath目录下创建entry_dir.go文件,在其中定义DirEntry结构体,代码如下:

  1. package classpath
  2.  
  3. import "io/ioutil"
  4. import "path/filepath"
  5.  
  6. type DirEntry struct {
  7. absDir string
  8. }
  9.  
  10. func newDirEntry(path string) *DirEntry {……}
  11.  
  12. func (self *DirEntry) readClass(className string) ([]byte, Entry, error) {……}
  13.  
  14. func (self *DirEntry) String() string {……}

  

DirEntry只有一个字段,用于存放目录的绝对路径。和Java语言不同,Go结构体不需要显示实现接口,只要方法匹配即可。Go没有专门的构造函数,可以使用new开头的函数来创建结构体实例,并把这类函数称为构造函数。newDirEntry()函数的代码如下:

  1. func newDirEntry(path string) *DirEntry {
  2. absDir, err := filepath.Abs(path)
  3. if err != nil {
  4. panic(err)
  5. }
  6. return &DirEntry{absDir}
  7. }

  

newDirEntry()先把参数转换成绝对路径,如果转换过程出现错误,则调用panic()函数终止程序执行,否则创建DirEntry实例并返回。

下面介绍readClass()方法:

  1. func (self *DirEntry) readClass(className string) ([]byte, Entry, error) {
  2. fileName := filepath.Join(self.absDir, className)
  3. data, err := ioutil.ReadFile(fileName)
  4. return data, self, err
  5. }

  

readClass()先把目录和class文件名拼成一个完整的路径,然后调用ioutil包提供的ReadFile()函数读取class文件内容,最后返回。String()方法很简单,直接返回目录,代码如下:

  1. func (self *DirEntry) String() string {
  2. return self.absDir
  3. }

  

ZipEntry

ZipEntry表示ZIP或JAR文件形式的类路径。在ch02\classpath目录下创建entry_zip.go文件,在其中定义ZipEntry结构体,代码如下:

  1. package classpath
  2.  
  3. import "archive/zip"
  4. import "errors"
  5. import "io/ioutil"
  6. import "path/filepath"
  7.  
  8. type ZipEntry struct {
  9. absPath string
  10. }
  11.  
  12. func newZipEntry(path string) *ZipEntry {……}
  13.  
  14. func (self *ZipEntry) readClass(className string) ([]byte, Entry, error) {……}
  15.  
  16. func (self *ZipEntry) String() string {……}

  

absPath字段存放ZIP或JAR文件的绝对路径。构造函数和String()与DirEntry大同小异,代码如下:

  1. func newZipEntry(path string) *ZipEntry {
  2. absPath, err := filepath.Abs(path)
  3. if err != nil {
  4. panic(err)
  5. }
  6. return &ZipEntry{absPath}
  7. }

  

下面重点介绍如何从ZIP文件中提取class文件,代码如下:

  1. func (self *ZipEntry) readClass(className string) ([]byte, Entry, error) {
  2. r, err := zip.OpenReader(self.absPath)
  3. if err != nil {
  4. return nil, nil, err
  5. }
  6.  
  7. defer r.Close()
  8. for _, f := range r.File {
  9. if f.Name == className {
  10. rc, err := f.Open()
  11. if err != nil {
  12. return nil, nil, err
  13. }
  14.  
  15. defer rc.Close()
  16. data, err := ioutil.ReadAll(rc)
  17. if err != nil {
  18. return nil, nil, err
  19. }
  20.  
  21. return data, self, nil
  22. }
  23. }
  24.  
  25. return nil, nil, errors.New("class not found: " + className)
  26. }

  

CompositeEntry

在ch02\classpath目录下创建entry_composite.go文件,在其中定义CompositeEntry结构体,代码如下:

  1. package classpath
  2.  
  3. import "errors"
  4. import "strings"
  5.  
  6. type CompositeEntry []Entry
  7.  
  8. func newCompositeEntry(pathList string) CompositeEntry {……}
  9.  
  10. func (self CompositeEntry) readClass(className string) ([]byte, Entry, error) {……}
  11.  
  12. func (self CompositeEntry) String() string {……}

  

如前所述,CompositeEntry由更小的Entry组成,正好可以表示成[]Entry。在Go语言中,数组属于比较低层的数据结构,很少直接使用。大部分情况下,使用更便利的slice类型。构造函数把参数(路径列表)按分隔符分成小路径,然后把每个小路径都转换成具体的Entry实例,代码如下:

  1. func newCompositeEntry(pathList string) CompositeEntry {
  2. compositeEntry := []Entry{}
  3.  
  4. for _, path := range strings.Split(pathList, pathListSeparator) {
  5. entry := newEntry(path)
  6. compositeEntry = append(compositeEntry, entry)
  7. }
  8.  
  9. return compositeEntry
  10. }

  

相信读者已经想到readClass()方法的代码了:依次调用每一个子路径的readClass()方法,如果成功读取到class数据,返回数据即可;如果收到错误信息,则继续;如果遍历完所有的子路径还没有找到class文件,则返回错误。readClass()方法的代码如下:

  1. func (self CompositeEntry) readClass(className string) ([]byte, Entry, error) {
  2. for _, entry := range self {
  3. data, from, err := entry.readClass(className)
  4. if err == nil {
  5. return data, from, nil
  6. }
  7. }
  8.  
  9. return nil, nil, errors.New("class not found: " + className)
  10. }

  

String()方法也不复杂。调用每一个子路径的String()方法,然后把得到的字符串用路径分隔符拼接起来即可,代码如下:

  1. func (self CompositeEntry) String() string {
  2. strs := make([]string, len(self))
  3.  
  4. for i, entry := range self {
  5. strs[i] = entry.String()
  6. }
  7.  
  8. return strings.Join(strs, pathListSeparator)
  9. }

  

WildcardEntry

WildcardEntry实际上也是CompositeEntry,所以就不再定义新的类型了。在ch02\classpath目录下创建entry_wildcard.go文件,在其中定义newWildcardEntry()函数,代码如下:

  1. package classpath
  2.  
  3. import "os"
  4. import "path/filepath"
  5. import "strings"
  6.  
  7. func newWildcardEntry(path string) CompositeEntry {
  8. baseDir := path[:len(path)-1] // remove *
  9. compositeEntry := []Entry{}
  10.  
  11. walkFn := func(path string, info os.FileInfo, err error) error {……}
  12.  
  13. filepath.Walk(baseDir, walkFn)
  14.  
  15. return compositeEntry
  16. }

  

首先把路径末尾的星号去掉,得到baseDir,然后调用filepath包的Walk()函数遍历baseDir创建ZipEntry。Walk()函数的第二个参数也是一个函数,walkFn变量的定义如下:

  1. walkFn := func(path string, info os.FileInfo, err error) error {
  2. if err != nil {
  3. return err
  4. }
  5. if info.IsDir() && path != baseDir {
  6. return filepath.SkipDir
  7. }
  8. if strings.HasSuffix(path, ".jar") || strings.HasSuffix(path, ".JAR") {
  9. jarEntry := newZipEntry(path)
  10. compositeEntry = append(compositeEntry, jarEntry)
  11. }
  12. return nil
  13. }

  

在walkFn中,根据后缀名选出JAR文件,并且返回SkipDir跳过子目录(通配符类路径不能递归匹配子目录下的JAR文件)。

Classpath

Entry接口和4个实现介绍完了,接下来实现Classpath结构体。还是在ch02\classpath目录下创建classpath.go文件,把下面的代码输入进去。

  1. package classpath
  2.  
  3. import "os"
  4. import "path/filepath"
  5.  
  6. type Classpath struct {
  7. bootClasspath Entry
  8. extClasspath Entry
  9. userClasspath Entry
  10. }
  11.  
  12. func Parse(jreOption, cpOption string) *Classpath {……}
  13.  
  14. func (self *Classpath) ReadClass(className string) ([]byte, Entry, error) {……}
  15.  
  16. func (self *Classpath) String() string {……}

  

Classpath结构体有三个字段,分别存放三种类路径。Parse()函数使用-Xjre选项解析启动类路径和扩展类路径,使用-classpath/-cp选项解析用户类路径,代码如下:

  1. func Parse(jreOption, cpOption string) *Classpath {
  2. cp := &Classpath{}
  3. cp.parseBootAndExtClasspath(jreOption)
  4. cp.parseUserClasspath(cpOption)
  5. return cp
  6. }

  

parseBootAndExtClasspath()方法的代码如下:

  1. func (self *Classpath) parseBootAndExtClasspath(jreOption string) {
  2. jreDir := getJreDir(jreOption)
  3.  
  4. // jre/lib/*
  5. jreLibPath := filepath.Join(jreDir, "lib", "*")
  6. self.bootClasspath = newWildcardEntry(jreLibPath)
  7.  
  8. // jre/lib/ext/*
  9. jreExtPath := filepath.Join(jreDir, "lib", "ext", "*")
  10. self.extClasspath = newWildcardEntry(jreExtPath)
  11. }

  

优先使用用户输入的-Xjre选项作为jre目录。如果没有输入该选项,则在当前目录下寻找jre目录。如果找不到,尝试使用JAVA_HOME环境变量。getJreDir()函数的代码如下:

  1. func getJreDir(jreOption string) string {
  2. if jreOption != "" && exists(jreOption) {
  3. return jreOption
  4. }
  5. if exists("./jre") {
  6. return "./jre"
  7. }
  8. if jh := os.Getenv("JAVA_HOME"); jh != "" {
  9. return filepath.Join(jh, "jre")
  10. }
  11. panic("Can not find jre folder!")
  12. }

  

exists()函数用于判断目录是否存在,代码如下:

  1. func exists(path string) bool {
  2. if _, err := os.Stat(path); err != nil {
  3. if os.IsNotExist(err) {
  4. return false
  5. }
  6. }
  7. return true
  8. }

  

parseUserClasspath()方法的代码相对简单一些,如下:

  1. func (self *Classpath) parseUserClasspath(cpOption string) {
  2. if cpOption == "" {
  3. cpOption = "."
  4. }
  5. self.userClasspath = newEntry(cpOption)
  6. }

  

如果用户没有提供-classpath/-cp选项,则使用当前目录作为用户类路径。ReadClass()方法依次从启动类路径、扩展类路径和用户类路径中搜索class文件,代码如下:

  1. func (self *Classpath) ReadClass(className string) ([]byte, Entry, error) {
  2. className = className + ".class"
  3. if data, entry, err := self.bootClasspath.readClass(className); err == nil {
  4. return data, entry, err
  5. }
  6. if data, entry, err := self.extClasspath.readClass(className); err == nil {
  7. return data, entry, err
  8. }
  9. return self.userClasspath.readClass(className)
  10. }

  

注意,传递给ReadClass()方法的类名不包含“.class”后缀。最后,String()方法返回用户类路径的字符串表示,代码如下:

  1. func (self *Classpath) String() string {
  2. return self.userClasspath.String()
  3. }

  

现在,我们来测试一下ch02的代码,打开ch02/main.go文件,添加两条import语句,代码如下:

  1. package main
  2.  
  3. import "fmt"
  4. import "strings"
  5. import "jvmgo/ch02/classpath"
  6.  
  7. func main() {……}
  8.  
  9. func startJVM(cmd *Cmd) {……}

  

main()函数不用变,重写startJVM()函数,代码如下:

  1. func startJVM(cmd *Cmd) {
  2. cp := classpath.Parse(cmd.XjreOption, cmd.cpOption)
  3. fmt.Printf("classpath:%v class:%v args:%v\n",
  4. cp, cmd.class, cmd.args)
  5.  
  6. className := strings.Replace(cmd.class, ".", "/", -1)
  7. classData, _, err := cp.ReadClass(className)
  8. if err != nil {
  9. fmt.Printf("Could not find or load main class %s\n", cmd.class)
  10. return
  11. }
  12.  
  13. fmt.Printf("class data:%v\n", classData)
  14. }

  

startJVM()先打印出命令行参数,然后读取主类数据,并打印到控制台,虽然还是无法真正启动Java虚拟机。打开命令行窗口,执行下面的命令编译代码:

  1. D:\go_space\src>go install jvmgo\ch02

  

编译成功后,在{GOPATH}\bin目录下出现ch02.exe文件。执行ch02.exe,指定好-Xjre选项和类名,就可以把class文件的内容打印出来:

  1. D:\go_space\bin>ch02.exe -Xjre "D:\JDK\jdk1.8.0_77\jre" java.lang.Object
  2. classpath:D:\go_space\bin class:java.lang.Object args:[]
  3. class data:[202 254 186……2 0 77]

  

Java虚拟机之搜索class文件的更多相关文章

  1. 深入理解Java虚拟机04--类结构文件

    一.程序存储格式 统一的程序存储格式:不同平台的虚拟机于所有平台都统一使用程序存储格式——字节码(ByteCode); Java 虚拟机不关心 Class 文件的来源,而只和“Class文件" ...

  2. 翻译Java虚拟机的结构

    英文原版:  https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html 直接谷歌翻译: Java SE规范 > Java虚拟机 ...

  3. 《深入理解 Java 虚拟机》读书笔记:类文件结构

    正文 一.无关性的基石 1.两种无关性 平台无关性: Java 程序的运行不受计算机平台的限制,"一次编写,到处运行". 语言无关性: Java 虚拟机只与 Class 文件关联, ...

  4. 深入Java虚拟机

    第一章:Java体系结构介绍 1.Java为什么重要?       Java是为网络而设计的,而Java这种适合网络环境的能力又是由其体系结构决定的,可以保证安全健壮和平台无关的程序通过网络传播. 2 ...

  5. 深入理解java虚拟机【Java虚拟机类生命周期】

    C/C++等纯编译语言从源码到最终执行一般要经历:编译.连接和运行三个阶段,连接是在编译期间完成,而java在编译期间仅仅是将源码编译为Java虚拟机可以识别的字节码Class类文件,Java虚拟机对 ...

  6. (转)《深入理解java虚拟机》学习笔记7——Java虚拟机类生命周期

    C/C++等纯编译语言从源码到最终执行一般要经历:编译.连接和运行三个阶段,连接是在编译期间完成,而java在编译期间仅仅是将源码编译为Java虚拟机可以识别的字节码Class类文件,Java虚拟机对 ...

  7. 深入Java虚拟机读书笔记第三章安全

    为什么需要安全性 Java的安全模型是其多个重要结构特点之一,它使Java成为适于网络环境的技术.Java安全模型侧重于保护终端用户免受从网络下载的.来自不可靠来源的.恶意程序(以及善于程序中的bug ...

  8. 读《深入理解Java虚拟机》

    Java虚拟机运行时数据区 对象的创建 Java创建对象,在语言层面上使用new关键字.虚拟机遇到new关键字时,会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的 ...

  9. 深入Java虚拟机(2)——Java的平台无关性

    一.平台无关性的好处 Java技术在网络环境下非常有用,其中一个关键理由是,用Java创建的可执行二进制程序,能够不加改变地运行于多个平台. 这样的平台无关性随之带来许多的好处.这将极大地减轻系统管理 ...

随机推荐

  1. Linux命令之文件重定向2

    linux中重定向用符号“>”表示,语法一般是 源文件 > 目标文件 1)创出.txt文件touch 1.txt 注意:创建文件夹用mkdir 2)向.txt文件中写入内容 注意:①cat ...

  2. Eclipse Push出现rejected - non-fast-forward错误

    在 Push到服务器时有时会出现 rejected - non-fast-forward 错误,这是由于pull的代码而远端发生改变,此时再提交之前你需要将远端的改变合并到本地上 参考:https:/ ...

  3. 关于tcp的keepalive

    先记录几个要点 只能用在面向连接的tcp中,对应对端的非正常关闭有效(对端服务器重启这种,也是正常关闭,FIN RST包都算) 只要是写入到缓冲区就认为OK,所以UDP不适合,所以如果有正常的网络交互 ...

  4. ELF格式文件符号表全解析及readelf命令使用方法

    http://blog.csdn.net/edonlii/article/details/8779075 1. 读取ELF文件头: $ readelf -h signELF Header:  Magi ...

  5. 【BZOJ4487】[JSOI2015] 染色问题(高维容斥)

    点此看题面 大致题意: 有一个\(n*m\)的矩形,先让你用\(C\)种颜色给它染色.每个格子可染色可不染色,但要求每行每列至少有一个小方格被染色,且每种颜色至少出现一次.求方案数. 高维容斥 显然题 ...

  6. DP上课覆盖知识点,POJ(1513)

    题目链接:http://poj.org/problem?id=1513 解题报告: 思路: 知识点从第二个开始扫,递推表达式是:minlec[i]=min(minlec[k])+1,并且要保证,tim ...

  7. 使用taobao cnpm 源解决npm无法安装module问题

    npm 安装nativescript时出现异常,一直停着不动.应该是源被墙了的问题可以使用淘宝仓库,执行下面的命令: alias cnpm="npm --registry=https://r ...

  8. 怎么让Sublime Text不自动打开最近的文件/项目

    "hot_exit": false,"remember_open_files": false,

  9. AngularJS 外部文件中的控制器

    在大型的应用程序中,通常是把控制器存储在外部的文件中. <!DOCTYPE html><html><head><meta http-equiv="C ...

  10. redis 面试题

    https://www.cnblogs.com/ftl1012/p/redisExam.html 1. 使用Redis有哪些好处? (1) 速度快,因为数据存在内存中,类似于HashMap,HashM ...