Java零碎参数

零碎级全局变量,该参数能够在程序中任何地位都能够拜访到。优先级最高,笼罩程序中同名配置。
个别通过设置虚拟机参数来实现。
例如在idea中能够通过对应用程序进行配置:

Edit Configurations -> Add VM Options

零碎参数的规范格局为:-Dargname=agrvalue

多个参数之间用空格隔开,如果参数值两头有空格,则用引号括起来。
虚拟机零碎参数中设置的参数键值对,在程序中能够用 System.getProperty("propertyName") 获取对应参数值。

-X/-XX 为非标准零碎参数模式,个别与 JVM 虚拟机设置无关,参数名和值都由 JVM 标准规定。例如:-Xms :初始堆大小、-Xmx :最大堆大小。

Java运行参数

main 办法执行时传入的参数值,如果参数有多个,用空格离开。
main 办法的个别格局为:public static void main(String[] args),其中,String[] args 就是存储运行参数的变量,在程序中能够间接应用。
Java运行参数依然能够通过在Edit Configurations中设置

相似用法:

命令行中设置零碎参数或运行参数

当然也能够在命令行中对这两种参数进行设置
java 命令的根本格局为 java [-options] class [args...],其中:

  • [-options] 配置 Java 零碎参数
  • [args…] 配置 Java 运行参数

示例:java -Dfile.encoding=UTF-8 -Dmy=user Hi this is leo channel




字符串常量池(属于运行时常量池,只存储字符串)

参考博客(https://juejin.cn/post/693823...)(https://juejin.cn/post/685457...)

在 jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率降落很快。在jdk7中,StringTable的长度能够通过一个参数指定:

  • -XX:StringTableSize=99991

1.字符串常量池在Java内存区域的哪个地位

  • JDK7之前版本字符串常量池是放在办法区中的,不过自从JDK7之后,Hotspot虚拟机便将本来放在永恒代的字符串常量池移至堆中。

2.字符串常量池是什么

  • 在HotSpot里实现的string pool性能的是一个固定大小的HashTable,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上,StringTable外面存的是key(字面量“abc”, 即驻留字符串)-value(字符串"abc"实例对象在堆中的援用)键值对。
  • 在JDK6.0中,StringTable的长度是固定的,长度就是1009,因而如果放入String Pool中的String十分多,就会造成hash抵触,导致链表过长,当调用String#intern()时会须要到链表上一个一个找,从而导致性能大幅度降落;
  • 在JDK7.0中,StringTable的长度能够通过参数指定:
  • -XX:StringTableSize=66666

3.字符串常量池里放的是什么

  • 在JDK6.0及之前版本中,String Pool里放的都是字符串常量;
  • 在JDK7.0中,因为String#intern()产生了扭转,因而String Pool中也能够寄存放于堆内的字符串对象的援用地址。
  • 须要阐明的是:字符串常量池中的字符串只存在一份!
    String s1 = "hello,world!";    String s2 = "hello,world!";    //即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是间接把已存在的字符串内存地址返回给s2。

4.String a= "ha" 与 String a = new String("ha")区别

  • 间接定义的"ha"是贮存在字符串常量池中;new String(“ha”)是String存储在堆中,“ha”在字符串常量池中;
  • 常量池中雷同的字符串只会有一个,然而new String(“ha”)每new一个对象就会在堆中新建一个对象,不论这个值是否雷同;
  • String a = “ha”与String b = “ha”:a b都指向字符串常量池中的“ha”,所以 a==b ;
  • String a = new String(“ha”)与String b = new String(“ha”):是会在堆中创立两个对象,这两个对象的值都为ha,所以a!=b;a.equals(b)返回true;
  • String a =“ha”在编译阶段就会在内存中创立,String a = new String(“ha”)是在运行时才会在堆中创建对象。

5.字符串拼接状况

    应用 ” ” 双引号创立 : String s1 = “first”;    应用字符串连接符拼接 : String s2=”se”+”cond”;    应用字符串加援用拼接 : String s12=”first”+s2;    应用new String(“”)创立 : String s3 = new String(“three”);    应用new String(“”)拼接 : String s4 = new String(“fo”)+”ur”;    应用new String(“”)拼接 : String s5 = new String(“fo”)+new String(“ur”);    s1 : 中的”first” 是字符串常量,在编译期就被确定了,先查看字符串常量池中是否含有”first”字符串,若没有则增加”first”到字符串常量池中,并且间接指向它。所以s1间接指向字符串常量池的”first”对象。    s2:“se”和”cond”也都是字符串常量,当一个字符串由多个字符串常量连贯而成时,它本人必定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,并且s2是常量池中”second”的一个援用。    s12 : JVM对于字符串援用,因为在字符串的”+”连贯中,有字符串援用存在,而援用的值在程序编译期是无奈确定的,即("first"+s2)无奈被编译器优化,只有在程序运行期来动态分配应用StringBuilder连贯后的新String对象赋给s12。    (编译器创立一个StringBuilder对象,并调用append()办法,最初调用toString()创立新String对象,以蕴含批改后的字符串内容)    s3 : 用new String() 创立的字符串不是常量,不能在编译期就确定,所以new String() 创立的字符串不放入常量池中,它们有本人的地址空间。然而”three”字符串常量在编译期也会被退出到字符串常量池(如果不存在的话)    s4 : 同样不能在编译期确定,然而”fo”和”ur”这两个字符串常量也会增加到字符串常量池中,并且在堆中创立String对象。(字符串常量池并不会寄存”four”这个字符串,会寄存“fo”这个字符串)

6.final援用拼接

    public class StringConcat {        final String a = "hello";        final String b = "moto";        String result = a + b + "2018";    }    • 对于final润饰的局部变量,它在编译时被解析为常量值的一个本地拷贝存储到本人的常量池中或嵌入到它的字节码流中。    • 所以此时的(a + b + “2018”)和(“hello” + “moto” + “2018”)成果是一样的。



课外话对于intern()函数的额定知识点:

援用
对于为什么增加了一个append()函数之后后果不同的问题,参考博客(https://blog.csdn.net/qq_4491...)的解答

7.intern()办法

参考博客(https://tech.meituan.com/2014...)
重点!!!(https://blog.csdn.net/qq_4491...)

  • String.intern( )是一个Native办法,它的作用是:如果字符串常量池曾经蕴含一个等于此String对象的字符串,则返回代表字符串常量池中这个字符串的String对象。【并不是堆中的字符串对象,而是字符串常量池中的字符串对象】;否则将此String对象的援用地址(堆中)增加到字符串常量池中,因为字符串常量池在堆中,所以没必要在字符串常量池中再多创立一份String对象。
  • jdk 1.7 后的字符串常量池存在于堆中。
  • jdk6中的常量池是放在 Perm(永恒代) 区中的,Perm 区和失常的 JAVA Heap 区域是齐全离开的。如果是应用引号申明的字符串都是会间接在字符串常量池中生成,而 new 进去的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比拟必定是不雷同的,即便调用String.intern办法也是没有任何关系的。
  • 再说说 jdk7 中的状况。这里要明确一点的是,在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的 Perm 区的,Perm 区是一个类动态的区域,次要存储一些加载类的信息,常量池,办法片段等内容,默认大小只有4m,一旦常量池中大量应用 intern 是会间接产生java.lang.OutOfMemoryError: PermGen space谬误的。 所以在 jdk7 的版本中,字符串常量池曾经从 Perm 区移到失常的 Java Heap 区域了。为什么要挪动,Perm 区域太小是一个次要起因,当然据音讯称 jdk8 曾经间接勾销了 Perm 区域,而新建设了一个元区域。应该是 jdk 开发者认为 Perm 区域曾经不适宜当初 JAVA 的倒退了。
  • 在intern()源码的注解上有说过一段话:
    All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java™ Language Specification.翻译如下
    所有的文字字符串 ( 这里指的应该是用 双引号 "" 包裹起来的字符串,比方new String("a") 中的 “a”), 以及字符形常量 (集体了解, 这里指的是 String a = "abc" 中的 a. ) 都会具备 intern() 调用后的成果. 也就是, 前述两种字符串实例会进行判断、退出到字符串常量池中,也能够了解为前述两种定义主动调用 intern() 办法
    从这一段的正文咱们能够推断出:
    ""包裹的字符串都会进入字符串常量池. 也就是说, String a = new String("string") 这一行代码, 会让 "string"自身作为一个字符串对象进入字符串常量池、而不是 a 实例进入字符串常量池.
    此外, 从 intern() 办法的正文咱们能够晓得, 在执行 intern() 办法时会产生以下事件:
    测验指标字符串对象的具体值在字符串常量池中是否存在, 不存在则则将指标对象的援用放入字符串常量池, 并返回指标字符串的援用; 否则返回字符串常量池里与指标字符串对象内容雷同的对象援用
  • 举个例子阐明,例如String s = new String("1") + new String("1"),这时候产生了两个对象,一个是s指向的对象在堆上(该对象为“11”),此时在常量池中还生成了”1“对象,所以为两个对象。在s调用intern()函数后,会把s援用的对象”11“地址返回给字符串常量池(jdk7当前常量池既能够寄存对象也能够寄存援用地址),这时候新建的一个变量s2,使得s2 = “11”,s2检测到常量池中有"11"的援用地址,就将寄存在常量池中s的援用地址返回给s2,所以s == s2,因为等号比拟的是援用是否相等(地址)(by the way,equals是调用对象的equals办法进行判断。在String类中,equals判断的是String外部用于存储字符串信息的char数组的每个元素是否齐全相等),所以s等于s1,即两者地址雷同,而这种状况,在jdk6之前则天壤之别,应用intern()函数之后,jdk6中是在字符串常量池中生成“11”这个字面量,而不是去存储放在堆中的”11“变量。
  • 应用intern()比不应用intern()耗费的内存更少
  • https://blog.csdn.net/vivianX... 该链接解释了为什么应用intern()函数会节俭内存

    String aa=new String("abcdef");//"abcdef"字符串对象,创立在字符串常量池String aaIntern=aa.intern();//aaIntern为字符串常量池的"abcdef"对象System.out.println("aa==aaIntern    "+(aa==aaIntern));//false   String aaStr="abcdef";System.out.println("aaIntern==aaStr "+(aaIntern==aaStr));//trueSystem.out.println("aa==aaStr   "+(aa==aaStr));//false  aa在堆,aaStr在字符串常量池           ——————————————  ——————————————  ——————————————  ——————————String bb = new String("123") + "456";String bbIntern = bb.intern();System.out.println("bb==bbIntern    " + (bb == bbIntern));// true,字符串常量池没有"123456"String bbStr = "123456";System.out.println("bb==bbStr   " + (bb == bbStr));// trueSystem.out.println("bbIntern==bbStr " + (bbIntern == bbStr));// trueString cc = new String("1") + "23";String ccIntern = cc.intern();// 字符串常量池曾经有"123"System.out.println("cc==ccIntern    " + (cc == ccIntern));// false



对于主动装箱和主动拆箱

先说整型常量池

java中为了进步程序的执行效率,将[-128, 127]之间256个整数所有的包装对象提前创立好了,类加载时就曾经创好了,放在了一个办法区的“整数常量池”当中。

目标是:如果一个整数范畴在[-128, 127]外面的整数进行包装,包装时不须要再new对象了,间接从“整数常量池”中取出来。

池:就是缓存区的意思。缓存区的益处是:程序用起来执行很快,很不便。毛病是:如果没用到,就有点消耗了内存。

举例说,像Integer包装类,Integer类源码中有定义IntegerCache子类,该类用来在编译期间向常量池中增加Integer常量????
如果两个援用指向同一个对象,用==示意它们是相等的。如果两个援用指向不同的对象,用==示意它们是不相等的,即便它们的内容雷同。即==判断的是两个对象的地址是否雷同,===判断的是两个对象内的值是否相等。

Integer bInteger=127;

它实际上在外部的操作是:

Integer bInteger=Integer.valueOf(127);

valueOf(int i)函数的源码:

public static Integer valueOf(int i) {        assert IntegerCache.high >= 127;        if (i >= IntegerCache.low && i <= IntegerCache.high)            return IntegerCache.cache[i + (-IntegerCache.low)];        return new Integer(i);    }

能够看到,如果值的范畴在-128到127之间,它就从高速缓存返回实例。
所以…上面这两个指向同一个对象:

    Integer aInteger=127;    Integer bInteger=127;

咱们能够失去true。
另外,一个容易记错的点是

Integer i1 = new Integer(1)的时候是在Java堆中创立一个Integer对象,i1指向堆中的对象,i1与常量池没关系,而Integer i2 = 1, 所以i1==i2为false。
Integer e1 = 127;Integer e2 = new Integer(127);System.out.println(e1 == e2);  // false

private static class IntegerCache {        static final int low = -128;        static final int high;        static final Integer[] cache;        static Integer[] archivedCache;        static {            // high value may be configured by property  常量池中的最大值            int h = 127;            String integerCacheHighPropValue =                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");            if (integerCacheHighPropValue != null) {                try {                    h = Math.max(parseInt(integerCacheHighPropValue), 127);                    // Maximum array size is Integer.MAX_VALUE                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);                } catch( NumberFormatException nfe) {                    // If the property cannot be parsed into an int, ignore it.                }            }            high = h;            // Load IntegerCache.archivedCache from archive, if possible            CDS.initializeFromArchive(IntegerCache.class);            int size = (high - low) + 1;            // Use the archived cache if it exists and is large enough            if (archivedCache == null || size > archivedCache.length) {                Integer[] c = new Integer[size];                int j = low;                for(int i = 0; i < c.length; i++) {                    c[i] = new Integer(j++);                }                archivedCache = c;            }            cache = archivedCache;            // range [-128, 127] must be interned (JLS7 5.1.7)            assert IntegerCache.high >= 127;        }        private IntegerCache() {}    }

在Java7之后,能够通过设置零碎参数来扩充Integer常量池的最大值,即更改 -Djava.lang.Integer.IntegerCache.high=<size>来批改

这是没被动去批改java.lang.Integer.IntegerCache.high的后果:

这是被动批改java.lang.Integer.IntegerCache.high的后果:




对于String的不可变性

参考至掘金博客:https://juejin.cn/post/6854573212886368270(对于String类的些许认识以及对StringTable(字符串常量池)的性能调优)

 String s = "hello";

这时候,"hello"字面量就被寄存在StringTable(即字符串常量池)中,而变量s是一个援用,s指向了StringTable中的"hello"

当把s的值改为"hello world"

String s = "hello";s = "hello world";

如何去验证是指向了一个新的字符串而不是批改其内容呢,咱们能够打印一下hash值看看。

String s = "hello";System.out.println(System.identityHashCode(s));s = "hello world";System.out.println(s.hashCode());s = "hello";System.out.println(System.identityHashCode(s));

能够看到,第一次和第三次的hash值一样,第二次hash值和其它两次不同,阐明的确是指向了一个新的对象而不是批改了String的值。

那么String是怎么实现不可变的呢?咱们来看一下String类的源码:

从源码中咱们能够看出,首先String类是final的,阐明其不可被继承,就不会被子类扭转其不可变的个性;其次,String的底层其实是一个被final润饰的数组,阐明这个value在确定值后就不能指向一个新的数组。这里咱们要明确一点,被final润饰的数组尽管不能指向一个新的数组,但却是能够批改数组的值的:

既然能够被批改,那String怎么是不可变的呢?因为String类并没有提供任何一个办法去批改数组的值,所以String的不可变性是因为其底层的实现,而不是一个final。
那么String为什么要设计成不可变的呢?我感觉是因为出于安全性的考量,试想一下,在一个程序中,有多个中央同时援用了一个雷同的String对象,然而你可能只是想在一个中央批改String的内容,要是String是可变的,导致了所有的String的内容都扭转了,万一这是在一个重要场景下,比方传输明码什么的,不就出大问题了吗。所以String就被设计成了不可变的。

字符串的拼接

看上面一段程序

public static void main(String[] args) {    String a = "hello";    String b = " world!";    String c = a+b;}

咱们来看一下这段代码对应的字节码指令:

重点看一下用红色标注的几行代码,看不懂后面的字节码指令没关系,能够看前面的正文。能够看到,字符串拼接其实就是调用StringBuilder的append()办法,而后调用了toString()办法返回一个新的字符串。




对于类加载时动态代码块、静态方法、动态变量、一般代码块、构造方法的执行程序

首先定义父类:

public class Father {    static int age = 10;    static {        System.out.println("Father动态代码块");    }    {        System.out.println("Father一般代码块");    }    public static void leo(){        System.out.println("Father静态方法");    }    public Father(){        System.out.println("Father无参构造函数");    }}

子类继承父类:

public class Son extends Father{    static {        System.out.println("Son动态代码块");    }    {        System.out.println("Son一般代码块");    }    public Son(){        System.out.println("Son无参构造函数");    }    public static void main(String[] args) {        Father.leo();        new Son();        System.out.println(Father.age);    }}

运行后果如下:

能够晓得,首先当类加载的时候,会主动加载动态代码块动态代码块静态方法都是和类一起加载的,并且动态变量静态方法是依据调用程序来执行的。

当咱们运行Son类的时候,会产生什么呢?
首先当虚拟机加载Son类时,它会主动加载动态代码块,因为Demo继承了Father类,它会先去找Father类当中有没有动态代码块,如果有,则按程序运行父类中的动态代码块。如果执行完或没有,则运行以后类的静态方法。故会顺次输入 Father动态代码块–>Son动态动态代码块->Father静态方法。而后执行new Son(); 在执行子类的实例化之前,虚拟机又会去父类找一般代码块,如果有则执行,没有则会去调用父类的无参构造方法。随后去调用子类的一般代码块,而后才去执行子类的构造方法。所以会顺次输入:Father一般代码块–>Father无参构造方法–>Son一般代码块–>Son无参构造函数。随后执行System.out.println(“Father.age”);语句,输入10。

留神点:


因为遵行先申明先执行的准则,所以在打印语句的时候,因为还没申明age属性,所以会报“非法向前利用”谬误。

留神:(动态)变量和(动态)代码块的也是有执行程序的,与代码书写的程序统一。在(动态)代码块中能够应用(动态)变量,然而被应用的(动态)变量必须在(动态)代码块后面申明。

即存在以下执行步骤:

1、父类动态变量和动态代码块(先申明的先执行);

2、子类动态变量和动态代码块(先申明的先执行);

3、父类的变量和代码块(先申明的先执行);

4、父类的构造函数;

5、子类的变量和代码块(先申明的先执行);

6、子类的构造函数。

执行工夫:

1.父类动态代码块 ( java虚拟机加载类时,就会执行该块代码,故只执行一次)

2 .子类动态代码块 ( java虚拟机加载类时,就会执行该块代码,故只执行一次)

3.父类属性对象初始化

4.父类一般代码块(每次new,每次执行 )

5.父类构造函数(每次new,每次执行)

6.子 类 属性对象初始化

7.子类一般代码块(每次new,每次执行 )

8.子 类构造函数(每次new,每次执行)

总结

当加载Demo类时,JVM会去调用该类父类的动态代码块,父类的动态代码块运行完后去执行子类的动态代码块。当子类的动态代码块执行结束后JVM又会去找父类中的一般代码块,当父类的一般代码块执行结束后又会调用父类的构造方法。父类的构造方法执行结束后,JVM又会去看子类中有没有一般代码块,如果有则执行。执行结束后,会去调用子类的构造方法。这就是他们之间的程序
留神:java中所有类都是Object类的子类。当父类中含有有参结构而没有显示的写出无参结构,则子类必须用super关键字显示的调用父类的有参结构,否则编译将不通过。

对于类加载器

根本汇合类的接口继承关系和实现


对于重写equals()办法就得要重写hashcode()办法

blog:https://www.shouxicto.com/art...(略微有点帮忙)
blog:https://blog.csdn.net/javazej...
这个问题次要是针对映射相干的操作(Map接口)。学过数据结构的同学都晓得Map接口的类会应用到键对象的哈希码,当咱们调用put办法或者get办法对Map容器进行操作时,都是依据键对象的哈希码来计算存储地位的,因而如果咱们对哈希码的获取没有相干保障,就可能会得不到预期的后果。在java中,咱们能够应用hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个办法在Object类中申明,因而所有的子类都含有该办法。
————————————————
版权申明:本文为CSDN博主「zejian_」的原创文章,遵循CC 4.0 BY-SA版权协定,转载请附上原文出处链接及本申明。
原文链接:https://blog.csdn.net/javazej...
equals()办法的底层源码调用了“==”

public boolean equals(Object obj) {   return (this == obj);     }

为什么?因为是规定。当重写了equals()办法而没重写hashcode()办法时,equals返回的是TRUE,但hashcode()就不肯定了,所以为了规定,须要重写,不能违反规定。