文章目录
  1. Java内存泄漏:指无用对象持续占有内存或无用对象的内存得不到及时释放,从而造成空间的浪费称为内存泄露。Java内存泄露的根本原因,指长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有短生命周期对象引用而导致不能被回收。

    1. 静态集合类引起的内存泄露,HashMap, Vector等他们引用的所有短生命周期对象当没用后,如果没有从集合中remove都是内存泄漏
    2. 在hashset集合中,当你自定义你的hashcode()方法后,当集合里面的对象属性(影响hashcode计算的属性)被修改后,hashcode()发生改变,再调用remove()方法时不起作用,不会把对象从hashset中移除
    3. 监听器 通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住删除这些监听器,从而增加了内存泄漏的机会。
    4. 各种连接 数据库连接,网络连接和io连接,除非其显示的调用了其close()方法将其连接关闭,否则是不会自动被GC回收的
    5. 内部类和外部模块等的引用 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列对象没有释放。小心外部模块不经意的引用,如果a模块引用了对象b,public void registerMsg(Object b); 注意是否有对b引用没有去除
    6. 单例模式 单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,内存泄漏。
  2. Volatile 关键字,当我们使用volatile关键字去修饰变量时,所有线程都会直接读取该变量并且不缓存它,意思是不会从寄存器中去读取,这就确保了线程读取到的变量是同内存中是一致的。

  3. 关于方法参数传递的总结:对于Java中的方法参数传递,无论传递的是原生数据类型还是引用类型,统一是传值(pass by value),java中没有传引用的概念
  4. 如果在定义一个类的时候,没有为类声明构造方法,那么Java编译器会自动为类添加一个没有参数且方法体为空的构造方法(默认的构造方法),如果在定义一个类的时候,为类声明了构造方法,那么Java编译器就不会再为类添加空构造方法了。
  5. 方法重载(Overload)。表示两个或多个方法名字相同,但方法参数不同。方法参数不同有两层含义:1)参数个数不同。2)参数类型不同。 注意:方法的返回值对重载没有任何影响。
  6. 当生成子类对象时,Java默认首先调用父类的不带参数的构造方法,然后执行该构造方法,生成父类的对象。接下来,再去调用子类的构造方法,生成子类的对象。【要想生成子类的对象,首先需要生成父类的对象,没有父类对象就没有子类对象。比如说:没有父亲,就没有孩子】。
  7. Parent p = new Child();当使用多态方式调用方法p.sing()时,首先检查父类中是否有sing()方法,如果没有则编译错误;如果有,再去调用子类的sing()方法。
  8. 一个类实现了某个接口,那么该类必须要实现接口中声明的所有方法。如果该类是个抽象类,那么就无需实现接口中的方法了。
  9. static修饰方法:static修饰的方法叫做静态方法。对于静态方法来说,可以使用类名.方法名的方式来访问,静态方法只能继承,不能重写(Override)。
  10. 当一个方法被final所修饰时,表示该方法是一个终态方法,即不能被重写(Override)。当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生变化(比如说不能从10变为20);如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。
  11. static代码块:静态代码块。静态代码块的作用也是完成一些初始化工作。首先执行静态代码块,然后执行构造方法。静态代码块在类被加载的时候执行,而构造方法是在生成对象的时候执行;要想调用某个类来生成对象,首先需要将类加载到Java虚拟机上(JVM),然后由JVM加载这个类来生成对象。类的静态代码块只会执行一次,是在类被加载的时候执行的,因为每个类只会被加载一次,所以静态代码块也只会被执行一次;而构造方法则不然,每次生成一个对象的时候都会调用类的构造方法,所以new一次就会调用构造方法一次。
    如果继承体系中既有构造方法,又有静态代码块,那么首先执行最顶层的类的静态代码块,一直执行到最底层类的静态代码块,然后再去执行最顶层类的构造方法,一直执行到最底层类的构造方法。注意:静态代码块只会执行一次。
  12. import aa.bb.*并不会导入aa.bb.cc包下面的类。这时需要这样写:import aa.bb.*; import aa.bb.cc.*;
  13. 访问修饰符(access modifier)。
    1. public(公共的):被public所修饰的属性和方法可以被所有类访问。
    2. protected(受保护的):被protected所修饰的属性和方法可以在类内部、相同包以及该类的子类所访问。
    3. private(私有的):被private所修饰的属性和方法只能在该类内部使用
    4. 默认的(不加任何访问修饰符):在类内部以及相同包下面的类所使用。
  14. equals()方法,该方法定义在Object类当中,因此Java中的每个类都具有该方法,对于Object类的equals()方法来说,它是判断调用equals()方法的引用与传进来的引用是否一致,即这两个引用是否指向的是同一个对象。对于Object类的equals()方法来说,它等价于==。
    对于String类的equals()方法来说,它是判断当前字符串与传进来的字符串的内容是否一致。
    对于String对象的相等性判断来说,请使用equals()方法,而不要使用==。
  15. String s = “aaa”;(采用字面值方式赋值)

    1. 查找String Pool中是否存在“aaa”这个对象,如果不存在,则在String Pool中创建一个“aaa”对象,然后将String Pool中的这个“aaa”对象的地址返回来,赋给引用变量s,这样s会指向String Pool中的这个“aaa”字符串对象
    2. 如果存在,则不创建任何对象,直接将String Pool中的这个“aaa”对象地址返回来,赋给s引用。
  16. String s = new String(“aaa”);

    1. 首先在String Pool中查找有没有“aaa”这个字符串对象,如果有,则不在String Pool中再去创建“aaa”这个对象了,直接在堆中(heap)中创建一个“aaa”字符串对象,然后将堆中的这个“aaa”对象的地址返回来,赋给s引用,导致s指向了堆中创建的这个“aaa”字符串对象。
    2. 如果没有,则首先在String Pool中创建一个“aaa“对象,然后再在堆中(heap)创建一个”aaa“对象,然后将堆中的这个”aaa“对象的地址返回来,赋给s引用,导致s指向了堆中所创建的这个”aaa“对象。
  17. 数组三种定义方法:
    1. int[] a = new int[10]
    2. Int a[] = new int[10];
    3. Int[] a = {0,1,2,3,4,5,6,7,8,9};
  18. 当向ArrayList添加一个对象时,实际上就是将该对象放置到了ArrayList底层所维护的数组当中;当向LinkedList中添加一个对象时,实际上LinkedList内部会生成一个Entry对象,该Entry对象的结构为:

    Entry
    {
    Entry previous;
    Object element;
    Entry next;
    }

    其中的Object类型的元素element就是我们向LinkedList中所添加的元素,然后Entry又构造好了向前与向后的引用previous、next,最后将生成的这个Entry对象加入到了链表当中。换句话说,LinkedList中所维护的是一个个的Entry对象。

  19. 当使用HashSet时,hashCode()方法就会得到调用,判断已经存储在集合中的对象的hash code值是否与增加的对象的hash code值一致;如果不一致,直接加进去;如果一致,再进行equals方法的比较,equals方法如果返回true,表示对象已经加进去了,就不会再增加新的对象,否则加进去。
    如果我们重写equals方法,那么也要重写hashCode方法,反之亦然。 注意String类重写了hashCode()方法和equals()方法,new 两个相同字符串的String对象只有一个能加进去。

  20. Integer类有一个缓存,它会缓存介于-128~127之间的整数。
  21. 可变参数:可变参数本质就是一个数组,对于某个声明了可变参数的方法来说,我们既可以传递离散的值也可以传递数组对象。但如果将方法中的参数定义为数组,那么只能传递数组对象而不能传递离散的值。可变参数必须要作为方法参数的最后一个参数,即一个方法不可能具有两个或两个以上的可变参数。
  22. 枚举(Enum):我们所定义的每个枚举类型都继承自java.lang.Enum类,枚举中的每个成员默认都是public static final的。
    而每个枚举的成员其实就是您定义的枚举类型的一個实例(Instance)。换句话说,当定义了一个枚举类型后,在编译时刻就能确定该枚举类型有几个实例,分别是什么。在运行期间我们无法再使用该枚举类型创建新的实例了,这些实例在编译期间就已经完全确定下来了。
  23. 要想使用反射,首先需要获得待处理类或对象所对应的Class对象。
    获取某个类或某个对象所对应的Class对象的常用的3种方式:
    1. 使用Class类的静态方法forName:Class.forName(“java.lang.String”);
    2. 使用类的.class语法:String.class;
    3. 使用对象的getClass()方法:String s = “aa”; Class<?> clazz = s.getClass();
  24. 若想通过类的不带参数的构造方法来生成对象,我们有两种方式:
    1. 先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:
      Class<?> classType = String.class; Object obj = classType.newInstance();
    2. 先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成:
      Class<?> classType = Customer.class; Constructor cons = classType.getConstructor(new Class[]{}); Object obj = cons.newInstance(new Object[]{});
      若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式:
      Class<?> classType = Customer.class; Constructor cons = classType.getConstructor(new Class[]{String.class, int.class}); Object obj = cons.newInstance(new Object[]{“hello”, 3});
  25. 动态代理步骤:
    1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
    2. 创建被代理的类以及接口
    3. 通过Proxy的静态方法 newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)创建一个代理
    4. 通过代理调用方法
  26. 自定义注解:当注解中的属性名为value时,在对其赋值时可以不指定属性的名称而直接写上属性值即可;除了value以外的其他值都需要使用name=value这种赋值方式,即明确指定给谁赋值。
    当我们使用@interface关键字定义一个注解时,该注解隐含地继承了java.lang.annotation.Annotation接口;如果我们定义了一个接口,并且让该接口继承自Annotation,那么我们所定义的接口依然还是接口而不是注解;Annotation本身是接口而不是注解。可以与Enum类比。
  27. 如果try块中存在System.exit(0)语句,那么就不会执行finally块中的代码,因为System.exit(0)会终止当前运行的Java虚拟机,程序会在虚拟机终止前结束执行。
  28. 内部类(Inner Class),内部类共分为4种。
    1. 静态内部类(static inner class):只能访问外部类的静态成员变量与静态方法,生成静态内部类对象的方式为:
      OuterClass.InnerClass inner = new OuterClass.InnerClass();
    2. 成员内部类(member inner class):可以访问外部类的静态与非静态的方法与成员变量。生成成员内部类对象的方式为:
      OuterClass.InnerClass inner = new OuterClass().new InnerClass();
      若想在局部内部类中访问外部类的成员变量,语法为:OuterClass.this.a;
    3. 局部内部类(Local Inner Class):定义在方法当中,只能访问方法中声明的final类型的变量。
    4. 匿名内部类(Anonymous Inner Class):匿名内部类会隐式地继承一个父类或实现一个接口。
  29. 在序列化时,static变量是无法序列化的;如果A包含了对B的引用,那么在序列化A的时候也会将B一并地序列化;如果此时A可以序列化,B无法序列化,那么当序列化A的时候就会发生异常,这时就需要将对B的引用设为transient,该关键字表示变量不会被序列化。
  30. 停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
  31. Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。
  32. 如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
  33. 类的加载,连接与初始化:
    1. 加载:查找并加载类的二进制数据。把字节码文件加载到内存中
    2. 连接:
    3. 验证:确保被加载的类的正确性 (编译后的字节码是否正确)
    4. 准备:为类的静态变量分配内存,并将其初始化(分配默认值)
    5. 解析:把类中的符号引用转换为直接引用
    6. 初始化:为类的静态变量赋予正确的初始值(正确的初始值指用户附的,执行静态代码块)
  34. Java类的主动使用:(只有类的主动使用才会执行上面类的初始化)
    1. 创建类的实例
    2. 访问某个类或接口的静态变量,或者对该静态变量赋值
    3. 调用类的静态方法
    4. 反射(如 Class.forName(“xxx”))
    5. 初始化一个类的子类
    6. Java虚拟机启动时被标明为启动类的类(同一个Java文件包含多个类)
  35. 有两种类的类加载器
    1. Java虚拟机自带的加载器
      1. 根类加载器(BootStrap)使用C++编写,程度员无法在Java代码中获得
      2. 扩展类加载器,使用Java代码实现
      3. 系统类加载器(应用加载器),使用Java代码实现
    2. 用户自定义的类加载器都是java.lang.ClassLoader的子类,用户可以定制类的加载方式
  36. 程序中对子类的“主动使用”会导致父类被初始化,但对父类的“主动”使用并不会导致子类的初始化
  37. 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
文章目录