精华内容
下载资源
问答
  • 动态编译
    千次阅读
    2019-06-07 01:10:37

    java动态编译以及动态生成jar文件

    本文主要为将指定java文件编译并生成jar,供其他程序依赖,直接上代码

    动态编译java文件

    /**
     * @author zhangchengping
     * @PackageName:com.demo.wms.tasks
     * @ClassName:CompilerUtils
     * @Description: 动态编译java文件
     * @date 2019-06-06 10:37
     * @Version 1.0
     */
    
    import org.apache.commons.lang.StringUtils;
    
    import java.io.*;
    import java.util.Arrays;
    import java.util.List;
    
    import javax.tools.*;
    
    public class CompilerUtils {
        private static JavaCompiler javaCompiler;
        private static String encoding = "UTF-8";
    
        private CompilerUtils() {
        };
    
        private static JavaCompiler getJavaCompiler() {
            if (javaCompiler == null) {
                synchronized (CompilerUtils.class) {
                    if (javaCompiler == null) {
                        //根据JavaCompiler 的获取方式来看,应该是采用了单例模式的,但是这里为了顺便复习一下单例模式,以及确保一下单例吧
                        javaCompiler = ToolProvider.getSystemJavaCompiler();
                    }
                }
            }
    
            return javaCompiler;
        }
    
        /**
         * @Description: 编译java文件
         * @param encoding 编译编码
         * @param jarPath 需要加载的jar的路径
         * @param filePath 文件或者目录(若为目录,自动递归编译)
         * @param sourceDir java源文件存放目录
         * @param targetDir 编译后class类文件存放目录
         * @return boolean
         * @Author zhangchengping
         * @Date 2019-06-06 19:50
         */
        public static void compiler( String filePath, String targetDir, String sourceDir,String encoding, String jarPath)
                throws Exception {
    
            // 得到filePath目录下的所有java源文件
            List<File> sourceFileList = File4ComplierUtils.getSourceFiles(filePath);
    
            if (sourceFileList.size() == 0) {
                // 没有java文件,直接返回
                System.out.println(filePath + "目录下查找不到任何java文件");
                return;
            }
            //获取所有jar
            String jars = File4ComplierUtils.getJarFiles(jarPath);
            if(StringUtils.isBlank(jars)){
                jars="";
            }
            File targetFile = new File(targetDir);
            if(!targetFile.exists())targetFile.mkdirs();
            // 建立DiagnosticCollector对象
            DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
            //该文件管理器实例的作用就是将我们需要动态编译的java源文件转换为getTask需要的编译单元
            StandardJavaFileManager fileManager = getJavaCompiler().getStandardFileManager(diagnostics, null, null);
            // 获取要编译的编译单元
            Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFileList);
            /**
             * 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。 -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录,-d就是编译文件的输出目录。
             */
            //Iterable<String> options =Arrays.asList("-encoding",encoding,"-classpath",jars,"-d", targetDir, "-sourcepath", sourceDir);
            Iterable<String> options =Arrays.asList("-encoding",encoding,"-classpath",jars,"-d", targetDir, "-sourcepath", sourceDir);
            /**
             * 第一个参数为文件输出,这里我们可以不指定,我们采用javac命令的-d参数来指定class文件的生成目录
             * 第二个参数为文件管理器实例  fileManager
             * 第三个参数DiagnosticCollector<JavaFileObject> diagnostics是在编译出错时,存放编译错误信息
             * 第四个参数为编译命令选项,就是javac命令的可选项,这里我们主要使用了-d和-sourcepath这两个选项
             * 第五个参数为类名称
             * 第六个参数为上面提到的编译单元,就是我们需要编译的java源文件
             */
            JavaCompiler.CompilationTask task = getJavaCompiler().getTask(
                    null,
                    fileManager,
                    diagnostics,
                    options,
                    null,
                    compilationUnits);
                // 运行编译任务
            // 编译源程式
            boolean success = task.call();
            for (Diagnostic diagnostic : diagnostics.getDiagnostics())
                System.out.printf(
                        "Code: %s%n" +
                                "Kind: %s%n" +
                                "Position: %s%n" +
                                "Start Position: %s%n" +
                                "End Position: %s%n" +
                                "Source: %s%n" +
                                "Message: %s%n",
                        diagnostic.getCode(), diagnostic.getKind(),
                        diagnostic.getPosition(), diagnostic.getStartPosition(),
                        diagnostic.getEndPosition(), diagnostic.getSource(),
                        diagnostic.getMessage(null));
            fileManager.close();
            System.out.println((success)?"编译成功":"编译失败");
        }
    }
    /**
     * @author zhangchengping
     * @PackageName:com.demo.wms.tasks.web.listener
     * @ClassName:s
     * @Description: 文件处理类
     * @date 2019-06-06 19:07
     * @Version 1.0
     */
    public class File4ComplierUtils {
    
        /**
         * @Description:  获取目录下所有源文件
         * @param sourceFilePath
         * @return java.util.List<java.io.File>
         * @Author zhangchengping
         * @Date 2019-06-06 19:47
         */
        public static List<File> getSourceFiles(String sourceFilePath){
    
            List<File> sourceFileList = new ArrayList<>();
            try {
                getSourceFiles(new File(sourceFilePath),sourceFileList);
            } catch (Exception e) {
                e.printStackTrace();
                sourceFileList = null;
            }
            return sourceFileList;
        }
    
        /**
         * @Description:  获取目录下所有的jar
         * @param sourceFilePath
         * @return java.lang.String
         * @Author zhangchengping
         * @Date 2019-06-06 19:46
         */
        public static String getJarFiles(String sourceFilePath){
    
            String jars = "";
            try {
                getJarFiles(new File(sourceFilePath),jars);
            } catch (Exception e) {
                e.printStackTrace();
                jars = "";
            }
            return jars;
        }
        /**
         * 查找该目录下的所有的java文件
         *
         * @param sourceFile
         * @param sourceFileList
         * @throws Exception
         */
        private static void getSourceFiles(File sourceFile, List<File> sourceFileList) throws Exception {
            if (!sourceFile.exists()) {
                // 文件或者目录必须存在
                throw new IOException(String.format("%s目录不存在",sourceFile.getPath()));
            }
            if (null == sourceFileList) {
                // 若file对象为目录
                throw new NullPointerException("参数异常");
            }
            if (sourceFile.isDirectory()) {// 若file对象为目录
                File[] childrenDirectoryFiles = sourceFile.listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        return pathname.isDirectory();
                    }
                });
                for (File file : sourceFile.listFiles()) {
                    if(file.isDirectory()){
                        getSourceFiles(file,sourceFileList);
                    } else {
                        sourceFileList.add(file);
                    }
                }
            }else{
                sourceFileList.add(sourceFile);
            }
        }
    
        /**
         * 查找该目录下的所有的jar文件
         *
         * @param sourceFile
         * @throws Exception
         */
        private static String getJarFiles(File sourceFile,String jars) throws Exception {
            if (!sourceFile.exists()) {
                // 文件或者目录必须存在
                throw new IOException("jar目录不存在");
            }
            if (!sourceFile.isDirectory()) {
                // 若file对象为目录
                throw new IOException("jar路径不为目录");
            }
            if(sourceFile.isDirectory()){
                for (File file : sourceFile.listFiles()) {
                    if(file.isDirectory()){
                        getJarFiles(file,jars);
                    }else {
                        jars = jars + file.getPath() + ";";
                    }
                }
            }else{
                jars = jars + sourceFile.getPath() + ";";
            }
            return jars;
        }
    }

    将编译后的class生成jar文件

    **
     * @author zhangchengping
     * @PackageName:com.demo.wms.tasks.web.listener
     * @ClassName:PackageUtil
     * @Description: 生成jar文件
     * @date 2019-06-06 22:59
     * @Version 1.0
     */
    
    import com.sun.javafx.beans.annotations.NonNull;
    import org.apache.commons.io.FileUtils;
    import org.apache.commons.lang.StringUtils;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.lang.annotation.Target;
    import java.util.jar.JarEntry;
    import java.util.jar.JarOutputStream;
    import java.util.jar.Manifest;
    
    public class CreateJarUtils {
    
        /**
         * @param rootPath    class文件根目录
         * @param targetPath  需要将jar存放的路径
         * @param jarFileName jar文件的名称
         * @Description: 根据class生成jar文件
         * @Author zhangchengping
         * @Date 2019-06-06 23:56
         */
        public static void createTempJar(String rootPath, String targetPath, String jarFileName) throws IOException {
            if (!new File(rootPath).exists()) {
                throw new IOException(String.format("%s路径不存在", rootPath));
            }
            if (StringUtils.isBlank(jarFileName)) {
                throw new NullPointerException("jarFileName为空");
            }
            //生成META-INF文件
            Manifest manifest = new Manifest();
            manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
            //manifest.getMainAttributes().putValue("Main-Class", "Show");//指定Main Class
            //创建临时jar
            File jarFile = File.createTempFile("edwin-", ".jar", new File(System.getProperty("java.io.tmpdir")));
            JarOutputStream out = new JarOutputStream(new FileOutputStream(jarFile), manifest);
            createTempJarInner(out, new File(rootPath), "");
            out.flush();
            out.close();
            //程序结束后,通过以下代码删除生成的jar文件
           /* Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    jarFile.delete();
                }
            });*/
            //生成目标路径
            File targetFile = new File(targetPath);
            if (!targetFile.exists()) targetFile.mkdirs();
            File targetJarFile = new File(targetPath + File.separator + jarFileName + ".jar");
            if(targetJarFile.exists() && targetJarFile.isFile())targetJarFile.delete();
            FileUtils.moveFile(jarFile, targetJarFile);
            //jarFile.renameTo(new File(""));
        }
    
        /**
         * @Description: 生成jar文件
         * @param out 文件输出流
         * @param f 文件临时File
         * @param base 文件基础包名
         * @return void
         * @Author zhangchengping
         * @Date 2019-06-07 00:02
         */
        private static void createTempJarInner(JarOutputStream out, File f,
                                               String base) throws IOException {
    
            if (f.isDirectory()) {
                File[] fl = f.listFiles();
                if (base.length() > 0) {
                    base = base + "/";
                }
                for (int i = 0; i < fl.length; i++) {
                    createTempJarInner(out, fl[i], base + fl[i].getName());
                }
            } else {
                out.putNextEntry(new JarEntry(base));
                FileInputStream in = new FileInputStream(f);
                byte[] buffer = new byte[1024];
                int n = in.read(buffer);
                while (n != -1) {
                    out.write(buffer, 0, n);
                    n = in.read(buffer);
                }
                in.close();
            }
        }
    }

    程序调用

    import java.io.File;
    import java.io.FileFilter;
    import java.io.IOException;
    
    import com.demo.wms.tasks.utils.CompilerUtils;
    import com.demo.wms.tasks.utils.CreateJarUtils;
    import org.apache.commons.io.FileUtils;
    
    public class BuildRmi {
        //资源文件根路径
        static String basePath = "E:\\WorkSpace\\demo\\WMS_TASK_NC";
        //生成jar文件路径
        static String jarFilePath = "E:\\WorkSpace\\demo\\rmi-client";
        //需要编译的源文件路径
        static String[] srcFiles = {
                "/src/com/demo/wms/tasks/vo/",
                "/rmi/com/demo/wms/tasks/rmi/",
                "/rmi/com/demo/wms/tasks/rmi/po/",
                "/rmi/com/demo/wms/tasks/rmi/client/"
        };
        static String jarReyOnPath = "E:\\WorkSpace\\demo\\WMS_TASK_NC\\WebRoot\\WEB-INF\\lib";
        static String jarFileName = "rim";
        static String encoding = "utf-8";
    
        public static void main(String[] args) {
            String sourcePath = "";
            String classPath = "";
            try {
                // 将RMI需要使用的JAVA文件拷贝到制定目录中
                System.out.println("分隔符:" + File.separator);
                System.out.println("资源拷贝......");
                sourcePath = jarFilePath + File.separator + "source";
                copySource(sourcePath);//拷贝资源
                System.out.println("资源拷贝结束");
                System.out.println("编译资源......");
                //编译java文件
                classPath = jarFilePath + File.separator + "class";
                try {
                    CompilerUtils.compiler(sourcePath, classPath, basePath, encoding, jarReyOnPath);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("编译资源结束");
                System.out.println("生成jar......");
                //生成jar文件
                CreateJarUtils.createTempJar(classPath, jarFilePath, jarFileName);
                System.out.println("生成jar完成");
                //删除临时文件
                ExeSuccess(sourcePath, classPath);
            } catch (IOException e) {
                e.printStackTrace();
                deleteTempFile(sourcePath, classPath);
    
            } finally {
            }
    
        }
    
        private static void ExeSuccess(String sourcePath, String classPath) {
            final String sourcedir = sourcePath;
            final String classdir = classPath;
            //程序结束后,通过以下代码删除生成的文件
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    deleteTempFile(sourcedir, classdir);
                    System.out.println("***************执行完毕**********************");
                }
            });
        }
    
        private static void deleteTempFile(String sourcePath, String classPath) {
            //程序结束后,通过以下代码删除生成的class 和java文件
            try {
                File sourceFile = new File(sourcePath);
                if (sourceFile.exists()) {
                    FileUtils.deleteDirectory(sourceFile);
                }
                File classFile = new File(classPath);
                if (classFile.exists()) {
                    FileUtils.deleteDirectory(classFile);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
        }
    
        private static void copySource(String sourcePath) throws IOException {
            for (String f : srcFiles) {
                String path = f.replace("/", File.separator);
                System.out.println(path);
                File srcFile = new File(basePath + path);
                File targetFile = new File(sourcePath + path);
                FileUtils.copyDirectory(srcFile, targetFile, new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        System.out.println(pathname);
                        return pathname.getName().endsWith(".java");
                    }
                });
            }
        }
    }

     

     

    更多相关内容
  • word文档: 文档名称:动态编译部署及静态库编译
  • JIT动态编译技术

    万次阅读 2020-08-25 16:11:47
    JIT动态编译技术 一个Java程序执行的过程,就是执行字节码指令的过程,一般这些指令会按照顺序一条一条指令解释执行,这种就是解释执行,解释执行的效率是非常低下的,因为需要先将字节码翻译成机器码,才能执行。 ...

    JIT动态编译技术

    一个Java程序执行的过程,就是执行字节码指令的过程,一般这些指令会按照顺序一条一条指令解释执行,这种就是解释执行,解释执行的效率是非常低下的,因为需要先将字节码翻译成机器码,才能执行。

    而那些被频繁调用的代码,比如调用次数很高或者for循环次数很多的那些代码,称为热点代码,如果按照解释执行,效率是非常低下的。

    为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器,就称为即时编译器(Just In Time Compiler),简称JIT编译器。这些被编译后的机器码会被缓存起来,以备下次使用,但对于那些执行次数很少的代码来说,这种编译动作就纯属浪费。

    即时编译器类型

    在HotSpot虚拟机中,内置了两个JIT,分别为C1编译器和C2编译器。

    • C1编译器:是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,例如,GUI应用对界面启动速度就有一定要求,C1也被称为Client Compiler。
    • C2编译器:是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序,C2也被称为Server Compiler。

    热点代码

    热点代码有两类:

    1. 被多次调用的方法。
    2. 被多次执行的循环体。

    JIT即时编译后的机器码都会放在CodeCache里,JVM提供了一个参数-XX:ReservedCodeCacheSize用来限制CodeCach的大小。如果这个空间不足,JIT就无法继续编译,编译执行会变成解释执行,性能会降低一个数量级。同时,JIT编译器会一直尝试去优化代码,从而造成了CPU占用上升。

    # java -XX:+PrintFlagsFinal -version | grep ReservedCodeCacheSize
        uintx ReservedCodeCacheSize                     = 251658240                           {pd product}
    java version "1.8.0_151"
    

    -XX:ReservedCodeCacheSize默认大小为240M。

    热点探测

    J9使用过采样的热点探测技术,但是缺点是很难精确的确认一个方法的热度。

    在HotSpot虚拟机中采用基于计数器的热点探测,为每个方法建立一个计数器,用于统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。

    虚拟机为每个方法准备了两类计数器:方法调用计数器和回边计数器(Back Edge Counter)。

    方法调用计数器

    方法调用计数器(Invocation Counter):用于统计方法被调用的次数,默认阈值在客户端模式下是1500次,在服务端模式下是10000次,可通过-XX: CompileThreshold来设定。

    先来看一下运行模式:

    # java -version
    java version "1.8.0_151"
    Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
    

    从上面的输出可以看出jdk8中默认的运行模式为Server VM

    再来看一下阈值的默认值:

    # java -XX:+PrintFlagsFinal -version | grep CompileThreshold
         intx CompileThreshold                          = 10000                               {pd product}
    

    回边计数器

    回边计数器(Back Edge Counter):用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流在循环边界往回跳转的指令称为“回边”(Back Edge)。

    虚拟机运行在服务端模式下,回边计数器的阈值计算公式为方法调用计数器阈值(CompileThreshold)*(OSR 比率(OnStackReplacePercentage)- 解释器监控比率(InterpreterProfilePercentage)/ 100。

    其中OnStackReplacePercentage默认值为140,InterpreterProfilePercentage默认值为33,如果都取默认值,那Server模式虚拟机回边计数器的阈值为10000*(140-33)/100=10700。

    intx CompileThreshold                          = 10000                               {pd product}
    intx InterpreterProfilePercentage              = 33                                  {product}
    intx OnStackReplacePercentage                  = 140                                 {pd product}
    

    建立回边计数器的主要目的是为了触发OSR(On StackReplacement)编译,即栈上编译。在一些循环周期比较长的代码段中,当循环达到回边计数器阈值时,JVM会认为这段是热点代码,JIT编译器就会将这段代码编译成机器码并缓存,在该循环时间段内,会直接将执行代码替换,执行缓存中的机器码。

    分层编译

    在Java8中,默认开启分层编译。可以通过java -version命令查看到当前系统使用的编译模式。

    # java -version
    java version "1.8.0_151"
    Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
    

    mixed mode为分层编译。

    使用-Xint参数强制虚拟机运行于只有解释器的编译模式下:

    # java -Xint -version
    java version "1.8.0_151"
    Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, interpreted mode)
    

    使用-Xcomp参数强制虚拟机运行于只有解释器的编译模式下:

    # java -Xcomp -version
    java version "1.8.0_151"
    Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, compiled mode)
    

    分层编译根据编译器编译、优化的规模和耗时,划分出5个不同的层次:

    • 第0层:程序纯解释执行,并且解释器不开启性能监控功能(Profiling)。
    • 第1层:使用C1将字节码编译为本地代码,进行简单、可靠的优化,不开启Profiling。
    • 第2层:仍然使用C1编译,仅开启方法调用计数器和回边计数器等Profiling。
    • 第3层:仍然使用C1编译,开启全部Profiling。
    • 第4层:使用C2将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

    在不启用分层编译的情况下,当方法的调用次数和循环回边的次数总和,超过由参数-XX:CompileThreshold指定的阈值时,便会触发即时编译;当启用分层编译时,这个参数将会失效,会采用动态调整的方式进行。

    编译优化技术

    JIT编译运用了一些经典的编译优化技术来实现代码的优化,即通过一些例行检查优化,可以智能地编译出运行时的最优性能代码。

    方法内联

    方法内联的优化行为就是把目标方法的字节码复制到发起调用的方法之中,避免发生真实的方法调用,这样就减少了虚拟机栈中一次栈帧的入栈和出栈。

    C2编译器会在解析字节码的过程中完成方法内联。内联后的代码和调用方法的代码,会组成新的机器码,存放在CodeCache区域里。

    另外,C2支持的内联层次不超过9层,太高的话,CodeCache区域会被挤爆,这个阈值可以通过-XX:MaxInlineLevel进行调整。相似的,编译后的代码超过一定大小也不会再内联,这个参数由-XX:InlineSmallCode进行调整。有非常多的参数,被用来控制对内联方法的选择,整体来说,短小精悍的小方法更容易被优化。

    例如以下方法:

        private int add1(int x1, int x2, int x3, int x4) {
            return add2(x1, x2) + add2(x3, x4);
        }
        private int add2(int x1, int x2) {
            return x1 + x2;
        }
    

    最终会被优化为:

        private int add(int x1, int x2, int x3, int x4) {
            return x1 + x2+ x3 + x4;
        }
    

    下面通过一段代码来演示方法内联的过程:

    public class CompDemo {
        private int add1(int x1, int x2, int x3, int x4) {
            return add2(x1, x2) + add2(x3, x4);
        }
        private int add2(int x1, int x2) {
            return x1 + x2;
        }
    
        public static void main(String[] args) {
            CompDemo compDemo = new CompDemo();
            //方法调用计数器的默认阈值10000次,我们循环遍历超过需要阈值
            for(int i=0; i<1000000; i++) {
                compDemo.add1(1,2,3,4);
            }
    
        }
    }
    

    设置VM参数:-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

    • -XX:+PrintCompilation:在控制台打印编译过程信息。
    • -XX:+UnlockDiagnosticVMOptions:解锁对JVM进行诊断的选项参数。默认是关闭的,开启后支持一些特定参数对JVM进行诊断。
    • -XX:+PrintInlining:将内联方法打印出来。

    运行结果如下:

    ...
        159   29 %     4       com.morris.jvm.jit.CompDemo::main @ 10 (32 bytes)
                                  @ 21   com.morris.jvm.jit.CompDemo::add1 (15 bytes)   inline (hot)
                                    @ 3   com.morris.jvm.jit.CompDemo::add2 (4 bytes)   inline (hot)
                                    @ 10   com.morris.jvm.jit.CompDemo::add2 (4 bytes)   inline (hot)
    ...
    

    方法最后面有个hot关键字,说明已经触发了方法内联。

    通过JITWatch工具也能发现触发了方法内联:

    在这里插入图片描述
    在这里插入图片描述
    如果将循环次数减少至1000次,就不会触发方法内联了,修改后的运行结果如下:

    ....
        116   26       3       com.morris.jvm.jit.CompDemo::add2 (4 bytes)
        116   27       3       com.morris.jvm.jit.CompDemo::add1 (15 bytes)
                                  @ 3   com.morris.jvm.jit.CompDemo::add2 (4 bytes)
                                  @ 10   com.morris.jvm.jit.CompDemo::add2 (4 bytes)
    

    热点方法的优化可以有效提高系统性能,一般我们可以通过以下几种方式来提高方法内联:

    • 通过设置JVM参数来减小热点阈值,以便更多的方法可以进行内联,但这种方法意味着需要占用更多地内存。
    • 在编程中,避免在一个方法中写大量代码,习惯使用小方法体。

    锁消除

    在不需要保证线程安全的情况下,尽量不要使用线程安全容器,比如StringBuffer,由于StringBuffer中的append()方法被synchronized关键字修饰,会使用到锁,从而导致性能下降。

    但实际上,在以下代码测试中,StringBuffer和StringBuilder的性能基本没什么区别。这是因为在局部方法中创建的对象只能被当前线程访问,无法被其它线程访问,这个变量的读写肯定不会有竞争,这个时候JIT编译会对这个对象的方法锁进行锁消除。

    public class UnLock {
        public static void main(String[] args) {
            long timeStart1 = System.currentTimeMillis();
            for (int i = 0; i < 10000000; i++) {
                testStringBuffer("aaaaa", "bbbbb");
            }
            long timeEnd1 = System.currentTimeMillis();
            System.out.println("StringBuffer cost: " + (timeEnd1 - timeStart1) + "(s)");
    
            long timeStart2 = System.currentTimeMillis();
            for (int i = 0; i < 10000000; i++) {
                testStringBuilder("aaaaa", "bbbbb");
            }
            long timeEnd2 = System.currentTimeMillis();
            System.out.println("StringBuilder cost: " + (timeEnd2 - timeStart2) + "(s)");
        }
    
        public static void testStringBuffer(String s1, String s2) {
            StringBuffer sb = new StringBuffer();
            sb.append(s1);
            sb.append(s2);
        }
    
        public static void testStringBuilder(String s1, String s2) {
            StringBuilder sd = new StringBuilder();
            sd.append(s1);
            sd.append(s2);
        }
    }
    

    运行结果如下:

    StringBuffer cost: 229(s)
    StringBuilder cost: 190(s)
    

    当我们把锁消除(-XX:-EliminateLocks)关闭后,运行结果如下:

    StringBuffer cost: 570(s)
    StringBuilder cost: 148(s)
    

    锁消除关闭后测试发现性能差别有点大。

    -XX:+EliminateLocks:开启锁消除(jdk1.8 默认开启)。

    -XX:-EliminateLocks:关闭锁消除

    逃逸分析与标量替换

    通过逃逸分析(Escape Analysis),JVM能够分析出一个新对象的使用范围,从而决定是否要将这个对象分配到堆上。

    对象的三种逃逸状态:

    • GlobalEscape(全局逃逸):一个对象的引用逃出了方法或者线程。例如,一个对象的引用是复制给了一个类变量,或者存储在在一个已经逃逸的对象当中,或者这个对象的引用作为方法的返回值返回给了调用方法。
    • ArgEscape(参数逃逸):在方法调用过程中传递对象的引用给调用方法。
    • NoEscape(没有逃逸):该对象只在本方法中使用,未发生逃逸。

    下面用一段代码来说明对象的三种逃逸状态:

    package com.morris.jvm.gc;
    
    /**
     * 演示逃逸分析的三种状态
     * 1. 全局逃逸
     * 2. 参数逃逸
     * 3. 没有逃逸
     */
    public class EscapeStatus {
    
        private B b;
    
        /**
         * 给全局变量赋值,发生逃逸(GlobalEscape)
         */
        public void globalVariablePointerEscape() {
            b = new B();
        }
    
        /**
         * 方法返回值,发生逃逸(GlobalEscape)
         */
        public B methodPointerEscape() {
            return new B();
        }
    
        /**
         * 实例引用传递,发生逃逸(ArgEscape)
         */
        public void instancePassPointerEscape() {
            methodPointerEscape().printClassName(this);
        }
    
        /**
         * 没有发生逃逸(NoEscape)
         */
        public void noEscape() {
            Object o = new Object();
        }
    }
    
    class B {
        public void printClassName(EscapeStatus escapeStatus) {
            System.out.println(escapeStatus.getClass().getName());
        }
    }
    

    可以用JVM参数-XX:+DoEscapeAnalysis来开启逃逸分析,JDK8默认开启。

    逃逸分析的性能测试:

    package com.morris.jvm.gc;
    
    /**
     * 演示逃逸分析的标量替换
     * VM args:-Xmx50m -XX:+PrintGC -XX:-DoEscapeAnalysis --> 682ms+大量的GC日志
     * VM args:-Xmx50m -XX:+PrintGC --> 4ms,无GC日志
     */
    public class EscapeAnalysisDemo {
    
        public static void main(String[] args) {
    
            long start = System.currentTimeMillis();
            for (int i = 0; i < 1_0000_0000; i++) {
                allocate();
            }
            System.out.println((System.currentTimeMillis() - start) + " ms");
        }
    
        private static void allocate() {
            new Person(18, 120.0);
        }
    
        private static class Person {
            int age;
            double weight;
    
            public Person(int age, double weight) {
                this.age = age;
                this.weight = weight;
            }
        }
    
    }
    

    使用jvm参数-Xmx50m -XX:+PrintGC -XX:-DoEscapeAnalysis运行程序耗时682ms,控制台会打印大量的GC日志。

    使用jvm参数-Xmx50m -XX:+PrintGC运行程序耗时4ms,控制台没有打印GC日志,也就是没有发生GC。由此可以发现开启逃逸分析后,对象分配的性能显著提升。

    标量:一个数据无法再分解为更小的数据来表示,Java中的基本数据类型byte、short、int、long、boolean、char、float、double以及reference类型等,都不能再进一步分解了,这些就可以称为标量。

    标量替换:如果一个对象只是由标量属性组成,那么可以用标量属性来替换对象,在栈上分配。

    例如上面的Persion只是由int和double类型的属性构成,可以进行标量替换,替换后变成类似如下的代码:

        private static void allocate() {
            int age = 18;
            double weight = 120.0;
        }
    

    变成上面的代码后,这样基本数据类型就可以在栈上分配了。

    而下面的Person类无法进行标量替换,只能在堆上分配了:

        private static class Person {
            byte[] bytes = new byte[1024]; // 不是标量
            String name;
            int age;
        }
    

    -XX:+EliminateAllocations:开启标量替换(jdk1.8 默认开启)。

    -XX:-EliminateAllocations:关闭标量替换。

    展开全文
  • Linux之静态编译与动态编译

    千次阅读 2019-01-24 18:50:14
    可以事先对这些函数进行编译,然后将它们放置在一些特殊的目标代码文件中,这些目标代码文件就称为库,供其它程序使用(代码的复用) 库文件中的函数可以通过连接程序与应用程序进行链接,这样就不必在每次开发程序...

    日常编程中,常有一些函数在多个文件中使用(如数据库输入/输出操作或屏幕控制等标准任务函数)。可以事先对这些函数进行编译,然后将它们放置在一些特殊的目标代码文件中,这些目标代码文件就称为库,供其它程序使用(代码的复用)

    库文件中的函数可以通过连接程序与应用程序进行链接,这样就不必在每次开发程序时都对这些通用的函数进行编译了。库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。

    库分静态库和动态库两种,一般的静态编译即可以理解为加载静态链接库,静态链接库在程序编译时会被链接到目标代码中,程序运行时将不再需要该静态库;动态编译理解为加载动态链接库,在程序运行时才被载入,因此在程序运行时还需要动态库存在。下面分别进行阐述总结

    一、Linux下的动态库和动态编译

    动态函数库的扩展名一般为(.so),这类函数库通常名为libxxxx .so 。与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说动态库在可执行程序运行时才载入内存

    一般,在程序第一次执行或者第一次调用的时候,可能会在加载链接库的时候慢点,但这样减少看每个可执行文件的长度, 而且其他的程序如果发现内存中已经存在这个链接库的时候就不需要再次加载了。

    另外,动态库链接的时候,它只是保留接口,将动态库与程序代码独立,这样就可以提高代码的可复用度,和降低程序的耦合度,也就是说使用共享库的可以使用库函数来迭代更新,应用程序将无需更改,只要函数的接口不变。

    示例

    1、使用gcc动态编译printf(“Hello,world”);生成动态链接库myhello

    gcc hello.c -fPIC -shared -o libmyhello.so
    
    • -shared:指定生成动态连接库
    • -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的,导致动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

    动态库的名字一般是libxxxx .so,其中xxxx是该lib的名称

    2、动态编译main.c,链接myhello动态库,即指明动态链接库为myhello

    gcc -o hello main.c -L. -lmyhello
    

    其中-L选项,告诉hello库的位置在当前目录下

    在这里插入图片描述
    3、上面的测试结果可以发现,提示无法找到libmyhello,因为Linux系统默认从/usr/lib中找动态链接库,而该链接库在当前目录,自然找不到。解决这个问题主要有三个途径:

    • 可以把当前路径加入 /etc/ld.so.conf中然后运行ldconfig,或者以当前路径为参数运行ldconfig(要有root权限才行)
    • 把当前路径加入环境变量LD_LIBRARY_PATH中
    • 直接把该动态库复制到usr/lib目录中后运行ldconfig更新库

    测试中使用的是第三种方式,方便,不容易引起混乱。

    在这里插入图片描述
    在这里插入图片描述

    二、Linux下的静态库和静态编译

    静态函数库一般扩展名为(.a),这类的函数库通常扩展名为libxxx.a 。这类函数库在编译的时候会直接整合到程序中,所以利用静态函数库编译成的文件会比较,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。

    示例

    1、编译.c文件为.o文件gcc -c hello.c

    2、把目标文件归档ar -r libmyhello.a hello.o
    程序 ar 配合参数 -r 创建一个新库 libmyhello.a ,并将hello.o插入。如果库不存在的话,参数 -r 将创建一个新的库,而如果库存在的话,将用新的模块替换原来的模块。

    3、链接静态库libmyhellogcc -o test main.c -L. -lmyhello -static
    -L与动态链接命令中的一致,指明动态链接库在当前目录中,注意命令尾部还有一个-static说明,表明是静态编译。

    4、直接运行可执行文件test
    在这里插入图片描述

    三、静态编译相关静态库的安装

    1、先直接静态编译,看能否成功。

    2、如果不成功,则先动态编译,生成可执行文件

    3、ldd 可执行文件名命令查看该可执行文件依赖哪些动态库

    4、找到缺失的动态库,使用rpm -qf查看该文件对应的包
    在这里插入图片描述
    5、使用yum search搜索该缺失的包对应的库文件

    6、根据描述选择相应的静态库更新安装
    在这里插入图片描述
    7、完成上述步骤后重新进行回到步骤1,直到静态编译成功为止

    展开全文
  • 动态加载代码并编译,免去了vs编译-->再启动这一环节。
  • C语言静态编译和动态编译

    千次阅读 多人点赞 2018-11-12 00:51:25
    可执行文件是计算机可以直接执行的程序,与windows系统的.exe程序相似,它是由源代码经过一定的手段翻译成计算机能够读懂的二进制编码,由计算机直接去执行,这个翻译的过程就称之为编译。 脚本文件 脚本文件是一...

    概述

    在Linux系统中,应用程序表现为两种文件,一种是可执行文件, 另一种是脚本文件

    可执行文件

    可执行文件是计算机可以直接执行的程序,与windows系统的.exe程序相似,它是由源代码经过一定的手段翻译成计算机能够读懂的二进制编码,由计算机直接去执行,这个翻译的过程就称之为编译

    脚本文件

    脚本文件是一系列指令的组合,由另一个解释器来执行,相当于windows系统的.bat文件。
    与windows系统不同之处在于,windows系统通常由后缀来决定该文件是什么文件,而Linux系统则与后缀无关,而是由文件的权限来决定的。可以有后缀,也可以没有后缀。
    关于文件的权限相关,会在后续的文章中展开讨论,此处就不多做说明了。

    Linux应用程序目录结构

    一级目录二级目录三级目录说明
    /bin二进制文件目录,用于存放启动时用到的程序
    /usrbin用户二进制目录,用于存放用户使用的标准程序
    /usrlocalbin本地二进制目录,用于存放软件安装的程序
    /usrsbinroot用户登录后的PATH管理目录
    /opt第三方应用程序安装目录

    编译器初探

    将源代码转换成计算机能够识别的二进制编码的过程称之为编译,编译使用的工具即为编译器。
    在POSIX兼容的系统中,C语言编译器被称为c89,简称为cc。在Unix系统中,普遍使用的都是cc编译器。而我们这里讨论的gcc编译器是随着Linux发行版一起提供的,全称是GNU C编译器。

    普通程序的编译

    关于编译器的使用,不妨通过一个简单的例子来说明。
    我们简单的写出一个C程序如下,该程序即编程者的入门程序hello.c

    #include <stdio.h>
    
    int main(void)
    {
        printf("hello world\n");
        return 0;
    }
    

    该程序的编译命令如下:

    $ gcc -o hello hello.c
    

    执行以上命令后,会在该目录下生成一个名为hello的可执行文件,使用以下命令即可执行该程序:

    $ ./hello
    hello world
    

    下面来对该编译命令做一下说明:

    • -o 紧跟着的是指定编译后可执行文件的名称,上述命令中,-o hello即指定hello为可执行文件。如果不指定-o, 则默认生成一个叫做a.out的可执行文件。即:上述编译命令直接写成gcc hello.c也是没有问题的,不过可执行文件变成了a.out,执行该程序就是:
    $ ./a.out
    hello world
    
    • hello.c 是需要编译的源文件,该文件是开发者写好的代码,可以有多个,也可以只有一个。
    • 在执行应用程序中,./代表的是当前目录,如果不加./,系统会默认到PATH环境变量中去寻找,如果找不到则会报错,如果恰巧找到了一个同名的可执行程序,程序会执行,但得到的结果并不是我们所需要的,因为它执行的并不是我们编译好的可执行程序。

    链接头文件

    使用C语言进行程序设计时,需要链接头文件进行系统及库函数的调用,这时候就需要链接头文件。Linux系统常用的系统头文件都存放在/usr/include目录下,该目录能够被gcc编译器自行检索到并主动链接到程序里。
    但对于有些用户自定义的头文件,编译器并不能搜索到其目录,这时候就需要在编译的时候去指定需要链接的头文件的路径。链接头文件的命令是在编译的时加上大写的-I,后面紧跟头文件的路径。如下所示:

    $ gcc -o fred -I/usr/openwin/include fred.c
    

    注意:-I和头文件路径之间不要有空格。

    库文件

    库是一组预先编译好的函数组合,其特点是可重用性好,通常由一组相互关联的函数组成,用以执行某项任务。
    系统的标准库函数存放在/lib或者/usr/lib目录下,与头文件必须以.h作为后缀一样,库函数同样也需要遵循一些规范。
    库函数必须以lib作为开头,以.a或者.so作为结尾。其中,.a代表静态函数库,.so代表动态函数库。比较典型的比如:libc.alibm.a即代表标准C函数库和标准数学函数库。

    静态编译

    由于历史原因,编译器仅能识别标准c函数库,部分系统库函数,即使已经放在/usr/lib目录下,编译器仍然不能够识别,这时候就需要在编译的时候告诉编译器使用的是哪个库函数,编译时可以通过给出完整的静态库绝对路径的方式,或使用-l标志来告诉编译器你所使用的静态库函数。如:

    $ gcc -o fred fred.c /usr/lib/libm.a
    $ gcc -o fred fred.c -lm
    

    以上两个命令达到的效果是一样的,可以通过ls /usr/lib命令来查看系统的库函数。
    除此之外,用户也可以自己定义库函数,链接非标准位置的自定义库函数可以通过大写的-L标志来实现,命令如下:

    $ gcc -o xllfred  -L/usr/openwin/lib xllfred.c -lxll  
    

    创建静态库

    使用ar命令(archive)可以很容易地创建属于自己的静态库。ar命令一般对.o的目标文件进行操作,目标文件可以由gcc -c命令得到。
    下面就以一个具体的例子来说明一下。
    首先,我们有如下两个源程序文件:

    $ ls
    main.c  print.c test.h
    $ pg print.c
    #include <stdio.h>
    
    int print()
    {
        printf("Hello world\n");
        return 0;
    }
    
    $ pg main.c                                                        
    #include "test.h"
    
    int main()
    {
        print();
        return 0;
    }
    
    $ pg test.h
    int print();
    

    先通过gcc -c命令将其编译成.o文件:

    $ gcc -c *.c                                                        
    $ ls 
    main.c  main.o  print.c  print.o  test.h
    

    我们可以看到两个.o的目标文件已经成功生成。
    这时候,如果我们使用以下命令,是可以直接编译成功的:

    $ gcc -o test *.o
    $ ls
    main.c  main.o  print.c  print.o  test.h test
    $ ./test
    Hello world
    

    但是这里由于我们是要创建静态库,所以可以使用ar命令来创建一个归档文件:

    $ ar crv libtest.a print.o
    a - test2.o
    $ ls
    libtest.a  main.c  main.o  print.c  print.o  test  test.h
    

    可以看到,静态库libtest.a已经成功创建,这时,还需要使用ranlib命令来生成一个内容表,这一步在Unix系统中必不可少,但在Linux中,当使用的是GUN开发工具时,这一步可以省略。以上步骤完成后,即可以使用下面的命令来编译程序:

    $ ranlib libtest.a
    $ gcc -o testa main.o -L./ -ltest
    $ ls
    libtest.a  main.c  main.o  print.c  print.o  test  testa  test.h
    $ ./testa
    Hello world
    

    通过以上案例,可以发现得到的效果其实是一样的。
    当然,也可以使用以下命令,得到相同的效果(这里因为没有链接头文件,会报一个错,但是结果没有影响):

    $ gcc -o testb main.c -L./ -ltest  
    $ ls
    libtest.a  main.c  main.o  print.c  print.o  test  testa  testb  test.h
    $ ./testb
    Hello world
    

    动态编译

    静态库有一个局限性,当同时运行多个程序,并且这些程序都去调用同一个静态库函数时,就会出现多个函数库副本,会占用大量的虚拟内存和磁盘空间,这时候,动态库(又叫共享库)可以解决这个问题。
    当程序使用动态库时,其链接方式是这样的:程序本身不包含代码,而是引用运行时可访问的共享代码。即,只有在必要的时候,才会加载到内存中。
    即,可以简单的理解如下:静态库在编译时就已经把内存给分配好了,每一次调用,都会分配一份内存。而动态库是在运行时才会去访问共享代码分配的内存,永远只会存在一份,因此不会出现内存浪费。
    动态库的格式是以.so结尾,比如典型的有/lib/libm.so
    装载和解析动态库的工具是ld.so,如果需要搜索标准位置以外的动态库,则需要在/etc/ld.so.conf中进行配置,然后执行ldconfig来处理它。
    可以使用ldd命令来查看程序所需要的动态库文件,如:

    $ ldd test
            linux-vdso.so.1 =>  (0x00007ffc0e4a8000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd26176b000)
            /lib64/ld-linux-x86-64.so.2 (0x000056170c352000)
    

    静态库与动态库的一个明显的区别在于:使用静态库编译完成后,静态库被删除,不会影响可执行文件的执行,但是如果删除了动态库,可执行文件执行就会报错。

    创建动态库

    仍然是看上面这个程序:

    $ ls
    libtest.a  main.c  main.o  print.c  print.o  test  testa  testb  test.h
    

    动态编译的命令是针对共享代码进行操作的,命令如下:

    $ gcc -shared -fPIC -o libtest.so print.c
    $ ls
    libtest.a  libtest.so  main.c  main.o  print.c  print.o  test  testa  testb  test.h
    

    这样libtest.so就已经生成了,编译时链接如下:

    $ gcc -o testc main.c ./libtest.so
    $ ls
    libtest.a  libtest.so  main.c  main.o  print.c  print.o  test  testa  testb  testc  test.h
    $ ./testc
    Hello world
    

    程序的编译过程

    C程序的编译共有四个步骤组成,分别为:

    • 预处理
    • 编译
    • 汇编
    • 链接
      编译过程

    为了可以直观的说明这个过程,我们不妨使用一个案例演示一下:
    假设有两个文件,头文件hello.h 和源文件 hello.c:

    $ ls
    hello.c  hello.h
    $ pg hello.h
    # include<stdio.h>
    $ pg hello.c
    #include "hello.h"
    
    int main()
    {
        printf("Hello world\n");
        return 0;
    }
    

    预处理

    预处理主要完成的工作是去注释、头文件包含和宏替换,该步骤并不会检查语法。预处理命令为:

    $ gcc -E -I./ hello.c -o hello.i                
    $ ls
    hello.c  hello.h  hello.i
    

    预处理完成后,会由.c文件生成一个.i文件。

    编译

    编译步骤完成的功能是将预处理之后的程序转换为汇编语言代码。编译命令如下:

    $ gcc -S  hello.i -o hello.s 
    $ ls
    hello.c  hello.h  hello.i  hello.s
    

    这一步会将.i文件生成为.s格式的汇编语言代码。在该步骤中会检查语法,如果语法有错误,会在这一步报出来。

    汇编

    汇编就是将汇编语言程序处理成二进制目标文件。其命令如下:

    $ gcc -c  hello.s -o hello.o   
    $ ls
    hello.c  hello.h  hello.i  hello.o  hello.s
    

    或者使用以下命令:

    $ as  hello.s -o hello.o       
    $ ls
    hello.c  hello.h  hello.i  hello.o  hello.s
    

    这两个命令实际是等价的。

    链接

    链接是最后一步,即将多个目标文件,或者静态库文件(.a)以及动态库文件(.so)链接成最后的可执行程序的过程。其命令如下:

    $ gcc -o hello hello.o  
    $ ls
    hello  hello.c  hello.h  hello.i  hello.o  hello.s
    $ ./hello
    Hello world
    

    如果使用了动态库,可能使用到ld链接命令。

    通过以上分析发现,看似简单的一条编译命令,其内部完成的步骤是相当复杂的,能够理解编译器编译的原理,对编程是有相当大的帮助的。当然实际开发过程中,只需要使用以下编译命令一步到位,它完成了以上所有四步命令的所有过程:

    $ gcc -o hello hello.c
    $ ./hello
    hello world
    

    结语

    关于gcc编译命令还有很多,这里就不多做介绍了,如果有需要,可以通过man gcc或者info gcc来获取帮助。

    展开全文
  • 静态编译与动态编译的区别

    千次阅读 2019-04-18 21:23:28
    静态编译与动态编译的区别  动态编译的可执行文件需要附带一个的动态链接库,在执行时,需要调用其对应动态链接库中的命令。所以其优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统...
  • .net动态编译demo

    热门讨论 2013-03-23 11:46:20
    .net动态编译输入的代码片段,自动解析仿aspx页面为html
  • springboot动态发布接口 动态编译class

    千次阅读 2020-05-17 20:05:36
    1.通过velocity模板,生成Java模板 ...动态编译成class 3.通过class.forName("包类名字")加载字节码成为class对象 4.通过获取spring的ApplicationContext手动把mapping注册到RequestMappingHandlerMapping...
  • 最近一直在研究Java8 的动态编译, 并且也被ZipFileIndex$Entry 内存泄漏所困扰,在无意中,看到一个第三方插件的动态编译。并且编译速度是原来的2-3倍。原本打算直接用这个插件,但是发现插件的编译源码存在我之前...
  • java实现动态编译并动态加载

    万次阅读 2018-09-19 09:40:55
    //动态编译 JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); int status = javac.run(null, null, null, "-d", System.getProperty("user.dir")+"\\target\\classes","D:/test/AlTest.java"); ...
  • gcc 静态编译与动态编译

    千次阅读 2018-04-14 15:50:02
    gcc中的连接选项(Linker Options):-LDirectory-L 用于指定连接库所需要的搜索路径,后面的参数“Directory”为相对路径或绝对路径-llibrary-l ...如果存在动态库,则意味着需要连接libmylib.so-static -static ...
  • 开心一笑【年底是各种案件的高发期,我们去ATM取钱的时候,一定要注意遮挡,不要被陌生人看到你的余额,要不然啊,就,,,非常容易被人嘲笑。... 】提出问题java中的动态编译和静态编译如何理解???解
  • Java类文件动态编译并执行方法

    千次阅读 2018-07-16 10:25:41
    * @Description: 测试动态编译并且加载类并执行对应方法 * @param: @param expr * @param: @param classPath * @param: @return * @param: @throws Exception * @return: double * @throws */ ...
  • 动态编译配置 这时,自己想起了以前学习Spring Boot时,使用的动态编译。 具体参考了博客:IDEA 编写 SpringBoot 项目自动编译刷新 与 同一应用启动多次 首先,在pom.xml中添加热部署依赖。 <dependency> <groupId>...
  • vue的动态编译

    千次阅读 2018-12-28 17:11:38
    动态编译的需求: 如在写一个表格组件时,我们可能需要表格的某一列是可定制化的,比如这一列可实现点击响应一个事件,要实现这样一个需求,可通过写配置文件的方式实现 实现方式1 data(){  let me = this  ...
  •  MD选项:使用DLL版的C和C++运行库,这样在程序运行时会动态的加载对应的DLL,程序体积会减小,缺点是在系统没有对应DLL时程序无法运行。  MDd选项:表示使用DLL的调试版。  在 《由使用LeakDialog时遇到的问题...
  • 使用 动态编译在 运行期根据配置文件生成java代码 并且编译为class 加载到 classloader中 的玩法已经用了一年多了,但是一直有个坑就是 在编译Java class的时候需要 提取依赖jar包到 服务器的某个目录中,然后加上- ...
  • Java动态编译和动态加载详解

    千次阅读 2018-10-15 13:58:43
    动态编译 在某些情况下,我们需要动态生成java代码,通过动态编译,然后执行代码。JAVA API提供了相应的工具(JavaCompiler)来实现动态编译。 //获取JavaCompiler JavaCompiler compiler = ToolProvider....
  • java实现动态编译的几种方法

    万次阅读 2018-09-19 09:05:00
    所谓动态编译,就是在程序运行时产生java类,并编译成class文件。    在D盘test目录下有两个java文件:AlTest1.java、AlTest2.java,现需要通过java代码实现java文件到class文件的编译操作: import java.io....
  • java动态编译的几种方法(简单实现)

    千次阅读 2020-06-08 16:07:58
    一、应用场景 可以做一个浏览器端编写java代码,上传服务器编译运行 在线评测系统 服务器动态加载某些类文件进行编译 ...通过javaCompiler动态编译 三、具体实现 1、使用Runtime来进行动态编译 Runtime...
  • 动态编译、静态编译区别(转)

    千次阅读 2018-08-07 12:06:28
    1.静态编译:编译器在编译可执行文件时,把需要用到的对应动态链接库(.so或.ilb)中的...2.动态编译: 动态编译的可执行文件需要附带一个的动态链接库,在执行时,需要调用其对应动态链接库中的命令。所以其优点一 ...
  • Java中的静态编译和动态编译

    千次阅读 2015-11-16 17:29:10
    这些改进及其他一些类似的改进所产生的综合效果是:对于大量的 Java 应用程序来说,动态编译已经弥补了与 C 和 C++ 之类语言的静态本地编译性能之间的差距,在某些情况下,甚至超过了后者的性能。 缺点 但是...
  • 由于unity并不完全支持所有的c#支持的dll库,如:Mircrosoft.CSharp.dll 就不支持,所以unity在运行时并不能动态编译脚本,我的做法是利用unity与c#控制台程序进行socket通信达到在unity运行时动态编译unity脚本的...
  • 静态编译和动态编译的区别

    千次阅读 2018-04-28 11:02:44
    静态编译:在编译时确定类型,绑定对象,即... 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。 java中的反射就是运用了动态编译创建对象。...
  • 动态编译的应用场景 Java 6.0之后要引入了动态编译动态编译可以实现:让用户提交一段Java代码,上传到服务器,运行,实现在线评测系统。 Java 6.0之前,可以使用这种方式,达到动态编译的效果: Java 6.0之后,...
  • Java动态编译JavaCompiler

    千次阅读 2018-10-27 19:52:16
    Java在进行动态编译的时候需要用到tools.jar资源包,此包在jdk\lib目录中。若tools.jar不存在则会出现进行编译时提示空指针异常: 对于缺少jar包,首先想到的解决办法是找到需要的jar包并且将其加入到buildpath,...
  • linux动态编译和静态编译

    千次阅读 2017-11-27 19:50:22
    动态链接库编译生成的 可执行文件需调用.so文件方可正常运行,灵活但稍显麻烦;用静态链接库编译生成的可执行文件可直 接运行,不用再调用如.so般的依赖库文件,简单但不灵活。 静态链接库: 1、编译...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 961,877
精华内容 384,750
关键字:

动态编译