纪念我曾经的 JAVA 姿势

Jul9560 8年前
   <h2><strong>目录</strong></h2>    <ul>     <li> <p>流</p> </li>     <li> <p>异常</p> </li>     <li> <p>注解</p> </li>     <li> <p>安全性</p> </li>     <li> <p>类加载</p> </li>     <li> <p>关键字</p> </li>     <li> <p>初始化</p> </li>     <li> <p>多线程</p> </li>     <li> <p>线程池</p> </li>     <li> <p>内存模型</p> </li>    </ul>    <h2><strong>流</strong></h2>    <p>Java所有的流类位于java.io包中,都分别继承字以下四种抽象流类型。</p>    <table>     <thead>      <tr>       <th>Type</th>       <th>字节流</th>       <th>字符流</th>      </tr>     </thead>     <tbody>      <tr>       <td>输入流</td>       <td>InputStream</td>       <td>Reader</td>      </tr>      <tr>       <td>输出流</td>       <td>OutputStream</td>       <td>Writer</td>      </tr>     </tbody>    </table>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fbe07531739bbd27737bb5e3153b4298.png"></p>    <p>继承自InputStream/OutputStream的流都是用于向程序中输入/输出数据,且数据的单位都是字节(byte=8bit)。</p>    <p>继承自Reader/Writer的流都是用于向程序中输入/输出数据,且数据的单位都是字符(2byte=16bit)。</p>    <h2><strong>异常</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3598c712f622d3fca7cef238eed58afc.jpg"></p>    <p>Java的异常(包括 <strong>Exception</strong> 和 <strong>Error</strong> )分为:</p>    <ul>     <li> <p>可查的异常(checked exceptions)</p> </li>    </ul>    <p>除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。</p>    <ul>     <li> <p>不可查的异常(unchecked exceptions)</p> </li>    </ul>    <p>包括运行时异常(RuntimeException与其子类)和错误(Error)。</p>    <p>运行时异常和非运行时异常:</p>    <ul>     <li> <p>RuntimeException</p> </li>    </ul>    <p>NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。</p>    <ul>     <li> <p>RuntimeException以外的Exception</p> </li>    </ul>    <p>从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。</p>    <p> </p>    <p><span id="target"></span></p>    <h2><strong>注解</strong></h2>    <p>Java SE5内置了三种标准注解:</p>    <pre>  <code class="language-java">@Override,表示当前的方法定义将覆盖超类中的方法。     @Deprecated,使用了注解为它的元素编译器将发出警告,因为注解@Deprecated是不赞成使用的代码,被弃用的代码。     @SuppressWarnings,关闭不当编译器警告信息。</code></pre>    <p>Java还提供了4中注解,专门负责新注解的创建:</p>    <ul>     <li> <p>@Target:</p> </li>    </ul>    <p>表示该注解可以用于什么地方,可能的ElementType参数有:</p>    <p>CONSTRUCTOR :构造器的声明</p>    <p>FIELD :域声明(包括 enum 实例)</p>    <p>LOCAL_VARIABLE :局部变量声明</p>    <p>METHOD :方法声明</p>    <p>PACKAGE :包声明</p>    <p>PARAMETER :参数声明</p>    <p>TYPE :类、接口(包括注解类型)或 enum 声明</p>    <ul>     <li> <p>@Retention</p> </li>    </ul>    <p>表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:</p>    <p>SOURCE :注解将被编译器丢弃</p>    <p>CLASS :注解在class文件中可用,但会被VM丢弃</p>    <p>RUNTIME :VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息</p>    <ul>     <li> <p>@Document</p> </li>    </ul>    <p>将注解包含在Javadoc中</p>    <ul>     <li> <p>@Inherited</p> </li>    </ul>    <p>允许子类继承父类中的注解</p>    <p>Example</p>    <p>定义注解:</p>    <pre>  <code class="language-java">@Target(ElementType.METHOD)  @Retention(RetentionPolicy.RUNTIME)  public @interface UseCase {      public String id();      public String description() default "no description";  }</code></pre>    <p>使用注解:</p>    <pre>  <code class="language-java">public class PasswordUtils {       @UseCase(id = 47, description = "Passwords must contain at least one numeric")       public boolean validatePassword(String password) {           return (password.matches("\\w*\\d\\w*"));       }          @UseCase(id = 48)       public String encryptPassword(String password) {           return new StringBuilder(password).reverse().toString();       }   }</code></pre>    <p>解析注解:</p>    <pre>  <code class="language-java">public static void main(String[] args) {       List<Integer> useCases = new ArrayList<Integer>();       Collections.addAll(useCases, 47, 48, 49, 50);       trackUseCases(useCases, PasswordUtils.class);   }      public static void trackUseCases(List<Integer> useCases, Class<?> cl) {       for (Method m : cl.getDeclaredMethods()) {           UseCase uc = m.getAnnotation(UseCase.class);           if (uc != null) {               System.out.println("Found Use Case:" + uc.id() + " "                           + uc.description());               useCases.remove(new Integer(uc.id()));           }       }       for (int i : useCases) {           System.out.println("Warning: Missing use case-" + i);       }   }   // Found Use Case:47 Passwords must contain at least one numeric   // Found Use Case:48 no description   // Warning: Missing use case-49   // Warning: Missing use case-50</code></pre>    <p>返回目录</p>    <h2><strong>安全性</strong></h2>    <ol>     <li> <p>严格遵循面向对象的规范。这样封装了数据细节,只提供接口给用户。增加了数据级的安全性。</p> </li>     <li> <p>无指针运算。java中的操作,除了基本类型都是引用的操作。引用是不能进行增减运算,不能被直接赋予内存地址的,从而增加了内存级的安全性。</p> </li>     <li> <p>数组边界检查。这样就不会出现C/C++中的缓存溢出等安全漏洞。</p> </li>     <li> <p>强制类型转换。非同类型的对象之间不能进行转换,否则会抛出ClassCastException</p> </li>     <li> <p>语言对线程安全的支持。java从语言级支持线程。从而从语法和语言本身做了很多对线程的控制和支持。</p> </li>     <li> <p>垃圾回收。</p> </li>     <li> <p>Exception。</p> </li>    </ol>    <p>返回目录</p>    <h2><strong>类加载</strong></h2>    <h3><strong>原理</strong></h3>    <p>ClassLoader使用的是 <strong>双亲委托模型</strong> 来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。</p>    <p>当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。</p>    <p>如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。</p>    <p>JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。</p>    <h3><strong>加载器</strong></h3>    <ol>     <li> <p>BootStrap ClassLoader</p> </li>    </ol>    <p>启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。</p>    <pre>  <code class="language-java">URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();     for (int i = 0; i < urls.length; i++) {         System.out.println(urls[i].toExternalForm());       }     // 也可以通过sun.boot.class.path获取     System.out.println(System.getProperty("sun.boot.class.path"))</code></pre>    <ol>     <li> <p>Extension ClassLoader</p> </li>    </ol>    <p>扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。</p>    <ol>     <li> <p>App ClassLoader</p> </li>    </ol>    <p>系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件</p>    <p><strong>注意:</strong></p>    <pre>  <code class="language-java">除了Java默认提供的三个ClassLoader之外,用户还可以根据需要定义自已的ClassLoader,而这些自定义的ClassLoader都必须继承自java.lang.ClassLoader类,也包括Java提供的另外二个ClassLoader(Extension ClassLoader和App ClassLoader)在内。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。</code></pre>    <p>返回目录</p>    <h2><strong>关键字</strong></h2>    <p>strictfp( strict float point )</p>    <p>strictfp 关键字可应用于类、接口或方法。使用strictfp关键字声明一个方法时,该方法中所有的float和double表达式都严格遵守FP-strict的限制,符合IEEE-754规范。当对一个类或接口使用strictfp关键字时,该类中的所有代码,包括嵌套类型中的初始设定值和代码,都将严格地进行计算。严格约束意味着所有表达式的结果都必须是 IEEE 754算法对操作数预期的结果,以单精度和双精度格式表示。</p>    <p>如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,可以用关键字strictfp。</p>    <p>transiant</p>    <p>变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。</p>    <p>volatile</p>    <p>作为指令关键字,确保本条指令不会因编译器的优化而省略,修饰变量,保证变量每次都是从内存中重新读取。</p>    <p>final</p>    <ol>     <li> <p>修饰基础数据成员(as const)</p> </li>     <li> <p>修饰类或对象的引用</p> </li>     <li> <p>修饰方法的final(cannot overwrite)</p> </li>     <li> <p>修饰类或者参数</p> </li>    </ol>    <p>返回目录</p>    <h2><strong>初始化</strong></h2>    <p><em>父静态->子静态</em></p>    <p>父变量->父初始化区->父构造</p>    <p>子变量->子初始化区->子构造</p>    <h2><strong>多线程</strong></h2>    <p>JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。</p>    <p>返回目录</p>    <h2><strong>线程池</strong></h2>    <p>concurrent下的线程池:</p>    <table>     <thead>      <tr>       <th>名称</th>       <th>功能</th>      </tr>     </thead>     <tbody>      <tr>       <td>ExecutorService</td>       <td>真正的线程池接口</td>      </tr>      <tr>       <td>ScheduledExecutorService</td>       <td>能和Timer/TimerTask类似,解决那些需要任务重复执行的问题</td>      </tr>      <tr>       <td>ThreadPoolExecutor</td>       <td>ExecutorService的默认实现</td>      </tr>      <tr>       <td>ScheduledThreadPoolExecutor</td>       <td>继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现</td>      </tr>     </tbody>    </table>    <p><strong>Executors</strong></p>    <ol>     <li> <p>newSingleThreadExecutor</p> </li>    </ol>    <p>创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。</p>    <ol>     <li> <p>newFixedThreadPool</p> </li>    </ol>    <p>创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。</p>    <ol>     <li> <p>newCachedThreadPool</p> </li>    </ol>    <p>创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,</p>    <p>那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。</p>    <ol>     <li> <p>newScheduledThreadPool</p> </li>    </ol>    <p>创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。</p>    <p>返回目录</p>    <h2><strong>内存模型</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/de70f1cef5f3de3f42b250508cddd2f7.png"></p>    <p>Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,线程只能访问自己的工作内存,不可以访问其它线程的 工作内存。工作内存中保存了主内存共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中。</p>    <p>如何保证多个线程操作主内存的数据完整性是一个难题,Java内存模型也规定了工作内存与主内存之间交互的协议,首先是定义了8种原子操作:</p>    <ul>     <li> <p>lock:将主内存中的变量锁定,为一个线程所独占</p> </li>     <li> <p>unclock:将lock加的锁定解除,此时其它的线程可以有机会访问此变量</p> </li>     <li> <p>read:将主内存中的变量值读到工作内存当中</p> </li>     <li> <p>load:将read读取的值保存到工作内存中的变量副本中。</p> </li>     <li> <p>use:将值传递给线程的代码执行引擎</p> </li>     <li> <p>assign:将执行引擎处理返回的值重新赋值给变量副本</p> </li>     <li> <p>store:将变量副本的值存储到主内存中。</p> </li>     <li> <p>write:将store存储的值写入到主内存的共享变量当中。</p> </li>    </ul>    <h3><strong>内存组成</strong></h3>    <p><strong>堆(Heap)</strong></p>    <p>运行时数据区域,所有类实例和数组的内存均从此处分配。Java虚拟机启动时创建。对象的堆内存由称为垃圾回收器 的自动内存管理系统回收。</p>    <ul>     <li> <p>News Generation(Young Generation即图中的Eden + From Space + To Space)</p>      <ul>       <li> <p>Eden 存放新生的对象</p> </li>       <li> <p>Survivor Space 两个 存放每次垃圾回收后存活的对象</p> </li>      </ul> </li>     <li> <p>Old Generation(Tenured Generation 即图中的Old Space) 主要存放应用程序中生命周期长的存活对象</p> </li>    </ul>    <p><strong>非堆内存</strong></p>    <p>JVM具有一个由所有线程共享的方法区。方法区属于非堆内存。它存储每个类结构,如运行时常数池、字段和方法数据,以及方法和构造方法的代码。它是在Java虚拟机启动时创建的。</p>    <p>除了方法区外,Java虚拟机实现可能需要用于内部处理或优化的内存,这种内存也是非堆内存。例如,JIT编译器需要内存来存储从Java虚拟机代码转换而来的本机代码,从而获得高性能。</p>    <ul>     <li> <p>Permanent Generation  (图中的Permanent Space)存放JVM自己的反射对象,比如类对象和方法对象</p> </li>     <li> <p>native heap</p> </li>    </ul>    <p> </p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007122432</p>    <p> </p>