Java 垃圾回收机制

在Java中,JVM负责内存动态分配和垃圾回收的问题。Java的对象必须要有引用才能被使用,也就是说如果要操作对象,必须通过引用来进行。如果一个对象唯一的引用变量死了(随着堆栈块一起解散),对象就会被认定为可被垃圾回收(Garbage Collection)的。没有被引用的对象,是没有存在意义的,因为没有人知道它的地址,无法调用它,它的存在只会浪费空间。 目前内存的动态分配和内存回收技术已经相当成熟,但还是需要了解GC和内存分配,这样当需要排查各种内存溢出、内存泄漏问题时,当GC成为系统达到更高并发量的瓶颈时,需要对这些自动化的技术实施必要的监控和调节。 判断对象是否可回收 最简单的方法是通过引用计数(reference counting)来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,那么这个对象就被判定为可被回收的对象了。这种方式实现简单,而且效率较高,但是它无法解决对象间循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)。 主流的商用程序语言(Java,C#,Lisp)的主流实现中,使用可达性分析(reachability analysis)来判定对象是否存活。 使用一些列GC Root对象作为起始点,从这些节点开始往下沿着引用链搜索,如果GC Root到某个对象无法通过任何引用链项链,则该对象会被标记一次, 并且进行一次筛选, 筛选的条件是此对象是否有必要执行finalize()方法. 当对象没有覆盖finalize()方法, 或者该方法已经被JVM调用过, JVM都会视之为没有必要执行finalize()。 如果该对象被判定为有必要执行finalize()方法, 那么这个对象会被放置在F-Queue队列中, 并在稍后由一个由JVM自动建立的, 低优先级的Finalizer线程去执行. 执行只是触发该方法, 但不会等待它结束, 因为可能会有执行缓慢或者死循环等特殊情况 稍后GC会动F-Queue里的对象进行第二次标记, 如果对象要在finalize()中避免被消灭, 只需要重新与引用链上的任何一个对象建立关联即可(finalize()的优先级较低), 这样第二次标记时它将不会被考虑. 否则就只能被回收. 要注意, 任何一个对象的finalize()只会被系统自动调用一次, 下次再GC时不会再执行, 也就是只有一次自救机会. 在Java中,可作为GC Roots的对象包括: 虚拟机栈(栈帧中的本地变量表)中引用的对象。 方法区中类静态属性引用的对象 方法区中常量引用的对象 本地方法栈中native方法引用的对象 引用 引用分为强引用,软引用,弱引用,虚引用。根据不同引用,有不同的GC回收策略。 强引用:类似Object obj = new Object();这种, 垃圾回收器永远不会回收他们 软引用:非必须引用,内存溢出之前进行回收. 如果这次回收后内存还不足,才会抛出内存溢出异常 Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; sf.get();//有时候会返回null 软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。 弱引用:非必须引用, 强度比软引用更弱. 当GC工作时, 无论当前内存是否足够, 都会回收掉只有弱引用关联的对象. Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj); obj = null; wf....

2017-06-19 · 1 min · Cong Chan

Java Exceptions

Exception-handling 假如调用了一个不是自己写的方法, 该方法执行某些有风险的任务(当然,自己写的也可能会有风险),可能会在运行期间出状况,那么就必须认识到该方法是有风险的, 要写出可以在发生状况时做出应对的代码. 当程序出现错误时,假如继续运行下去已经没有意义(或者根本不可能继续),那么我们就想要中断正常的控制流程 - throws an exception。 Throwing Exceptions 比如当想从某ArrayMap中提取某个不存在的键值时, java自动抛出一个implicit exception $ java ExceptionDemo Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1 at ArrayMap.get(ArrayMap.java:38) at ExceptionDemo.main(ExceptionDemo.java:6) 如果想让自己的程序抛出更详细的信息, 可以在程序中加入explicit exception public V get(K key) { intlocation = findKey(key); if(location < 0) { throw newIllegalArgumentException("Key " + key + " does not exist in map."\); } return values[findKey(key)]; } $java ExceptionDemo Exception in thread "main" java.lang.IllegalArgumentException: Key yolp does not exist in map....

2017-05-29 · 3 min · Cong Chan

Java Immutability

An immutable data type is a data type whose instances cannot change in any observable way after instantiation. 比如String是immutable, Array是mutable. Immutable 的类型: String, Integer, Double, Color, Vector, Transaction, Point2D. Mutable: StringBuilder, Stack, Counter, Java array. public final class Vector { private final int N; private final double[] data; public Vector(double[] data) { this.N = data.length; this.data = new double[N]; for (int i = 0; i < N; i++) { // defensive copy of mutable instance variables this....

2017-05-29 · 1 min · Cong Chan

Java 堆栈

认识栈和堆的概念, 有助于了解变量的有效范围(scope),对象的建立,内存管理,线程(thread)和异常处理。 对象生存在可垃圾回收的堆(heap)上面,方法调用(方法的参数,局部变量的引用)的生存空间(stack)。 当JVM启动时,从底层操作系统取得一块内存, 以此区段来执行Java程序。 实例变量和局部变量 实例变量声明在类方法之外:实例变量保存在所属的对象中,位于堆上。如果实例变量是个对对象的引用,则引用和对象都在堆上。 public class Duck { int size; Mouth mouth; } 当新建一个Duck()时, Java必须在堆上帮它找位置, 保证空间足够存放该对象所有实例变量. 对于primitive数据类型的实例变量, Java会根据类型的大小为它们留下堆空间, int 32, long 64 .... 如果实例变量是个对象, 也就是Duck对象带有引用变量mouth, Java会在堆上留给Duck的专属空间只包含该引用变量, 不包含引用的那个对象. 被引用的对象, 只有实际实例化后, 才会在堆上占有一席之地mouth = new Mouth(), 该空间并不属于Duck. 局部变量声明在方法或方法的参数上:它们是暂时的,且生命周期只限于被放在栈上的这段时间(也就是方法调用至执行完毕位止)。所有局部变量存在栈上相对应的堆栈块中,故又称为栈变量。引用对象的局部变量(只是对对象的引用)和primitive数据类型变量也都放在栈上。 public void foo(int x) { int i = x + 3; } 不管是实例变量还是局部变量, 它们指向的对象本身在堆上。 栈 程序对方法的调用,以带有方法的状态的堆栈块的形式,不断堆在栈上面,当前被执行的方法处于栈顶。执行完毕的方法会出栈。递归调用太深层,会导致栈空间耗尽。 如果方法中有局部变量引用对象,那么该局部变量也是存在于栈中. 但被引用的对象还是运行在堆上面. 堆 变量的生命周期 局部变量只会存活在声明该变量的方法中, 其余方法无法接触到 实例变量的寿命与所属对象相同. 如果所属对象还活着, 则实例变量也会活着. public class Life { int size; // size 在类中一直可以用 public void setSize(int s) { size = s; } // 但 s 仅限于setSize中, 且在方法结束后消失 } 所以这里存在生命周期life和范围scope的概念差别...

2017-05-29 · 1 min · Cong Chan

Java 封装, 包, JAR, 权限控制

Encapsulation 封装是面向对象编程的基本原则之一,也是程序员处理复杂性一个方法。管理复杂性是编写大型程序时必须面对的主要挑战之一。 对抗复杂性的一些工具包括: Hierarchical abstraction: 创建一个个具有明确的 abstraction barriers 的抽象层 Abstraction Barriers:使用private, 保证对象内部不能被查看, 确保底层的复杂性不会暴露给外部世界。 “Design for change” (D. Parnas) Organize program around objects. Let objects decide how things are done. Hide information others don’t need. 大概的想法都是 - 程序应该被构建成模块化,可互换的片段,可以在不破坏系统的情况下进行交换。 封装就是构建在这种对外部隐藏信息的概念上。以细胞为类比:细胞内部可能非常复杂,由染色体,线粒体,核糖体等组成,但它完全被封装在一个单一模块中 - 抽象了内部的复杂性。 Module: A set of methods that work together as a whole to perform some task or set of related tasks. Encapsulated: A module is said to be encapsulated if its implementation is completely hidden, and it can be accessed only through a documented interface....

2017-05-29 · 2 min · Cong Chan

Java 抽象数据类型

Abstract Data Types (ADTS) ADTS 是由其行为属性定义的抽象类型, 跟如何实现无关. 堆栈 Stacks 和队列 Queues 是两种类似的线性集合。堆栈是后进先出的ADT:元素总是从数据结构的一端添加或删除。队列是先进先出的ADT. 二者都支持以下操作: push(): 加入 peek(): 返回下一个 poll(): 返回下一个并删除 Java的Deque(double ended queue, “deck”) 接口融合了堆栈和队列, 支持两端的元素插入和移除. ArrayDeque和LinkedListDeque都是实现deque这个接口,deque只是罗列了一些 methods,也即是一种合约,保证会实现的行为。而这些方法的具体实现则是由ArrayDeque和LinkedListDeque完成。从概念上讲,deque就是一种抽象的数据类型,只说会有什么行为,但不体现这些行为的具体实现方式,所以是抽象的。 优先级队列 priority queue 的每个元素都有一个与之关联的优先级,以决定从队列中元素操作的顺序。 三种方式实现Stack的push(Item x): 继承模式:extends LinkedList<Item>以使用其方法: public class ExtensionStack<Item> extends LinkedList<Item> { public void push(Item x) { add(x); } } 委托模式Delegation, 生成Linked List并调用其方法来达到目的 public class DelegationStack<Item> { private LinkedList<Item> L = new LinkedList<Item>(); public void push(Item x) { L.add(x); } } 类似委托模式, 只是这里可以利用任何实现了List接口的类, 如Linked List, ArrayList, 等等 public class StackAdapter<Item> { private List L; public StackAdapter(List<Item> worker) { L = worker; } public void push(Item x) { L....

2017-05-29 · 2 min · Cong Chan

Java 格式

Java格式化指令" 跟在%后面的都是格式化指令. % [argument number] [flags] [width] [.precision] type []内都是选择性的参数, 且必须按照顺序. argument number 当要格式化的参数超过一个, 这里指定是哪一个 flags 特定类型的特定选项, 如数字是否要加逗号或正负号 width 最小的字符数. 但不是总数, 输出可以超过此宽度, 若不足则会主动补零 .precision 精确度 type 类型标识 d decimal, 十进制整数 f 浮点数 x hexadecimal, 16进制 c character 如format("%, 6.1f", 33.000); 如果有多个待输出的参数, 可以把新的参数加到后面, 并对应两个不同的格式化设定, 也就是两个%格式指令 String s = String.format("%,.2f out of %,d", 999, 1000) 可变参数列表 可以看到格式化的参数似乎可以不断添加,如果用重载来实现会显得不现实。为了应对格式化的API,Java支持可变参数列表 varable argument list (vararg). 日期 日期格式化的类型是用t开头的两个字符表示 %tc 完整的日期与时间 Sun Nov 03 14:52:41 2018 %tr 只有时间 03:01:47 PM 周月日 Sunday, November 03, 通过组合而来...

2017-05-29 · 1 min · Cong Chan

Java 比较对象大小

对物品排序首先需要比较各个物品的大小, 这个大小的定义既可以是按照"自然顺序", 也可以是其他指定的特殊规则. Comparable Java的对象不能直接使用>, <, =进行比较. 在Python或C++中,当应用于不同对象类型时,比较运算符可以重新定义,但Java不支持。但可以借用接口继承,Java提供了一个Comparable接口,包含一个compareTo方法, 以保证任何实现该接口的类可以和其他同类做比较: /** Return negative if this < o. Return 0 if this equals o. Return positive if this > o. */ public interface Comparable<T> { public int compareTo(T obj); } 当有class需要与其他class比较时, 就实现这个接口: public class Dog implements Comparable<Dog> { ... public int compareTo(Dog uddaDog) { return this.size - uddaDog.size; } } Comparable定义了类用于比较的自然顺序(Natural order), 返回的是三种结果负整数, 0, 正整数, 分别对应小于, 等于和大于. Insertion.sort()不需要知道要排序的数组类型, 因为它直接调用数组成员自带的compareTo方法. Java的Integer, Double, String, Date, File数据类型都扩展了Comparable接口。...

2017-05-29 · 2 min · Cong Chan

Java 泛型

泛型 泛型意味着更好的类型安全性。主要目的是支持类型安全性的集合,让问题尽可能在编译阶段就能捉到。 泛型定义在类声明中 public class ArrayLiat<E> extends AbstractLìst<E> implements List<E> { public boolean add (E o); } E代表用来创建赋予初始ArrayList的类型 ArrayList<String> list = new ArrayList<String>; 编译器会自动把E看做String. 泛型方法 使用未定义在类声明的类型参数: 在返回类型之前指定泛型 maxKey: 返回给定ArrayMap中所有keys的最大值(仅在key可以比较的情况下)。假如这样写public static K maxKey(Map61B<K, V> map) { ... }会报错. 要将方法声明为泛型,必须在返回类型前面指定正式的类型参数 public static <K extends Comparable<K>, V> K maxKey(Map61B<K, V> map) { List<K> keylist = map.keys(); K largest = map.get(0); for (K k: keylist) { if (k.compareTo(largest)) { largest = k; } } return largest; } K extends Comparable<K> 保证了keys必须实现Comparable接口(也是一个generic接口), 并可以与其他K进行比较。...

2017-05-29 · 2 min · Cong Chan

Java 迭代 Iteration

Java提供了 foreach (enhanced for) 的循环简写语法: ArrayMap<String, Integer> am = new ArrayMap<String, Integer>(); for (String s : am) { System.out.println(s); } 实现的关键原理是使用Iterable接口使一个类变成可迭代的: 该接口包含一个iterator()方法用于返回一个Iterator对象。Iterator接口定义Iterator对象和hasNext(), next()方法来进行实际的迭代操作。 public class ArrayMap<K, V> implements Map61B<K, V>, Iterable<K> { private K[] keys; private V[] values; int size; public ArrayMap() { keys = (K[]) new Object[100]; values = (V[]) new Object[100]; size = 0; } @Override public Iterator<T> iterator() { return new KeyIterator(); } public class KeyIterator implements Iterator<K> { private int ptr; public KeyIterator() { ptr = 0; } public boolean hasNext() { return (ptr !...

2017-05-29 · 1 min · Cong Chan