learning, progress, future.

skydh


  • 首页

  • 归档

java虚拟机运行时数据区域

发表于 2018-05-04

进程和线程

  这里必须先提一下操作系统的进程和线程。

什么是进程?

  在操作系统中每一个独立的程序单元就是一个进程。每个进程都有相互隔离的内存空间(显然,不然一些流氓进程就可以访问网上银行账号密码等之类的敏感数据了),因此进程之间通信极其不容易。

什么是线程?

  线程是进程里面的执行流,每个线程都有自己的程序栈,也有自己TLS,独立的线程私有空间,由于线程是进程里面的一部分,那么线程也可以访问公有内存,线程也有自己私有的空间。因此线程之间的信息交流会非常方便。

线程与进程的关系。

  线程是真正的程序执行流,每个程序由一个主线程和多个子线程一起运行工作完成的,因此线程才是真正的程序运行的东东,而进程相当于一个容器,这个容器给线程提供了相对隔离的内存空间,和一个相对安全的访问条件。

jvm 的运行数据分布

  了解了一下进程和线程后。我们看看虚拟机的内存分布。

程序计数器

  程序计数器:是jvm中一小块内存空间,可以当做当前线程的字节码文件的行号,字节码解释器通过改变这个值来获取下一个指令。Java多线程通过线程轮流切换处理器的执行时间来执行的,大家知道现代处理器的原理,每次一个核上只能一个线程进行运转,由于中断,当线程切换后能恢复到原先正确的位置。因此每个程序计数器都是独立的,这些程序计数器的所在的内存称为线程私有的内存。如果线程执行的是java方法,那么程序计数器指代的是当前字节码指令的地址,如果是native方法,程序计数器的值为空,这处区域是jvm没有定义OutOfMemoryError错误。就是放代码的地方,通过操作这个栈,来执行这个代码。

虚拟机栈

  打个比方,一般吧java内存粗略分为堆栈,这个虚拟机栈中的局部变量表就是传说中的栈,线程私有的,描述java方法执行的内存模型:每个方法在执行的时候都会建立一个这个;用于存储局部变量表,操作数栈,动态链接,方法出口等,每一个方法从调用到执行完,就代表一个栈帧在虚拟机栈中从入栈到出栈的过程,其中局部变量表存放了编译器可知的基本变量(boolean,int,double,float,char),还有引用类型(),renturnAddress类型(指向了一个字节码的地址)局部变量表的大小信息在编译期间就确定好了,运行期间不会改变其大小,该虚拟机栈回报2个错误,第一个就是请求的栈帧大于虚拟机栈的深度回报StackOverflowError错误第二个错误就是虚拟机栈动态扩张,扩张到无法申请到内存时报OutOfMemoryError错误。

本地方法栈

  和虚拟机栈差不多,不过不是java方法是native方法,java规范对native方法的语言和数据格式没有强制要求虚拟机可以自由的去实现它,报的错误也是StackOverflowError错误和OutOfMemoryError错误.

Java堆:

  第一它是所有线程共享的,虚拟机启动创建堆,主要用于存储对象实例和数组,Java垃圾处理器主要处理的就是java堆,因此java堆也被称为gc堆,从内存回收的角度上讲可以划分为新生代和老生代,从内存分配角度讲,可以划分出多个线程私有的内存区域等,java堆有多种划分方式,但是无论怎么划分都是存储对象实例,多重划分是为了更好的划分内存和回收内存,java堆可以位于内存上不连续的空间上,只要逻辑上连续即可,java堆可以实现为固定大小的,也可以实现为可扩展大小的,要是java堆上无法为实例对象分配内存,那么就会报这个OutOfMemoryError.
  堆划分为新生代,老年代,默认比例为1:2,新生代划分为3个区域,eden,From survivor,to survivor,比例为8:1:1。为何呢。参照下面:IBM研究发现,98%的新生态对象生存周期很短的,于是一个新的算法出来了,就是把内存分为3部分,8:1:1,每次使用其中的0.9部分,当发生垃圾回收的时候那就把剩余的对象就放到剩余的0.1那里,当然剩余的0.1可能不够那就可以把一部分对象转化为老年态暂时存到老年态里面,这样子就只浪费0.1的内存了,性价比可以。
  参数设定:新生代和老年代的比例–XX:NewRatio. 新生代中,eden,from,to的参数为–XX:SurvivorRatio。

方法区:

  各个线程共享的区域,主要存储被虚拟机加载的类信息,常量,静态变量,编译器编译后的代码,和java堆一样逻辑上内存,可扩展的实现大小,这个区域的垃圾回收主要针对常量池的回收和类型的卸载,其中类型的卸载要求比较高,但是必要的垃圾回收是必要的,以前低版本的就出现过严重的内存泄漏。

运行期常量池:

  这个区域也是方法区的一部分,编译后的Class文件中的类名啊,方法名啊,常量啊被加载到虚拟机后就会放到方法去的运行常量池来保存,当然常量不止可以只是在编译期间放到常量池中,还可以用string.intern()方法(如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回)来在运行期间放到常量池中,当然要是常量池中无法申请到内存时就会报出OutOfMemoryError这个经典错误。

直接内存

  直接内存是一个重要的问题,首先它不是运行数据区的部分也不是java虚拟机规范的一部分,这个的出现主要和java1.4后出现的NIO相关,一个基于通道和缓冲区的io方式,它可以使用Native函数库来直接分配堆外内存,然后通过一个存在java堆中的DirectByteBuffer这个对象来对这个java堆外的内存的引用来进行操作,可以提高相关性能,因为避免了java堆和native堆中的来回复制数据。
  那个那么重点来了,既然这个直接内存不归java堆管理,但是也会受到总内存的限制,如果管理员设置课动态扩展的javajvm时,如果忽略了这个直接内存的,最后实际内存总和大于实际内存,当jvm要动态扩展内存时,就会出现这个outofmemoryerror错误。

启动参数配置

  -Xms :初始堆大小 物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.

  -Xmx:最大堆大小 物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制.

  -Xmn 年轻代大小(1.4or lator)
注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8.

  -XX:PermSize 设置持久代(perm gen)初始值.

  -Xss 每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右,一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长)

java String 的+操作导致的问题

发表于 2018-04-22

定义

  在java中 null既不是对象,也不是一种类型,而是一种特殊的值。在这里我要说的是一种特殊情况哈。

编译器自带的优化

  总所周知,我们的java代码中String对象那如果直接用+连连接字符串可能导致效率变低。但是现在在java1.5以后编译器对其自动做了优化,如果你有时间打开反编译的源码看看,你会发现,String a=”b”+”cc”;这个代码在编译器中优化了,优化方式为在这行代码的开始的地方声明一个StringBuilder,然后不断append后面的字符串。因此对于java来说你用StringBuilder和String的+其实效率都是一样的,但是为了保证一个良好的习惯还是用StringBuilder好一点。

+导致的问题

  我们用””+null,这个空对象,会生成一个null字符串。这是为什么呢?
  前文已经提到,我们已经用编译器做了优化,String对象的+被优化为了StringBuilder的append,然而我们看看append的源码,你就会发现为何是null字符串了。


         public StringBuilder append(Object obj) {
            return append(String.valueOf(obj));
        }

        @Override
        public StringBuilder append(String str) {
            super.append(str);
            return this;
        }

  查看源码发现,append方法向上调用了其抽象类AbstractStringBuilder方法的append方法,在看

     public AbstractStringBuilder append(String str) {
            if (str == null)
                return appendNull();
            int len = str.length();
            ensureCapacityInternal(count + len);
            str.getChars(0, len, value, count);
            count += len;
            return this;
        }
     private AbstractStringBuilder appendNull() {
            int c = count;
            ensureCapacityInternal(c + 4);
            final char[] value = this.value;
            value[c++] = 'n';
            value[c++] = 'u';
            value[c++] = 'l';
            value[c++] = 'l';
            count = c;
            return this;
        }

  你看真相大白了,在这个抽象类的这个append方法中,先进行了判断,如果为null,则是把这个null作为一个字符串加上去。因此解决了上面我们遇到的问题,为何String对象+null,会条件一个null字符串。   测试案例如下:

    public static void main(String[] args) {
            String a="dh";
            String b=a+null;
            System.out.println(b);

        }
  结果如下:
    dhnull

java属性为什么没多态,而是方法多态

发表于 2018-04-22

定义

  java多肽的特性:方法具有多态性,属性却没有。

准备

  基类:

aaa

  子类:

aaa

  测试类:

aaa.png)

  结果:

aaa

分析如下

父类 a=new 子类,实际对象时子类。由于向上转型,我们可以用父类在编译期间代替子类,使得编译不报错,当然你调用的方法必须是父类所拥有的,不然编译监察报错,

其实new 子类(),那么实际类型就是子类,运行期间就是子类的方法和属性啊,而一个父类有多个子类,那么就造成多态的生成和原理,那么问题来了,为什么

我们的属性不具有多态特性。我们直接调用属性值,那么出来的就是父类的属性值,为什么呢?

这个就是静态绑定和动态绑定的问题了

编译期间的绑定就是静态绑定,运行期间的绑定就是动态绑定,java为了实现多态的这个机制,选择让方法在运行期间绑定对应对象所对应实际类型,选择让属性在编译期间绑定其所对应实际类型。那么这个问题不就解决了?

编译期间时,肯定是父类的类型,如果直接调用属性,故名思议则是父类所对应的属性值。而方法则是在运行期间绑定的,这个对象实际上实际是子类对象,那么运行期间就肯定是子类类型,故方法是子类的方法,而在方法中调用的值是子类的值就更简单了,我们调用子类的值时,实际上简写了this.属性,而this却是指当前对象。当前对象只有被实例化才会有对象,那么肯定是运行期间,故在方法里面调用属性值是子类的值。

io缓冲为何可以提高效率

发表于 2018-04-22

问题

据我了解,运用FileInputStream读写一段数据是一个字节一个字节的读取,如果有10个字节大小的文件,就要调用10次系统调用,每次将读取的数据赋值给变量,然后程序使用变量。
缓冲区可以看作是一个放在内存中的数组,但是从硬盘中读取数据仍然要使用系统调用,系统调用的读取仍然是每次一个,只是每调用一次之后,将所得到的数据放入缓冲区中的,
然后程序一次性使用10个数据。
我是这样理解的,但是不管用与不用缓冲区,使用的系统调用是一样多的。(不知我的理解正确与否,请指出)如果我理解的是正确的,那么为什么使用缓冲区读写的效率要高呢??谢谢!

回答

理解是对的。
调用I\O操作的时候,实际上还是一个一个的读或者写,关键就在,CPU只有一个,不论是几个核心。CPU在系统调用时,会不会还要参与主要操作?参与多次就会花更多的时间。 系统调用时,
若不用缓冲,CPU会酌情考虑使用 中断。此时CPU是主动地,每个周期中都要花去一部分去询问I\O设备是否读完数据,这段时间CPU不能做任何其他的事情(至少负责执行这段模块的核不能)。所以,调用一次读了一个字,通报一次,CPU腾出时间处理一次。而设置缓冲,CPU通常会使用 DMA 方式去执行 I\O 操作。CPU 将这个工作交给DMA控制器来做,自己腾出时间做其他的事,当DMA完成工作时,DMA会主动告诉CPU“操作完成”。
这时,CPU接管后续工作。在此,CPU 是被动的。DMA是专门 做 I\O 与 内存 数据交换的,不仅自身效率高,也节约了CPU时间,CPU在DMA开始和结束时做了一些设置罢了。
所以,调用一次,不必通报CPU,等缓冲区满了,DMA 会对C PU 说 “嘿,伙计!快过来看看,把他们都搬走吧”。综上,设置缓冲,就建立了数据块,使得DMA执行更方便,CPU也有空闲,而不是呆呆地候着I\O数据读来。从微观角度来说,设置缓冲效率要高很多。尽管,不能从这个程序上看出来。 几万字的读写\就能看到差距。

转载自 http://blog.csdn.net/vic1990/article/details/46236837

lombok 配置使用以及优势

发表于 2018-04-22

maven依赖

  

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.20</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

让你的IDE不报错

  更新maven仓库,把lombok的文件拉下来,cmd进入那个目录,然后java -jar 对应jar包,让其运行安装在你的IDE里面。
  在你的eclipse安装目录下的eclipse.ini文件中加入
  -javaagent:lombok.jar
  -Xbootclasspath/a:lombok.jar
  重启eclipse,lombok即可生效

为什么使用?

  1, 使得代码整洁许多,大部分entity的get和set方法都是固定不变的,我们使用lombok可以有效的减少源代码的量,只关注最重要的属性。

  2,曾经遇到一个变态的entity,足足1800行代码,使用lombok后就只用400多行了,这个entity的可读性多了很多

  3,有些代码没有把属性和方法分隔,可能是遗留代码,也可能是忘了修改,也可能水品和我一样比较差的,导致代码可读性变得弱了好多,如果我们使用了lombok就不会出现这种情况

  4,还有就是有些情况,有人修改了属性,但是没有修改方法,而且没有报错的那种,等等一系列情况,使用了lombok就可以避免。

  5,还有就是我们的代码自动生成工具生成的vo也是不够工整,可能是由于某些原因导致的,如果我们使用了这个lombok就可以避免这个问题.

原理

就是元注解出现后注解运行生命周期里面的编译周期,这个就是 JSR 269 Pluggable Annotation Processing API,就是源代码在编译成字节码的时候修改了语法树的节点规则进行了加强生成

spring mvc post请求乱码问题

发表于 2018-04-22

如何处理

  在web.xml的拦截过滤器上加

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

  必须放在第一个拦截器上。

数据规范

  当前很多浏览器并不发送带Content-Type头部的字符编码标识符,它会把字符编码的决定留在读取HTTP请求的时候。如果客户端没有指明编码,容器用来创建请求读和解析POST数据的默认编码必须是”ISO-8859-1”。然而,为了提示开发者客户端没有成功发送一个字符编码,容器中getCharacterEncoding方法会返回null。
  如果客户端没有设置字符编码,并且请求数据使用了不同编码而不是上述的默认编码,程序将会出现中断。

为何放在第一个过滤器上

  为了纠正这种状态。一个新的方法setCharacterEncoding(String enc) 被添加到ServletRequest接口。开发者调用这个方法能重写容器提供的字符编码。这个方法必须在解析request中任何post数据或者读任何输入之前调用。一旦数据已经被读取,调用这个方法不会影响它的编码。

缓存穿透和缓存雪崩

发表于 2018-04-21

缓存穿透

  缓存穿透是查询一个不存在的数据,由于不存在,所以缓存里面没有,那么缓存查不到,就走查询数据库,由于数据库里面也不存在,那么也不写入缓存,导致这个不存在的数据每次都要到数据库中查询,导致缓存穿透。
  方案:简单利落的方式:就是把这个查询返回的空数据缓存下来,过期时间很短就好了。

缓存雪崩

  就是缓存就是集中在一段时间内全部失效,发生了大量的缓存穿透,导致大量的查询走到了数据库,严重时可能导致数据库宕机,从而引发连锁反应,导致整个系统崩溃了。(可以采用熔断模式来熔断。)
  方案:在缓存失效后通过加锁来控制线程数量。或者设置一个算法,让这个key的过期时间尽可能均匀。

缓存预热

  就是在系统上线后,将相关数据直接加载到缓存系统中去,这样可以避免,当用户请求时再去加载相关数据。

单例模式

发表于 2018-04-03

单例模式

  单例模式是24种单例模式中最为基本简单的设计模式。单例模式是为了这个类只提供一个对外的实例。单例模式的基本思想是,构造函数私有化,外部无法直接创建一个实例,只能通过该类的静态方法来获取对象实例。

饿汉模式

  饿汉模式就是说这个实例在类加载初始化的时候就加载好了,因为特别急,所以戏称饿汉模式。


public class Singleton{
private Singleton(){}
private static Singleton single=new Singleton();
public static Singleton getInstance()
{
return single
}
}

懒汉模式

  懒汉模式,顾名思义,就是说我懒得在没人用就创建,等到有人要用的时候就第一次创建,后面的调用方就直接用第一次创建的对象。所以戏称为懒汉模式。


public class Singleton{
private Singleton(){}
public static Singleton single=null;
public static Singleton getInstance(){
if(singleton==null)
{
single=new Singleton();
}
return single;
}
}

  但是有问题,在多线程并发的情况下,这个单例类还没有创建第一个实例时,多个线程同时进入到了if(singleton==null)这个条件里面,就会创建多个实例。考虑到线程安全,可以在这个getInstance这个方法里面这个非null判断加个类锁(静态方法属于这个类本身,所以加类锁)。


public class Singleton{
private Singleton(){}
public static Singleton single=null;
public static Singleton getInstance(){
synchronized(Singleton3.class){
if(singleton==null)
{
single=new Singleton();
}
}
return single;
}
}

  但是每一次创建类的时候都要加锁,对系统性能有很大的影响。


public class Singleton{
private Singleton(){}
private static Singleton single=null;
public static Singleton getInstance(){
if(single==null)
{
synchronized(Singleton.class)
{
if(single==null){
single=new Singleton()}
}
}
return single;
}
}

  这里2重锁非null判断,我们这里的话我们只是第一次创建对象的时候加了锁,而后面的获取对象就不用加锁了。恩,暂时没有问题了。

  为什么要2个非空判断呢?
  A,B2个线程获取实例,都经过了第一个非空判断,A先获取到了类锁,进行初始化,B进入锁等待。A初始化后释放锁,B获取到锁,如果不再进行一次非空判断,就会再次创建一个对象。

  为什么必须要volatile关键字呢?
  显然这样还不行,这里涉及到指令重排序问题。在上述的双重锁机制中,有的线程可能访问到没有被初始化的资源。
  这是因为 single=new Singleton()这段代码不是原子的,它可以分解为3个操作。

memory = allocate();   //1:分配对象的内存空间
instance = memory;     //3:设置instance指向刚分配的内存地址
                       //注意,此时对象还没有被初始化!
ctorInstance(memory);  //2:初始化对象

  根据java语言规范,在单线程中2和3操作的顺序是不影响程序执行效果的,那么这个顺序就是可以打乱的了,但是在多线程中这个可能会有影响,当a线程第一次访问时,获取到了类锁,进行对象创建,而马上第二个线程b,也访问到了,开始访问第一个非null判断,而a线程创建对象时,到了第3步,没有进行初始化,那么b线程返回的结果可能是一个没有初始化的对象。所以可能出现问题。
  那么怎么解决呢?
  那就是给这个要初始化的对象加volatile关键字,来禁止指令重排序,问题完美解决。

枚举方式实现单例

public enum Singleton{
    INSTANCE;
}

  我们可以通过Singleton.INSTANCE来访问实例。创建枚举默认就是线程安全的,还能防止反序列化导致重新创建新的对象。不然就要像其他单例模式一样要重写readObject方法。   

java 动态代理

发表于 2018-03-20

简介

  java动态代理是个很有用的东西,很多开源框架都用到了这个。比如aop等。

简单demo


package com.dh.test;
public interface PersonDao {
public void say();
}

  接口,java动态代理必须有个接口。


package com.dh.test;
public class PersonDaoImpl implements PersonDao{
@Override
public void say() {
System.out.println(“time to eat”);
}

}


  接口的实现。


package com.dh.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PersonHandler implements InvocationHandler {
private Object obj;
public PersonHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“before”);
Object result = method.invoke(obj, args);
System.out.println(“after”);
return result;
}
}

  这个是动态要加强的东西。


package com.dh.test;
import java.lang.reflect.Proxy;
public class PersonTest {
public static void main(String args[]) {
PersonDao pDao = new PersonDaoImpl();
PersonHandler handler = new PersonHandler(pDao);
PersonDao proxy = (PersonDao) Proxy.newProxyInstance(pDao.getClass().getClassLoader(),
pDao.getClass().getInterfaces(), handler);
proxy.say();
}
}
}

  运行这个得到结果。


before
time to eat
after

代码解析

  代码先从这个Proxy的newProxyInstance开始解析,注释就写在代码上面


@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{
/*

 *  这个方法校验这个实现了InvocationHandler接口的加强类是否为空
 */
    Objects.requireNonNull(h);
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    /*
     * Look up or generate the designated proxy class.
     * 这是生成class的地方。
     */
    Class<?> cl = getProxyClass0(loader, intfs);
    /*
     * Invoke its constructor with the designated invocation handler.
     * 使用我们实现的InvocationHandler作为参数调用构造方法来获得代理类的实例 
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}


  下面是对生成代理类的代码


private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
  //被代理类的接口不得超过65535个
      if (interfaces.length > 65535) {
          throw new IllegalArgumentException("interface limit exceeded");
      }
      // If the proxy class defined by the given loader implementing
      // the given interfaces exists, this will simply return the cached copy;
      // otherwise, it will create the proxy class via the ProxyClassFactory
      //JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理  
      return proxyClassCache.get(loader, interfaces);
  }


  我们进入 proxyclassCache里面找到其参数为内部类ProxyClassFactory,进去看看


private static final class ProxyClassFactory  
    implements BiFunction<ClassLoader, Class<?>[], Class<?>> {  
    // 所有代理类名字的前缀  
    private static final String proxyClassNamePrefix = "$Proxy";  
    // 用于生成代理类名字的计数器  
    private static final AtomicLong nextUniqueNumber = new AtomicLong();  
    @Override  
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  
        // 省略验证代理接口的代码……  
        String proxyPkg = null;     // 生成的代理类的包名  
        // 对于非公共接口,代理类的包名与接口的相同  
        for (Class<?> intf : interfaces) {  
            int flags = intf.getModifiers();  
            if (!Modifier.isPublic(flags)) {  
                String name = intf.getName();  
                int n = name.lastIndexOf('.');  
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));  
                if (proxyPkg == null) {  
                    proxyPkg = pkg;  
                } else if (!pkg.equals(proxyPkg)) {  
                    throw new IllegalArgumentException(  
                        "non-public interfaces from different packages");  
                }  
            }  
        }  
        // 对于公共接口的包名,默认为com.sun.proxy  
        if (proxyPkg == null) {  
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
        }  
        // 获取计数  
        long num = nextUniqueNumber.getAndIncrement();  
        // 默认情况下,代理类的完全限定名为:com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增  
        String proxyName = proxyPkg + proxyClassNamePrefix + num;  
        // 这里才是真正的生成代理类的字节码的地方  
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
            proxyName, interfaces);  
        try {  
            // 根据二进制字节码返回相应的Class实例  
            return defineClass0(loader, proxyName,  
                                proxyClassFile, 0, proxyClassFile.length);  
        } catch (ClassFormatError e) {  
            throw new IllegalArgumentException(e.toString());  
        }  
    }  
}  


  看看怎么生成字节码


public static byte[] generateProxyClass(final String var0, Class[] var1) {  
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);  
    final byte[] var3 = var2.generateClassFile();  
    // 这里根据参数配置,决定是否把生成的字节码(.class文件)保存到本地磁盘,我们可以通过把相应的class文件保存到本地,再反编译来看看具体的实现,这样更直观  
    if(saveGeneratedFiles) {  
        AccessController.doPrivileged(new PrivilegedAction() {  
            public Void run() {  
                try {  
                    FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");  
                    var1.write(var3);  
                    var1.close();  
                    return null;  
                } catch (IOException var2) {  
                    throw new InternalError("I/O exception saving generated file: " + var2);  
                }  
            }  
        });  
    }  
    return var3;  
}  


  这是反编译的生成代理类


package com.sun.proxy;
import com.mikan.proxy.HelloWorld;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements HelloWorld {
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void sayHello(String paramString) {
try {
this.h.invoke(this, m3, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
static {
try {
m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, new Class[] { Class.forName(“java.lang.Object”) });
m3 = Class.forName(“com.mikan.proxy.HelloWorld”).getMethod(“sayHello”, new Class[] { Class.forName(“java.lang.String”) });
m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”, new Class[0]);
m2 = Class.forName(“java.lang.Object”).getMethod(“toString”, new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}

  恩,大致如此,其实吧就是把源码考了过来,读了一下就这样。
  继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
  提供了一个使用InvocationHandler作为参数的构造方法。
  流程:
  1.Proxy的这个newProxyInstance方法返回生成的代理类。
  2.这个Proxy的这个newProxyInstance方法的getProxyClass0方法返回代理类的class,没有加载,没有初始化。
  3.这个方法通过ProxyClassFactory这个内部类获取生成的class.
  4.上面那个内部类的apply方法调用ProxyGenerator.generateProxyClass生成class文件的Byte数组。然后组合生成代理类的class返回给newProxyInstance然后在通过反射生成对象返回。

mysql联接算法

发表于 2018-03-19

  联接算法是MySQL数据库处理联接的物理策略,目前支持Nested-Loops Join算法,时间复杂度为O(N)有索引,无的话为o(N2).
这个是没有索引的表联接

    For each row r in R do
        For each row s in S do
            If r and s satisfy the join condition
                Then output the tuple 
  然而真实情况mysql没有采用这种算法。是这样的:   把驱动表的要select的数据,丢到join_buffer中内存。再扫描被驱动表,取出一行行数据对比,满足条件的作为结果集返回。和上面的算法比较,比较次数,扫描次数一样,但是是内存,因此效率高很多。显然join_buffer可能不够大,默认是256k,join_buffer_size这个参数确定, 分段处理 ,放满了join_buffer,比较,然后清空join_buffer,再放入驱动表的数据。比较,直到走完。但是分段次数太多,可能导致扫描次数变多。因此,可以将join_buffer这个调大一点。   时间复杂度为o(R*S)   这个是有索引的
    For each row r in R do
        lookup r in S index
            If find s == r
               Then output the tuple 

  对于对于联接的列含有索引的情况,外部表(驱动表,也就是需要全表扫描的表)的每条记录不再需要扫描整张内部表,只需要扫描内部表上的索引即可得到联接的判断结果。
  根据前面描述的Simple Nested-Loops Join算法,优化器在一般情况下总是选择将联接列含有索引的表作为内表。如果两张表R和S在联接列上都有索引,并且索引的高度相同,那么优化器会选择记录数少的表作为外部表,这是因为内部表的扫描次数总是索引的高度,与记录的数量无关。

  如果联接表没有索引时,Simple Nested-Loops Join算法扫描内部表很多次,执行效率会非常差。而Block Nested-Loops Join算法就是针对没有索引的联接情况设计的,其使用Join Buffer(联接缓存)来减少内部循环取表的次数。
  对这个没有太大注意,反正效率不高就是了。最好还是加上索引用第一个最好了。
  对于left join 那么一定是左边的表作为外部表,即使右边表没有索引而自身有索引。
  对于 right join 那么一定是右边的表作为外部表,即使左侧表无索引,而右侧表有索引。

  为了效率:
  1.我们一般左连接,那么希望2表连接的话,小数据量的放到左边。
  2.左连接,左边表字段是否有索引不关心,右边表必须要有索引。
  3.表连接最好加上索引,否则另外一个算法效率很低。
  4.内连接比左右连接方便,是因为内连接可以自身优化左右表的顺序。原则如下:1,若都有索引,默认数据量小的表作为外部表。即时加了索引缩小范围。2,若只有一个表有索引,那么默认没有索引的表作为外部表。3,若都没有索引,那么默认Block Nested-Loops Join算法。
  ps:这里的小表:是指按照各自的条件过滤后,这些判断是要走索引的,不然和全表扫描没区别了。计算参与join的各个字段的总数据量,这个总数据量小的,就是小表。

1…91011…13

skydh

skydh

126 日志
© 2020 skydh
本站访客数:
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.3