JVM----类加载和初始化_jvm 类加载 初始化-程序员宅基地

技术标签: JVM  jvm  java  

JVM-类加载和初始化

在这里插入图片描述
类加载-初始化

  1. loading 把class文件加载到内存
  2. linking
  3. Verification:校验class文件是否符合标准
  4. preparation:给静态变量赋默认值,如给static int i = 8赋值为i=0
  5. resolution:常量池中的用到的那些符号引用要准换成能访问到的内存地址
  6. initializing :这时候才会调用静态代码块给静态变量赋值

类加载器

在这里插入图片描述

loading

jvm中所有的class都是被classloader加载到内存
以上几个类加载器的关系不是继承,是父加载器与自加载器的关系。

双亲委派

  • 父加载器
    父加载器不是“类加载器的加载器”
  • 双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程

那么问题来了, 为什么要搞双亲委派
java.lang.String类由自定义加载器加载行不行?

回答这个问题, 首先要弄明白class的加载过程。

根据上图所示,一个class类首先要经过CustomClassloader(自定义类加载器),查询其缓存中是否已经将该class加载,如果有,则将其返回,没有,则向上检查,此时到了APP(AppClassLoader,同样检查其缓存是否已加载,没有,则继续向上,Extension加载器同样如此,一直检查到BootStrap加载器,当Bootstrap加载器同样没有加载该calss时,开始自顶向下进行实际查找和加载。首先判断该类是否该由Bootstrap加载,不是,则向下,一直到Custom加载器,如果没有找到,则抛异常(ClassNotFound)。

主要是为了安全
假设自定义了一个Java.lang.String,覆盖sun的String,同时自定义一个String的类加载器,将自定义的这个String加载到内存,接下来将整个自定义部分打包成一个类库,交给客户使用 ,此时客户输入密码将会变得非常不安全。
但是采用双亲委派就不会有这个问题,自低向上检查,一直到Bootstap,发现String类已经被Bootstrap加载,其他加载器便不能再次加载这个类,从而保证了安全。

类加载过程

在这里插入图片描述

类加载器范围

在这里插入图片描述
这些加载范围是由launcher的源码决定
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
查看每个目录下都有哪些jar包

public class T003_ClassLoaderScope {
    
    public static void main(String[] args) {
    
        String pathBoot = System.getProperty("sun.boot.class.path");
        System.out.println(pathBoot.replaceAll(";", System.lineSeparator()));

        System.out.println("--------------------");
        String pathExt = System.getProperty("java.ext.dirs");
        System.out.println(pathExt.replaceAll(";", System.lineSeparator()));

        System.out.println("--------------------");
        String pathApp = System.getProperty("java.class.path");
        System.out.println(pathApp.replaceAll(";", System.lineSeparator()));
    }
}

输出结果

C:\Program Files\Java\jdk1.8.0_51\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_51\jre\classes
--------------------
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
--------------------
C:\Program Files\Java\jdk1.8.0_51\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\deploy.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\access-bridge-64.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\cldrdata.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\dnsns.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\jaccess.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\jfxrt.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\localedata.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\nashorn.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunec.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunjce_provider.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunmscapi.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\sunpkcs11.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\ext\zipfs.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\javaws.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jfxswt.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\management-agent.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\plugin.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_51\jre\lib\rt.jar
G:\SoftWare\Java\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar

ClassLoader类加载器

package com.cyc.jvm.c2_classloader;

/**
 * classloader加载器
 */
public class T002_ClassLoaderLevel {
    
    public static void main(String[] args) {
    
        //String是由 bootStrapClassLoader加载的
        System.out.println(String.class.getClassLoader());
        System.out.println(sun.awt.HKSCS.class.getClassLoader());
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
        //自定义类的classLoader的类加载器为AppClassLoader
        System.out.println(T002_ClassLoaderLevel.class.getClassLoader());
        //extClassLoader的类加载器是BootStrapClassLoader加载的
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
        System.out.println(T002_ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());

        //一个自定义classLoader的默认父classLoader是AppClassLoader
        System.out.println(new T006_CYCClassLoader().getParent());
        //ClassLoader的systemClassLoader也是AppClassLoader
        System.out.println(ClassLoader.getSystemClassLoader());
    }
}

自定义类加载器

准备阶段

  1. 一个需要解析的的class文件
package com.cyc.jvm;

public class Hello {
    
    public void m() {
    
        System.out.println("Hello JVM!");
    }
}

  1. 去项目目录下找到Hello的class文件, 带上根目录复制到D盘的test文件夹下

在这里插入图片描述

  1. 自定义类加载器

public class T006_CYCClassLoader extends ClassLoader {
    

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
        File f = new File("D:/test/", name.replace(".", "/").concat(".class"));
        try {
    
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            while ((b=fis.read()) !=0) {
    
                baos.write(b);
            }

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();//可以写的更加严谨

            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
    
            e.printStackTrace();
        }
        return super.findClass(name); //throws ClassNotFoundException
    }

    public static void main(String[] args) throws Exception {
    
        ClassLoader l = new T006_CYCClassLoader();
        Class clazz = l.loadClass("com.cyc.jvm.Hello");
        Class clazz1 = l.loadClass("com.cyc.jvm.Hello");
        //由于使用的是同一个类加载器,加载出来的是同一个class对象, 所以这里会输出true
        System.out.println(clazz == clazz1);

        Hello h = (Hello)clazz.newInstance();
        h.m();
        //自定义类加载器的类加载器是AppClassLoader
        System.out.println(l.getClass().getClassLoader());
        //他的父加载器同样也是AppClassLoader,但是注意, 他们之间不是继承关系。这些类加载器继承的都是ClassLoader
        System.out.println(l.getParent());
        System.out.println(getSystemClassLoader());
    }
}

lazyloading

严格来讲应该叫lazylnitializing

public class T008_LazyLoading {
     //严格讲应该叫lazy initialzing,因为java虚拟机规范并没有严格规定什么时候必须loading,但严格规定了什么时候initialzing
    public static void main(String[] args) throws Exception {
    
        P p;
        X x = new X();
        System.out.println(P.i);
        System.out.println(P.j);
        Class.forName("com.cyc.jvm.c2_classloader.T008_LazyLoading$P");

    }

    public static class P {
    
        final static int i = 8;
        static int j = 9;
        static {
    
            System.out.println("P");
        }
    }

    public static class X extends P {
    
        static {
    
            System.out.println("X");
        }
    }
}

混合模式

在这里插入图片描述
测试

package com.cyc.jvm.c2_classloader;

public class T009_WayToRun {
    
    public static void main(String[] args) {
    
        for(int i=0; i<10_0000; i++)
            m();

        long start = System.currentTimeMillis();
        for(int i=0; i<10_0000; i++) {
    
            m();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    public static void m() {
    
        for(long i=0; i<10_0000L; i++) {
    
            long j = i%3;
        }
    }
}

首先是混合模式,这也是默认的运行模式
在这里插入图片描述
混合模式运行时间

在这里插入图片描述
解释模式
在这里插入图片描述
查看运行时间(时间过于漫长)

编译模式
在这里插入图片描述

查看运行时间
在这里插入图片描述

初始化(initializing)

package com.cyc.jvm.c2_classloader;

public class T001_ClassLoadingProcedure {
    
    public static void main(String[] args) {
    
        System.out.println(T.count);
    }
}

class T {
    
    //这个顺序,由于new T(),会进行T的初始化, 给T赋值为null,T中的成员编程int型的count赋值为0,
    //然后调用T的构造方法, 执行count++, 此时count为1,接来下开始执行 public static int count = 2;在这里给count赋值为2
    public static T t = new T(); // null
    public static int count = 2; //0

    private T() {
    
        count ++;
        //System.out.println("--" + count);
    }
}
class T {
    

    //首先归对象T进行初始化, 此时对象为null, 对象内的变量count赋值为默认值0,然后在initial阶段给count赋指定值2
    //接着调用T的构造方法, 执行count++, count变为3
    public static int count = 2; //2->3
    public static T t = new T(); // null->对象

    private T() {
    
        count ++;
        //System.out.println("--" + count);
    }
}

new对象的过程其实也是分为两步, new 出来T , 先给里面的成员变量赋默认值,new出来T,申请完内存之后,开始调用构造方法,才给成员变量赋初始值。

扩展

结合单例模式解析初始化过程

public class Singleton06 {
    

    //volatile关键字禁止指令重排
    private static volatile Singleton06 INSTANCE;


    public void c() {
    
        System.out.println("C");
    }

    /**
     * 构造方法为私有,只能在当前类中new,外部类无法new出来
     */
    private Singleton06() {
    
    }

    public static Singleton06 getInstance() {
    
        if (INSTANCE == null) {
    
            //双重检查
            synchronized (Singleton06.class) {
    
                if (INSTANCE == null) {
    
                    try {
    
                        //这里让进入此代码块的线程睡一毫秒
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
    
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton06();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
    
        for (int i = 0; i < 100; i++) {
    
            new Thread(() ->
                    System.out.println(Singleton06.getInstance().hashCode())
            ).start();
        }
    }

}

为什么要加volatile?

为了防止指令重排。这里涉及到类的加载过程。

首先,第一个线程进来了, 加上锁之后,进入到INSTANCE = new Singleton06();代码,在初始化进行到一半的时候,也就是在preparation阶段,已经给Singleton06申请完内存,里面的成员变量已经赋过默认值,比如0,此时INSTANCE 已经指向这个分配的内存, 已经不再是null,此时另外一个线程进来了,由于此时INSTANCE 已经进行了半初始化状态,所以在if (INSTANCE == null)为false,此时另一个线程会拿到这个INSTANCE中的成员变量进行操作, 这样显然是不满足要求的。

想要解析这个问题, 需要查看其字节码文件

例如下面这个测试类T, 使用idea插件查看其字节码文件

在这里插入图片描述

在0 new #2 <com/cyc/jvm/c0_basic/T>之后,已经申请过内存。

4 invokespecial #3 <com/cyc/jvm/c0_basic/T.> 这个给类中的静态变量赋初始值

在调用完4之后,才会把这块内存赋值给t,但是由于指令可能会重排的原因, 如果先执行的是7 astore_1, 相当于先把这个地址扔到内存中, 然后在进行的T初始化, 这种情况下,在双重检查懒汉式单例中,就会出现有别的线程读取到半初始化的单例。

相关问题

如何打破双亲委派机制

如果只是重写findClass方法, 是无法打破双亲委派机制的, 示例如下

package com.cyc.jvm.c2_classloader;

public class T011_ClassReloading1 {
    
    public static void main(String[] args) throws Exception {
    
        T006_CYCClassLoader cycClassLoader = new T006_CYCClassLoader();
        Class clazz = cycClassLoader.loadClass("com.cyc.jvm.Hello");

        cycClassLoader = null;
        System.out.println(clazz.hashCode());

        cycClassLoader = null;

        cycClassLoader = new T006_CYCClassLoader();
        Class clazz1 = cycClassLoader.loadClass("com.cyc.jvm.Hello");
        System.out.println(clazz1.hashCode());

        System.out.println(clazz == clazz1);
    }
}

输出结果

在这里插入图片描述

可以看到两者class的hashcode值相同, 所以, 依然是同一个class对象。

显然这里需要重写loadclass方法才行

package com.cyc.jvm.c2_classloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class T012_ClassReloading2 {
    
    private static class MyLoader extends ClassLoader {
    
        //重写loadClass方法, 每次都去加载新的class , 而不是去类加载器缓存池中去找该类是否已加载
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
    

            File f = new File("D:/test/", name.replace(".", "/").concat(".class"));

            if(!f.exists()) return super.loadClass(name);

            try {
    

                InputStream is = new FileInputStream(f);

                byte[] b = new byte[is.available()];
                is.read(b);
                return defineClass(name, b, 0, b.length);
            } catch (IOException e) {
    
                e.printStackTrace();
            }

            return super.loadClass(name);
        }
    }

    public static void main(String[] args) throws Exception {
    
        MyLoader m = new MyLoader();
        Class clazz = m.loadClass("com.cyc.jvm.Hello");

        m = new MyLoader();
        Class clazzNew = m.loadClass("com.cyc.jvm.Hello");

        System.out.println(clazz == clazzNew);
    }
}

查看输出结果

在这里插入图片描述


第一次被加载的类, 在类空间里,当它的classLoader被干掉之后, 由于没有任何引用指向它了, 所以会被gc回收。

  • bootstrap加载器为什么返回的是null?

    因为它是由c++编写的,java中并没有与之对应的class

  • class的加载过程用到了哪些设计模式?

    classloader的load过程用到了设计模式中的模板方法模式,因为所有方法都已经写好了,如果要自定义classloader,自己只需要重写findclass方法就可以了。

Tomcat为什么要重写类加载器?

无法实现隔离性:如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。一个web容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离的。部署在同一个web容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进JVM, web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离

无法实现热替换:jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。

打破双亲委派机制(参照JVM中的内容)OSGI是基于Java语言的动态模块化规范,类加载器之间是网状结构,更加灵活,但是也更复杂,JNDI服务,使用线程上线文类加载器,父类加载器去使用子类加载器

在这里插入图片描述

  1. tomcat自己定义的类加载器:

    CommonClassLoader:tomcat最基本的类加载器,加载路径中的class可以被tomcat和各个webapp访问

    CatalinaClassLoader:tomcat私有的类加载器,webapp不能访问其加载路径下的class,即对webapp不可见

    SharedClassLoader:各个webapp共享的类加载器,对tomcat不可见

    WebappClassLoader:webapp私有的类加载器,只对当前webapp可见

  2. 每一个web应用程序对应一个WebappClassLoader,每一个jsp文件对应一个JspClassLoader,所以这两个类加载器有多个实例

  3. 工作原理:

    a. CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用

    b. CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离

    c. WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离,多个WebAppClassLoader是同级关系

    d. 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能

  4. tomcat目录结构,与上面的类加载器对应

    /common/*

    /server/*

    /shared/*

    /WEB-INF/*

  5. 默认情况下,conf目录下的catalina.properties文件,没有指定server.loader以及shared.loader,所以tomcat没有建立CatalinaClassLoader和SharedClassLoader的实例,这两个都会使用CommonClassLoader来代替。Tomcat6之后,把common、shared、server目录合成了一个lib目录。所以在我们的服务器里看不到common、shared、server目录。

总结

  1. 加载过程
    1. Loading

      1. 双亲委派,主要出于安全来考虑

      2. LazyLoading 五种情况

        1. –new getstatic putstatic invokestatic指令,访问final变量除外

          –java.lang.reflect对类进行反射调用时

          –初始化子类的时候,父类首先初始化

          –虚拟机启动时,被执行的主类必须初始化

          –动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化

      3. ClassLoader的源码

        1. findInCache -> parent.loadClass -> findClass()
      4. 自定义类加载器

        1. extends ClassLoader
        2. overwrite findClass() -> defineClass(byte[] -> Class clazz)
        3. 加密
        4. 如何打破双亲委派
          1. 用super(parent)指定
          2. 双亲委派的打破
            1. 如何打破:重写loadClass()
            2. 何时打破过?
              1. JDK1.2之前,自定义ClassLoader都必须重写loadClass()

              2. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定

              3. 热启动,热部署(会把整个classLoader都干掉,把class重新load一遍)

                1. osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)

                  每一个webApplication,都有自己的一个classLoader, 每个classLoader中可以有自己的类。

      5. 混合执行 编译执行 解释执行

        1. 检测热点代码:-XX:CompileThreshold = 10000
    2. Linking

      1. Verification
        1. 验证文件是否符合JVM规定
      2. Preparation
        1. 静态成员变量赋默认值
      3. Resolution
        1. 将类、方法、属性等符号引用解析为直接引用
          常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
    3. Initializing

      1. 调用类初始化代码 ,给静态成员变量赋初始值
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/A_Java_Dog/article/details/117676139

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文