精华内容
下载资源
问答
  • Java程序main方法执行流程

    千次阅读 2019-12-06 18:42:40
    Java程序main方法执行流程 当我们编写完java源代码程序后,经过javac编译后,执行java命令执行这个程序时,是怎么一步步的调用到我们程序中的main方法的呢?今天通过查看OpenJdk的源码来揭开它的神秘面纱。 java命令...

    Java程序main方法执行流程

    当我们编写完java源代码程序后,经过javac编译后,执行java命令执行这个程序时,是怎么一步步的调用到我们程序中的main方法的呢?今天通过查看OpenJdk的源码来揭开它的神秘面纱。

    java命令是在安装jre/jdk时配置到系统环境路径中去的,执行java命令时会找到bin目录下的java可执行程序,并将我们编译后的java程序类名传递进去就可以执行了。

    java可执行程序是由C++编写的,它的内部会启动一个Java虚拟机实例。
    虚拟机启动入口函数位于src/java.base/share/native/launcher/main.c。

    // src/java.base/share/native/launcher/main.c
    
    // java程序启动入口主函数
    JNIEXPORT int main(int argc, char **argv) {
        
        ...
        
        return JLI_Laucher(margc, margv,
                            jargc, (const char**) jargv,
                             0, NULL,
                       VERSION_STRING,
                       DOT_VERSION,
                       (const_progname != NULL) ? const_progname : *margv,
                       (const_launcher != NULL) ? const_launcher : *margv,
                       jargc > 0,
                       const_cpwildcard, const_javaw, 0)
            
    }
    
    
    // src/java.base/share/native/libjli/java.c
    
    JNIEXPORT int JNICALL JLI_Launch(int argc,
                                    char** argv,
                                    int jargc,
                                    const char** jargv,
                                    int appclassc,
                                    const char** appclassv,
                                    const char* fullversion,
                                    const char* dotversion,
                                    const char* pname,
                                    const char* lname,
                                    jboolean javaargs,
                                    jboolean cpwildcard,
                                    jboolean javaw,
                                    jint ergo) {
                                    
                                    
        ...               
        
        return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
    }
    
    
    int ContinueInNewThread(InvocationFunction* ifn, 
                            jlong threadStackSize, 
                            int argc, 
                            char **argv, 
                            int mode, 
                            char *what, 
                            int ret) {
        int rslt;
        
        ...
        
        rslt = CallJavaMainInNewThread(threadStackSize, (void*)&args);
        return (ret != 0) ? ret : rslt;
    }
    
    //真正调用Java类的main函数入口
    int JavaMain(void* _args) {
        JNIEnv *env = 0;
     
        jclass mainClass = NULL;
        //找到main函数所在的类
        mainClass = LoadMainClass(env, mode, what);   
        //获取main函数的参数
        mainArgs = CreateApplicationArgs(env, argv, argc);
        //从类中找到main方法标识
        mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                           "([Ljava/lang/String;)V");
        
        //调用main方法                                   
        (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
    }
    
    
    // src/java.base/macosx/native/libjli/java_md_macosx.m
    // src/java.base/unix/native/libjli/java_md_solinux.c
    
    int JVMInit(InvocationFunctions* ifn, jlkong threadStackSize, int argc,
                char **argv, int mode, char **what, int ret) {
                
        ...
        
        return continueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
    }
    
    CallJavaMainInNewThread(jlong stack_size, void* args) {
        
        int rslt;
        
        ...
        
        
        rslt = JavaMain(args);
        
        return rslt;
    }
    
    
    //hotspot/share/prims/jni.cpp
    
    //调用一个main这个静态方法
    static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
        
        JavaCalls::call(result, method, &java_args, CHECK);
    }
    
    // hotspot/share/runtime/javaCalls.cpp
    
    void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
    
        os::os_exception_wrapper(call_helper, result, method, args, THREAD);
    }
    
    void JavaCalls::call_helper(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
    
        //字节码解释器入口函数地址
        address entry_point = method->from_interpreted_entry();
        if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
            entry_point = method->interpreter_entry();
        }
        
        ...
        
        通过call_stub->entry_point->method的调用链,完成Java方法的调用
        StubRoutines::call_stub()(
            (address)&link,//call_stub调用完后,返回值通过link指针带回来
            // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
            result_val_address,          // see NOTE above (compiler problem)
            result_type,
            method(),
            entry_point,
            parameter_address,
            args->size_of_parameters(),
            CHECK
          );
          
          result = link.result();//获取返回值
    }
    
    //  hotspot/share/runtime/stubRoutines.hpp
    
    // 将_call_stub_entry指针转换为CallStub类型,并执行该指针对应的函数
    // 这个_call_stub_entry指针是通过stubGenerator类在初始化生成的,
    // 这个stubGernerator负责为将要执行的方法创建栈帧,其实现区分不同CPU平台
    static CallStub call_stub() {
        return CAST_TO_FN_PTR(CallStub, _call_stub_entry);
    }
    
    // Calls to Java
    typedef void (*CallStub)(
        address   link,
        intptr_t* result,
        BasicType result_type,
        Method* method,
        address   entry_point,
        intptr_t* parameters,
        int       size_of_parameters,
        TRAPS
    );
    

    这里以x86_32平台为例进行说明_call_stub_entry的创建:

    // hotspot/cpu/x86/stubGenerator_x86_32.cpp
    
    class StubGenerator: public StubCodeGenerator {
        public:
        //构造函数
      StubGenerator(CodeBuffer* code, bool all) : StubCodeGenerator(code) {
        if (all) {
          generate_all();
        } else {
          generate_initial();
        }
      }
      
      //初始化
      void generate_initial() {
        
        ...
        
        //创见CallStub实例,并赋值给StubRoutines::_call_stub_entry
        StubRoutines::_call_stub_entry =
          generate_call_stub(StubRoutines::_call_stub_return_address);
        
        ...
      }
      
    }
    
    
    //创建方法调用的栈帧
    address generate_call_stub(address& return_address) {
        
        //创建栈帧、参数入栈等
        
        ...
        
        __ movptr(rbx, method);           // 保存方法指针到rbx中
        __ movptr(rax, entry_point);      // get entry_point
        __ mov(rsi, rsp);                 // set sender sp
        
        //调用rax寄存器存储的解释器入口函数,这里解释器入口函数就是entry_point指针指向的函数
        //解释器就会开始从method指针指向的位置开始执行字节码
        __ call(rax);   
        
        ...
    }
    

    下面看一下解释器的入口函数的实现,从前面可以知道解释器入口函数是从method中获取到的。

    // hotspot/share/oops/method.hpp
    
    //该方法被内联了,即获取成员变量_from_interpreted_entry的值。
    address from_interpreted_entry() const;
    
    inline address Method::from_interpreted_entry() const {
      return Atomic::load_acquire(&_from_interpreted_entry);
    }
    
    // _from_interpreted_entry是在link_mehtod函数被赋值的。
    void Mehthd
    
    

    那么_from_interpreted_entry是在什么时候被赋值的呢?在链接方法时。

    // hotspot/share/oops/method.cpp
    
    void Method::link_method(const methodHandle& h_method, TRAPS) {
        
        ...
        
        if (!is_shared()) {
            //终于和字节码解释器勾搭上了
            //根据h_method的类型取出对应的解释器入口函数
            address entry = Interpreter::entry_for_method(h_method);
            set_interpreter_entry(entry);
        }
        
        ...
    }
    
    // hotspot/share/interpreter/abstractInterpreter.hpp
    
    //解释器入口函数数组
    static address  _entry_table[number_of_method_entries];
    
    static MethodKind method_kind(const methodHandle& m);
    static address entry_for_kind(MethodKind k){ 
         return _entry_table[k];
    }
    
    //从methodHandle中取出方法类型MethodKind,并根据方法类型从_entry_table取出对应的解释器入口函数地址
    static address entry_for_method(const methodHandle& m){
        return entry_for_kind(method_kind(m));
    }
    
    //虚拟机启动时通过该函数填充_entry_table数组
    static void set_entry_for_kind(MethodKind k, address e);
    

    那么到底是什么时候调用的set_entry_for_kind函数来初始化的呢?

    在文章开头说过,launcher/main.c中的main函数是java程序的启动函数,在main函数中调用了JLI_Launcher函数,在JLI_Launcher会调用LoadJavaVM函数加载虚拟机的动态链接库,并找到创建虚拟机的入口函数JNI_CreateJavaVM存储到结构体InvocationFunctions中。
    这个结构体InvocationFunctions会一直当做参数传递到JavaMain函数中。
    之后再JavaMain函数中,会根据JNI_CreateJavaVM虚拟机创建函数来初始化虚拟机,此时已经是在一个新的线程中运行了。

    下面看一下具体的调用流程:

    // src/java.base/share/native/libjli/java.c
    
    JNIEXPORT int JNICALL JLI_Launch(int argc,
                                    char** argv,
                                    int jargc,
                                    const char** jargv,
                                    int appclassc,
                                    const char** appclassv,
                                    const char* fullversion,
                                    const char* dotversion,
                                    const char* pname,
                                    const char* lname,
                                    jboolean javaargs,
                                    jboolean cpwildcard,
                                    jboolean javaw,
                                    jint ergo) {
                                    
                                    
        ...               
        
        //Java虚拟机动态链接库的路径
        char jvmpath[MAXPATHLEN];
        //
        InvocationFunctions ifn;
        
        //从参数中读取虚拟机运行环境所需的配置
        CreateExecutionEnvironment(&argc, &argv,
                                   jrepath, sizeof(jrepath),
                                   jvmpath, sizeof(jvmpath),
                                   jvmcfg,  sizeof(jvmcfg));
        
        ifn.CreateJavaVM = 0;
        ifn.GetDefaultJavaVMInitArgs = 0;
        
        //加载Java虚拟机动态链接库,并找到创建虚拟的函数JNI_CreateJavaVM
        //这里会区分不同平台和CPU位数,但大体上就是使用dlopen和dlsym这个两个系统调用来实现
        if (!LoadJavaVM(jvmpath, &ifn)) {
            return(6);
        }
        ...
        
        return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
    }
    

    下面以macos平台为例看一下LoadJavaVM的实现:

    // src/java/base/macosx/native/libjli/java_md_macosx.m
    
    jboolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
    {
        //动态链接库文件句柄
        void *libjvm;
        
        //判断是否是静态编译,使用dlopen函数打开动态链接库
    #ifndef STATIC_BUILD
        libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
    #else
        libjvm = dlopen(NULL, RTLD_FIRST);
    #endif
    
        //使用dlsym函数根据函数符号找到对应函数地址
        //这里一共获取了三个函数JNI_CreateJavaVM、JNI_GetDefaultJavaVMInitArgs、JNI_GetCreatedJavaVMs
        //如果有任何一下缺失都会返回错误,如果成功则将三个函数存储到InvocationFunctions结构体中。
        ifn->CreateJavaVM = (CreateJavaVM_t)
            dlsym(libjvm, "JNI_CreateJavaVM");
            
        ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
            dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
            
        ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
        dlsym(libjvm, "JNI_GetCreatedJavaVMs");
        
        return JNI_TRUE;
    }
    

    从动态链接库中找到创建虚拟机的入口函数后,会把InvocationFunctions结构体作为参数一路传递到JavaMain函数中,并在其中发起调用。

    // src/java.base/share/native/libjli/java.c
    
    int JavaMain(void* _args) {
        //将参数强制转换为JavaMainArgs类型
        JavaMainArgs *args = (JavaMainArgs *)_args;
        //从参数取出InvocationFunctions结构体
        InvocationFunctions ifn = args->ifn;
        
        JavaVM *vm = 0;
        JNIEnv *env = 0;
        
        ...
        
        //初始化Java虚拟机
        if (!InitializeJVM(&vm, &env, &ifn)) {
            JLI_ReportErrorMessage(JVM_ERROR1);
            exit(1);
        }
        
        ...
    }
    
    static jboolean InitializaJVM(JavaVM **pwm, JNIENV **penv, InvocationFunctions *ifn) {
        JavaVMInitArgs args;
        jint r;
    
        memset(&args, 0, sizeof(args));
        args.version  = JNI_VERSION_1_2;
        args.nOptions = numOptions;
        args.options  = options;
        args.ignoreUnrecognized = JNI_FALSE;
        
        //调用JNI_CreateJavaVM函数创建虚拟机,该函数内部会转调JNI_CreateJavaVM_inner函数
        r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
        JLI_MemFree(options);
        return r == JNI_OK;
    }
    
    // hotspot/share/prims/jni.cpp
    
    static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
        jint result = JNI_ERR;
        
        //通过Threads的create_vm函数创建虚拟机
        result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
        
        if (result == JNI_OK) {
           JavaThread *thread = JavaThread::current();
            *vm = (JavaVM *)(&main_vm);
            *(JNIEnv**)penv = thread->jni_environment(); 
            post_thread_start_event(thread);
            ThreadStateTransition::transition(thread, _thread_in_vm, _thread_in_native);
        }
        ...
        
        return result;
    }
    

    Threads::create_vm函数非常长,里面执行了很多初始化工作。例如

    • 预初始化信息,可以在初始化虚拟机之前预先初始化一些可能用到的信息,如虚拟机版本。不同的CPU平台可以在这里初始化自己定义的信息。
    • 初始化ThreadLocalStorage
    • 初始化输出流模块
    • 初始化os操作系统模块,主要是一些固定配置
    • 初始化系统属性
    • 初始化JDK版本
    • 根据JDK版本初始化特定参数
    • 解析命令行参数
    • 初始化和应用ergonomics,主要是初始化大内存页,根据CPU核心数、内存容量设置虚拟内存页大小、JVM内存参数、GC策略、java8取消永久代新增Metaspace区。
    • 解析完参数后,二次初始化os模块。例如快速线程时钟、Linux信号处理器、最小栈长度、最大文件描述符数量、线程优先级策略等
    • 初始化安全点机制,安全点机制是很重要的概念。安全点是指一些特定的位置,当线程运行到这些位置时,线程的一些状态可以被确定。
    • 初始化输出流日志
    • 加载系统库
    • 初始化全局数据结构。如java基础类型、事件队列、全局锁、JVM性能统计区、大块内存池chunkpool等
    • 创建JavaThread
    • 初始化对象监视器ObjectMonitor,它是Java语言级别的同步子系统
    • 初始化全局模块,这些模块是Hotspot的整体基础,如字节码初始化、类加载器初始化、编译策略初始化、解释器初始化等等。我们前面追踪的_entry_table数组就是在这里面初始化的。
    • 创建VMThread,并开始执行。VMThread用于执行VMOptions
    • 初始化主要JDK类,如String类、System类,Class类、线程/线程组类、Module类,还有其他反射、异常相关的类
    • 初始化jni方法的快速调用
    • 标记虚拟机的基本初始化完成
    • 日志系统的后续配置
    • 元数据区Metaspace的后续配置
    • 初始化jdk信号支持
    • 初始化Attach监听机制,它是JVM提供进程间通信机制,负责接收处理其他进程发送过来的命令
    • 初始化JSR292标准核心类,JSR292标准引入invokedynamic指令以支持调用动态类型语言中的方法,使得在把源码编译成字节码时不需要确定方法的签名。当运行invokedynamic指令时,JVM会通过新的动态链接机制Method Handles,寻找到真实的方法。
    • 第二阶段,初始化模块化系统
    • 第三阶段,初始化安全管理器、设置系统类加载器作为线程上下文的类加载器
    • 启动监听线程WatcherThread,用来模拟时钟中断。

    Threads::create_vm函数中做了上面那么多工作,这里为了简单就不讲所有源码都贴出来了。
    因为_entry_table数组的填充是在init_globals()函数中调用的,所以只说明一下init_globals()函数的调用路径。

    // hotspot/share/runtime/thread.cpp
    jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
        
        ...
        
        //初始化全局模块
        jint status = init_globals();
        
        ...
        
        return JNI_OK;
    }
    
    
    // hotspot/share/runtime/init.cpp
    jinit init_globals() {
        ...
        
        //初始化方法处理适配器
        MethodHandles::generate_adapters();
        ...
    }
    
    // hotspot/share/prims/methodHandles.cpp
    MethodHandlesAdapterBlob* MethodHandles::_adapter_code = NULL;
    void MethodHeandles::generate_adapters() {
        _adapter_code = MethodHandlesAdapterBlob::create(adapter_code_size);
        CodeBuffer code(_adapter_code);
        MethodHandlesAdapterGenerator g(&code);
        g.generate();
    }
    
    void MethodHandlesAdapterGenerator::generate() {
        //1.生成通用方法处理适配器
        //2.生成解释器入口点
        
        //这里的MethodKinds是一个枚举类型,声明了虚拟机固有的方法类型
        for (Interpreter::MethodKind mk = Interpreter::method_handle_invoke_FIRST;
        mk <= Interpreter::method_handle_invoke_LAST;
        mk = Interpreter::MethodKind(1 + (int)mk)) {
        vmIntrinsics::ID iid = Interpreter::method_handle_intrinsic(mk);
        StubCodeMark mark(this, "MethodHandle::interpreter_entry", vmIntrinsics::name_at(iid));
        
        //根据方法类型ID生成对应的方法处理解释器入口点,并通过set_entry_for_kind设置到abstractInterpreter.cpp中的_entry_table数组中。
        address entry = MethodHandles::generate_method_handle_interpreter_entry(_masm, iid);
        if (entry != NULL) {
          Interpreter::set_entry_for_kind(mk, entry);
        }
      }
    }
    
    // hotspot/share/interpreter/abstractInterpreter.cpp
    
    void AbstractInterpreter::set_entry_for_kind(AbstractInterpreter::MethodKind kind, address entry) {
    
        //将解释器入口点填充到_entry_table数组中
         _entry_table[kind] = entry;
    
         update_cds_entry_table(kind);
    }
    

    到此就可以总结一下了,当我们通过java命令执行一个应用程序时,首先会先启动虚拟机实例,启动过程中包含了很多初始化工作,这些工作是为java程序提供运行环境的必要条件。在初始化工作中会根据不同的方法类型构建对应解释器入口点,并存储到一个数组_entry_table中。
    当初始化工作完成后,会调用java应用程序的入口方法(static void main(String[] args)),然后根据main方法的类型从_entry_table数组中找出对应的解释器入口点,然后就开始解释执行main方法的字节码了。

    最后介绍一下JVM中都预定义了哪些方法类型。

    // hotspot/share/interpreter/abstractInterpreter.hpp
    
    enum MethodKind {
        //大多数没有声明为native和synchronized方法都属于这种类型
        //在执行之前要将局部变量初始化为0
        zerolocals,                                                 // method needs locals initialization
        zerolocals_synchronized,                                    // method needs locals initialization & is synchronized
        native,                                                     // native method
        native_synchronized,                                        // native method & is synchronized
        //空方法,也单独由特定解释器处理,避免创建无效的栈帧
        empty,                                                      // empty method (code: _return)
        //成员变量的get方法
        accessor,                                                   // accessor method (code: _aload_0, _getfield, _(a|i)return)
        abstract,                                                   // abstract method (throws an AbstractMethodException)
        method_handle_invoke_FIRST,                                 // java.lang.invoke.MethodHandles::invokeExact, etc.
        method_handle_invoke_LAST                                   = (method_handle_invoke_FIRST
                                                                       + (vmIntrinsics::LAST_MH_SIG_POLY
                                                                          - vmIntrinsics::FIRST_MH_SIG_POLY)),
        
        //一些固定作用的方法,直接指定特定的解释器入口,提高效率
        java_lang_math_sin,                                         // implementation of java.lang.Math.sin   (x)
        java_lang_math_cos,                                         // implementation of java.lang.Math.cos   (x)
        java_lang_math_tan,                                         // implementation of java.lang.Math.tan   (x)
        java_lang_math_abs,                                         // implementation of java.lang.Math.abs   (x)
        java_lang_math_sqrt,                                        // implementation of java.lang.Math.sqrt  (x)
        java_lang_math_log,                                         // implementation of java.lang.Math.log   (x)
        java_lang_math_log10,                                       // implementation of java.lang.Math.log10 (x)
        java_lang_math_pow,                                         // implementation of java.lang.Math.pow   (x,y)
        java_lang_math_exp,                                         // implementation of java.lang.Math.exp   (x)
        java_lang_math_fmaF,                                        // implementation of java.lang.Math.fma   (x, y, z)
        java_lang_math_fmaD,                                        // implementation of java.lang.Math.fma   (x, y, z)
        java_lang_ref_reference_get,                                // implementation of java.lang.ref.Reference.get()
        java_util_zip_CRC32_update,                                 // implementation of java.util.zip.CRC32.update()
        java_util_zip_CRC32_updateBytes,                            // implementation of java.util.zip.CRC32.updateBytes()
        java_util_zip_CRC32_updateByteBuffer,                       // implementation of java.util.zip.CRC32.updateByteBuffer()
        java_util_zip_CRC32C_updateBytes,                           // implementation of java.util.zip.CRC32C.updateBytes(crc, b[], off, end)
        java_util_zip_CRC32C_updateDirectByteBuffer,                // implementation of java.util.zip.CRC32C.updateDirectByteBuffer(crc, address, off, end)
        java_lang_Float_intBitsToFloat,                             // implementation of java.lang.Float.intBitsToFloat()
        java_lang_Float_floatToRawIntBits,                          // implementation of java.lang.Float.floatToRawIntBits()
        java_lang_Double_longBitsToDouble,                          // implementation of java.lang.Double.longBitsToDouble()
        java_lang_Double_doubleToRawLongBits,                       // implementation of java.lang.Double.doubleToRawLongBits()
        number_of_method_entries,
        invalid = -1
      };
    
    展开全文
  • 主要介绍了浅析java程序入口main()方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 此批处理程序用于执行java项目中某一类的main方法,修改jdk地址,项目地址即可
  • 主要介绍了android工程下不能运行java main程序的解决方法,需要的朋友可以参考下
  • Java main方法快捷键

    千次阅读 2021-03-01 22:31:28
    快捷键:psvm,就会出现main方法的提示: 然后回车: public static void main(String[] args) { }

    快捷键:psvm,就会出现main方法的提示:

    在这里插入图片描述
    然后回车:

        public static void main(String[] args) {
            
        }
    
    展开全文
  • javamain方法是什么

    千次阅读 2021-03-22 19:52:12
    Java中的main方法,是一个java应用程序的入口,无论一个java的应用多么复杂庞大,或是多么的小,只要他是一个可以运行的java程序那么必然就要有一个main方法main方法的定义格式通常固定如下: (推荐学习:java课程...

    d3f5db6139e718a6a1e712737bffdd50.png

    Java中的main方法,是一个java应用程序的入口,无论一个java的应用多么复杂庞大,或是多么的小,只要他是一个可以运行的java程序那么必然就要有一个main方法。

    main方法的定义格式通常固定如下: (推荐学习:java课程)public class Demo {

    public static void main(String[] args) {

    System.out.println("Hello Word");

    }}

    1、为什么是公共的(public)

    ans1:Java指定了一些可访问的修饰符如:private,protected,public。每个修饰符都有它对应的权限,public权限最大,为了说明问题,我们假设main方法是用private修饰的,那么main方法出了Demo这个类对外是不可见的。

    那么,JVM就访问不到main方法了。因此,为了保证JVM在任何情况下都可以访问到main方法,就用public修饰。

    ans2:Java设计了几个访问修饰符,包括:private,protected,public。 在Java中声明为public的任何方法或变量都可以从该类的外部访问。 而JVM访问main方法显然不是在类的内部访问,因此main方法需要定义为public的方法。

    2、为什么是静态的(static)

    静态可以让JVM调用main方法的时候更加方便,不需要通过对象调用。关于static关键字我们知道的是,static关键字修饰的方法,可以不用通过创建一个实例去访问,而是可以通过类名直接访问。并且static修饰的方法以及变量存储在虚拟机当中的方法区当中,而非堆内存当中。

    那么,对于虚拟机来说也是一样的道理。main方法定义为static的,则对于虚拟机来说,在程序启动之后并不需要创建一个实例,就可以去调用这个main方法。

    3、为什么没有返回值(void)

    void表示main方法没有返回值,没有返回值的原因是因为Java不需要main方法向操作系统返回退出信息。如果main方法正常退出,那么Java应用层序的退出代码为0,表示成功的运行了程序。

    4、main

    main的名称不能变是为了JVM能够识别程序运行的起点,main方法可以被重载,重载的main方法不会被执行。main方法作为程序初始线程的起点,任何其他线程均由该线程启动。

    JVM内部有两种线程,非守护线程和守护线程,main方法属于非守护线程,守护线程通常由JVM自己使用,Java程序也可以表明自己的线程是守护线程。当程序中所有的非守护线程终止时,JVM退出。也可以用Runtime类或者System.exit()来退出。

    5、String [] args

    String[] args,是main方法中唯一可以改变的地方!args是arguments的缩写,只是一个变量默认名,习惯性写作它,但是也可以改变的,只要符合命名规则随便你写成什么。

    在使用集成开发工具的今天,String[] args更像是一种摆设了,很多初学者都不知道它的作用,其实它是程序运行传入的一个参数组。

    展开全文
  • 下列各项中属于这种账务处理程序的是( )。(3.0分) 果糖的吸收通过以下哪种方式 患者女性,55岁。截瘫,生活不能自理。护士协助床上擦浴 为避免失真,常规双边带调制信号中的调幅指数应该大于1。 1638、 按照1974《SOLAS...

    一小球沿着斜面向上运动,其运动方程为:【图片则小球运动到最高点的时刻t=----------------秒。

    能将白喉棒状杆菌的菌落分成重型.中间型.轻型的培养基是

    在数据库管理系统中,能将数据的内容变成网页的对象是( )

    ( )教师灌输道德知识与机械训练道德行为。

    Frankis making a market survey in a shopping mall. Watch the video clip andmatch the brand

    地图图表的设计: 自由选择一种地图类图表形式(也可以对某一现有地图进行改良化设计),如资源地图、导游地图、导购地图等,通过提炼、加工、强化等手段,进行设计。 要求:环境分布简洁、概括、形象,有

    在藏族中,土葬是安葬方式中最高等的一种。

    143、逻辑运算中的逻辑加常用符号___表示。

    关于微循环直接通路的正确叙述是:

    投影面垂直面垂直于( )投影面。 (7.0分)

    一小球沿着斜面向上运动,其运动方程为:【图片则小球运动到最高点的时刻t=----------------秒。

    杆件的 轴线由直线弯为曲线, 发生平面 弯曲是 ( )的 特点。

    中涂漆未干透时对其进行打磨容易产生{.XZ}缺陷。

    FMS由_______ 、_____ 和_______ 等组成。

    5日龄羔羊腹泻,粪便带血。剖检见小肠黏膜充血、溃疡,回肠外观红色。病料接种普通琼脂,厌氧培养长出的菌落接种疱肉培养基,产生大量气体。该病最可能的病原是( )

    Cash and cash equivalents are the most liquid of all assets.

    车票使用管理可分为配发、调拨、收缴、()四个环节

    12、会计工作管理体制包括会计工作的行政管理和单位内部的会计工作管理两个方面( )

    52.粘液脓血便伴里急后重可见于:

    What happens when a charged insulator is placed near an uncharged metallic object?

    根据原始凭证或汇总原始凭证编制记账凭证,然后直接根据记账凭证逐笔登记总分类账。下列各项中属于这种账务处理程序的是( )。(3.0分)

    果糖的吸收通过以下哪种方式

    患者女性,55岁。截瘫,生活不能自理。护士协助床上擦浴

    为避免失真,常规双边带调制信号中的调幅指数应该大于1。

    1638、 按照1974《SOLAS》对散粮船进行稳性校核时,以横倾角40°时的剩余静稳性力臂值GZ40’代替动稳性数值Ad的前提条件之一是()。 (41597:第十章_散装谷物运输:203

    What happens when a charged insulator is placed near an uncharged metallic object?

    对联是常见的一种现象,下列对联中对应关系 不正确 的是( )。

    以下属于尺寸标注的组成部分有( )。

    结构化程序语言,简称 ( )语言。

    文献信息正由传统的纸质印刷型向什么发展?

    果糖的吸收通过以下哪种方式

    What happens when a charged insulator is placed near an uncharged metallic object?

    脸的中庭是指眉骨至鼻底

    病人,女,19岁.高热、恶心、呕吐、抽搐2日,诊断考虑暴发性流行性脑脊醺膜炎。 治疗应首选下列何药( )

    5日龄羔羊腹泻,粪便带血。剖检见小肠黏膜充血、溃疡,回肠外观红色。病料接种普通琼脂,厌氧培养长出的菌落接种疱肉培养基,产生大量气体。该病最可能的病原是( )

    展开全文
  • java程序入口main()方法浅析main()方法方法签名public static void main(String[] args)方法签名讲解public修饰符:java类由java虚拟机(JVM)调用,为了没有限制可以自由的调用,所以采用public修饰符。static修饰...
  • ()分方法方法【多选题】教育活动环节教师应完成哪些工作任务?中国大学MOOC: 出版了《亲爱的孩子,声明见字如面》、《一路修行做老师》等著作,“男孩儿是世界上最难教养的小动物——培养男孩儿”一文的作者是:应用...
  • 我正在查看一些Java源代码,并注意到main方法没有定义。Java如何编译源代码而不知道从哪里开始?main方法仅在Java虚拟机执行代码时使用。没有main方法就无法执行代码,但仍然可以编译代码。编译代码时,通常在命令行...
  • 全面解析Java main方法

    2020-08-27 14:09:40
    main方法是我们学习Java语言学习的第一个方法,也是每个java使用者最熟悉的方法,每个Java应用程序都必须有且仅有一个main方法。这篇文章通过实例代码给大家介绍java main方法的相关知识,感兴趣的朋友跟随脚本之家小...
  • Java-程序的入口main方法

    万次阅读 2018-01-18 15:08:14
    只有main方法可以作为程序的入口。main方法: public static void main(String[] args) { 方法体 }观察这个方法,可以看出:这是一个public static修饰的方法,所以他是一个静态的方法。返回值是void类型,...
  • java中的Main方法..

    2021-03-05 22:47:17
    在Java中,main()方法Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方 法有很大的不同,比如方法的名字必须是main方法必须是public static void 类型...
  • javamain方法参数的几种形式

    千次阅读 2019-05-30 11:30:43
    main方法参数的几种形式第一种第二种第三种 第一种 public static void main(String args[]) //最标准的写法 第二种 1 public static void main(String[] args){ } //两种写法都是一样的,都表示字符串数组args,...
  • 学过java的都知道main方法是学习java的开始,也是程序的入口,不过你有多少个类或程序,线程,他们的入口方法都是main()main方法是一个静态的方法,所以这个方法是属于类的,而不是对象的;在 main() 方法中,参数...
  • 【单选题】一个可以独立运行的Java应用程序A. 可以有一个或多个main方法 B. 最多只能有两个main方法 C. 可以有一个或零个main方法 D. 只能有一个main方法更多相关问题下列各项中,( )属于财产清查结果处理步骤。...
  • 【判断题】隔离衣挂放位置在污染区时候应该内面朝外【单选题】If city noises ( ) from increasing, people ( ) shout to be heard even at dinner.A.... EOF【单选题】降水方法可分明排水和( )A. 人工降低地...
  • Java中的main()方法详解

    千次阅读 2021-02-12 16:58:21
    在Java中,main()方法Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main方法必须是public static void 类型...
  • javamain方法的运行

    千次阅读 2019-10-27 14:28:18
    (最近要把一个main方法启动的项目集入web项目里,参考了main... 学过java的都知道main方法是学习java的开始,也是程序的入口,不过你有多少个类或程序,线程,他们的入口方法都是main()。main方法是一个静态的...
  • Java中的main方法

    千次阅读 2021-03-06 04:42:30
    首先需要说明的是:1.main函数(主函数)是可以调用的,这种调用是没有意义的;2. main函数只能出现在公共类中也就是public class中;...关于main的声明:1.java虚拟机调用的,所以必须为public;2. ...
  • Javamain方法的格式详解

    千次阅读 2015-10-28 09:15:56
    在Java中,main()方法Java应用程序的入口方法,也就是说程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main方法必须是public static void修饰的,...
  • Java程序入口:main()方法讲解

    千次阅读 2019-02-22 20:38:00
    Java程序的入口---main()方法的签名为:public static void main(String[] args) {...} ,其中, ♦ public修饰符:Java类由JVM调用,为了让JVM可以自由调用这个main()方法,所以使用public修饰符把这个方法暴露...
  • javamain方法的返回类型是?

    千次阅读 2021-02-12 20:20:21
    man方法是void类型,void没有返回值类型在java语言程序编写时都会涉及到一个main方法,它的格式为:public static void main(String[] args)(一般必须这么定义,这是java规范)在这里修饰符public和static的顺序是...
  • JAVA中的main()方法详解

    千次阅读 2020-07-11 17:54:47
    在Java中,main()方法Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方法有很大的不同,比如方法的名字必须是main方法必须是public static void 类型...
  • Java项目main方法启动的两种方式

    千次阅读 2021-02-12 15:45:48
    1.打包时指定了主类,可以直接用java -jar xxx.jar。maven-assembly-pluginfalsejar-with-dependenciescom.TestApplicationmake-assemblypackageassembly2.打包时没有指定主类,可以用java -cp xxx.jar 主类名称...
  • javamain方法详细介绍 1.main方法程序的入口,所以每次我们用的是public static void main的结构作为程序入口。 2.它是一种独特的方法,也可以叫方言。(psvm) 3.由于main是静态的方法,所以它只能调用静态的,...
  • JAVA中的main方法

    千次阅读 2019-03-14 17:22:53
    void 由于main方法程序的主方法,也就是程序执行的起点,不需要返回值 main 系统规定的默认调用的方法名称,执行时默认找到main方法名称 String[]args 表示的时运行时的参数,参数传递的的形式为“java类名称 参数...
  • javaMain方法不在模块内运行

    千次阅读 2021-03-21 08:16:03
    我是否需要在模块内或外部运行main方法?我是使用Java模块化系统的新手.我正在尝试使用Java 10中的JavaFX创建一个简单的程序,因为它是支持JavaFX的Java的最后一个版本.我在我的module-info.java上为JavaFX导入了必要...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,352,109
精华内容 540,843
关键字:

java应用程序main方法

java 订阅