label break/continue似乎非常必要,这种情况尤其见于多重循环,如果想直接调到最外层循环(break),或者是从外层循环继续执行(continue).缺少这种机制的话,外层循环只能够使用蹩脚的标记,一层层地逐层跳出。
class X{
public static void main(String[] args){
int i=0;
int j=0;
out:
for(i=0;i<10;i++){
for(j=0;j<10;j++){
if((i+j)==15){
break out;
}
}
}
System.out.println(i+","+j);
}
}
final关键字应该是有下面几个用途:
java关键字assert可以用来进行断言,和C++一样。但是天煞的java虚拟机必须指定标记才会开启assert特性。java -ea断言才会生效。java -ea:package_name…可以指定名字空间下所有类断言打开,java -ea:classname可以指定某个类断言打开。如果不带任何参数的话那么是将所有断言打开。同样使用-da可以禁用某个特定类或者是包的断言。 NOTE(dirlt):其实想想觉得这个还是很不错的,在运行时控制而不是在编译时控制,会让更多的人喜欢使用assertion.但是我觉得默认的话,应该是开启的除非显示关闭。
java断言可以指定检查表达式以及出错表达式,asser 条件:表达式
class X{
public static void main(String[] args) {
assert 0==1 : "omg";
}
}
有些类不是由类加载器加载,而是直接由虚拟机加载。使用-ea/-da不能够应用到这些类上面。对于系统类来说,需要使用-esa/-dsa来控制断言。
java提供了三种位移操作符:
注意java只提供了有符号整数
class X{ public static void main(String[] args){ int x=(1 << 31); assert((x >>> 1) == (1 << 30)); assert((x >> 1) == ((1 << 31) | (1 << 30))); assert((x << 1) == 0); } }
java相对于C++来说,在对象构造上面,需要多考虑初始化块这个概念(包括静态初始化块)。所谓初始化块,可以在对象执行构造函数之前执行的一 块代码。而静态初始化块,当引用到这个类的时候第一次就会执行。有了这个特性之后,我们就可以创建不需要使用main就可以运行的例子
class App {
static {
System.out.println("hello,world");
System.exit(0);
}
}
另外相对于C++来说,java的字段都可以通过简单的赋值就完成初始化,而不需要像C++在构造函数后面接上一推init variable list.
整个java对象构造过程大致如下:
对于构造函数来说,如果需要调用父类构造函数可以使用super(…),如果需要调用同类内部其他重载版本可以使用this(…)
java提供了一个finalize方法,但是这个方法并不是在析构时候执行,而是在被GC之前执行,但是你很难知道这个对象什么时候会被GC.因此最好 不要复写这个方法。如果想在GC之前做一些事情的话,可以通过Runtime.addShutdownHook添加钩子来在GC之前触发。
引入内部类(inner class)主要有下面三个原因:
关于java的内部类大概有这么几种:
class X{ private int x=1; class Y{ void foo(){ System.out.println(x); } } public static void main(String[] args){ X x=new X(); Y y=x.new Y(); y.foo(); } }
内部类生成class使用$分隔,所以可以看到X$Y.class文件。可以看到在Y里面访问x字段。原理非常简单,在Y内部生成了X的一个实例指针,同时在X里面为x字段提供了一个静态访问方法。
class X extends java.lang.Object{ X(); public static void main(java.lang.String[]); static int access$000(X); // 在X中静态访问方法 } class X$Y extends java.lang.Object{ final X this$0; // 在Y里面提供了外围实例指针 X$Y(X); void foo(); }
了解了这些之后对于x.new Y()这样的语法就好理解了。我们首先需要一个外围实例,才能够构造Y对象出来。
class X{ private int x=1; void foo(final int y){ class Y{ void foo(int z){ System.out.println(x+","+y+","+z); } } Y iy=new Y(); iy.foo(20); } public static void main(String[] args){ X x=new X(); x.foo(10); } }
这里要求参数为final原因很简单。因为局部类需要将这个参数在构造的时候就拿过来放在自己类中。final的话语义上会比较好理解。可以看看生成class内容
class X$1Y extends java.lang.Object{ final int val$y; // 这里将外部y捕获。 final X this$0; X$1Y(X, int); // 构造函数传入y void foo(int); }
class X{
public static void main(String[] args) throws InterruptedException {
Thread y=new Thread() { // 这个地方需要传入基类的构造参数。
public void run() {
for(int i=0;i<10;i++){
System.out.println("run...");
}
}
};
y.start();
y.join();
}
}
生成的类名称为X$1.class.其中1是数字用来区别匿名类。注意匿名类都是final的。
final class X$1 extends java.lang.Thread{ X$1(); public void run(); }
java有下面4个访问修饰符可以用来控制可见性:
访问修饰符可以作用在类,方法以及字段上面,控制可见性效果是相同的。
所谓静态导入,就是可以导入某个类下面的静态方法以及静态域,通常来说这样可以使得代码更容易阅读,比如
import static java.lang.Math.*; class App { public static void main(String[] args){ // System.out.println(Math.sqrt(Math.pow(3,2)+Math.pow(4,2))); System.out.println(sqrt(pow(3,2)+pow(4,2))); } }
有时候import顺序还是比较重要的,比如下面这个程序com/dirlt/X.java
/* coding:utf-8 * Copyright (C) dirlt */ package com.dirlt; import com.dirlt.X.B.A; import java.util.ArrayList; public class X{ public static class B extends ArrayList { public class A{ } } }
编译会出现如下问题
➜ ~ javac com/dirlt/X.java com/dirlt/X.java:10: cannot find symbol symbol : class ArrayList location: class com.dirlt.X public static class B extends ArrayList { ^ 1 error
这个import顺序intellj认为是正确的,而且只需要反转两个import的顺序就可以正常编译。 NOTE(dirlt):因此我花了比较多的时间纠结在这个问题上面,因为intellij不太可能错误把,而且问题也比较诡异 我不太理解java的导入顺序,但是猜想和C++的include非常类似,出现上面的问题可能就是循环依赖导致的问题
解决这个问题最好的办法,我觉得应该就是: 对于文件内部本身的类,不要使用import来导入,直接使用全称即可。
TODO(dirlt):
其实一开始Date是想做成日历的。所谓日历就是说能够处理年月日这些信息。但是Date本身处理比较差,没有考虑闰秒这种东西,另外因为日历仅仅 是历法其中的一种,虽然广泛使用。因此有必要将历法单独形成一个类称为Calendar,而日历是历法的一种实现在Java里面是 GregorianCalendar.而现在Date仅仅用于保存一个绝对的时间点就是时刻,保存的方法就是相对于某一固定时间点的毫秒数,而这个时间点 就称为纪元(epoch),它是UTC 1970.1.1 00:00:00。
因此我们在比较时刻方面的话,可以使用Date,而在处理历法方面的话需要使用GregorianCalendar.
java里面异常都是派生于Throwable,但是分解成为两个分支:
RuntimeException包括下面几种情况:
java语言规范将派生于Error或者是RuntimeException的所有异常称为未检查异常(unchecked exception),而将所有其他异常(也就是编译时异常)称为已检查异常(checked).称为已检查异常原因是因为,java的异常规格也是作为 函数声明的一部分的。因此如果方法foo抛出异常X,那么调用foo的方法,要么检查异常X,要么就在自己的规则里面写上throws X传给上层处理,无论如何你都是需要面对这个异常的,所以称为已检查。
使用代理可以动态地生成一些类或者是接口(但是不是动态生成代码)。创建一个代理对象,使用Proxy类的newProxyInstance方法,有下面三个参数:
调用处理器接口为Object invoke(Object proxy, Method method, Object… args).其中proxy表示代理对象本身,method,args表示调用方法以及参数。
import java.util.logging.*; import java.lang.reflect.*; class X{ public static void main(String[] args) throws InterruptedException { final Runnable r=new Runnable() { public void run() { for(int i=0;i<10;i++){ System.out.println("run..."); } } }; Runnable proxy=(Runnable)Proxy.newProxyInstance(r.getClass().getClassLoader(),new Class[]{Runnable.class}, new InvocationHandler() { public Object invoke(Object proxy, Method m, Object[] args){ System.out.println("entering..."); try { return m.invoke(r,args); } catch(Exception ex){ return null; } } }); Thread t=new Thread(proxy); t.start(); t.join(); } }
Class类本身表示这个类的一些元信息。通常拿到这个类的元信息之后,就可以完成一些动态事情比如反射。java有三种方式可以获得Class类:
获得Class之后,就可以获取到这个class内部:
这样就可以开始做一些反射工作了。 NOTE(dirlt):more about reflection
线程包括下面6种状态,并且切换关系如下:
守护线程(daemon)和unix操作系统的daemon有些差别。在java里面如果还有存活的线程的话,即使main线程完毕那么程序依然不会结束 (这个在c/c++程序里面则不然)。如果将线程设置成为daemon状态的话,那么最后剩下的线程都是daemon的话,那么jvm也会自动退出。
Runnable的run方法是不允许抛出任何异常的,对于可检查的异常可以在代码里面完成,而对于不可检查的异常因为不能够处理,因此如果触发的话那么线程终止。而对于可检查异常如果没有处理的话,那么在线程死亡之前,异常会被一个异常处理器处理:
synchronized关键字其实有两个场景
一旦理解这点之后,就比较好理解為什麼存在
这些方法了。其实都是相当于这个lock对应的condition本身提供的方法。
volatile关键字为 实例字段 的同步访问提供了一种免锁机制。如果声明一个字段为volatile的话,那么编译器和虚拟机就可以知道这个字段很可能会被另外一个线程并发更新。 NOTE(dirlt):在我看来使用volatile最好是作用在基本类型上面,这里将对象指针本身也作为基本类型来看待=D
為什麼抛弃stop和suspend方法? 其实这点非常好理解,因为这些方法都尝试破坏线程本身正常的行为。比如A,B两个线程同时acquire一个lock,如果A成功之后,B在等待,这个之后A被stop或者是suspend的话,那么情况就变成了死锁。
TODO(dirlt):
jmx似乎是一个标准,在JDK里面有默认的实现。通过jmx可以暴露jvm进程的一些运行参数以及系统状态(jdk默认实现),也可以暴露应用程序状态 (需要自己实现),在jvm内部用单独的线程以server运行。外部client可以通过jmx协议访问,然后输出到其他terminal上面(比如 opentsdb, ganglia等,jmxtrans就是做这个事情的)。
我大致阅读了一下代码,在server有两个比较重要的概念:agent(mbean server)和mxbean. agent(mbean server)类似server启动,mxbean则是各个data source. 但是从jdk默认的实现(ManagementFactory::getPlatformMBeanServer)里面可以看到,mxbean不是一个静 态基类,而是通过反射的方式将mxbean类转换成为DynamicMBean(猜测数据传输格式应该是JPO,Java Persistent Object,也就是java对象自带序列化方式,这种方式的好处就是没有限制data source format,但是却复杂了实现)。
float类型数值常量后面加上F比如3.042F,而double类型数值常量后面加上D比如3.402D.所有浮点数值计算都遵循IEEE 752规范。java提供了三种表示溢出或者计算错误的三种特殊浮点数值:
对于较大浮点数应该使用BigDecimal来进行计算。
java虚拟机规范强调可移植性,对于在任何机器上来说相同的程序得到的结果应该是相同的。但是对于浮点计算的话,比如Intel CPU针对于浮点数计算所有中间结果都使用bit 80表示,而最后截取bit 64,造成和其他CPU计算结果不同。为了达到可移植性,java规范所有中间结果必须使用bit 64截断,但是遭反对,因此java提供了strictfp关键字标记某个方法,对于这个方法里面所有浮点数计算,所有中间结果使用64 bit截断,否则使用适合native方式计算。另外一些浮点数计算比如pow2,pow3,sqrt的话,一方面依赖于CPU浮点计算方式,另外一方面 依赖于本身算法(如果CPU本身提供这种指令的话就可以使用CPU指令),也会造成不可移植性,比如Math.sqrt.如果希望在这方面也达到同样效果 的话,可以使用StrictMath类,底层使用fdlibm,以确保所有平台上得到相同的结果。
The JNI is designed to handle situations where you need to combine Java applications with native code. As a two-way interface, the JNI can support two types of native code: native libraries and native applications. (允许相互调用)
下面是一些JNI的代替方案 NOTE(dirlt):主要都是通过进程间通信来完成的
下面是一些JNI比较适合的场景
主要介绍的是native methods编写,JVM通过so来调用native methods.这里给出一个无参native mthods例子。
/* coding:utf-8
* Copyright (C) dirlt
*/
public class Hello {
private static native void run();
public static void main(String[] args) {
System.loadLibrary("Hello");
run();
}
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: run
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_run
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
/* coding:utf-8 * Copyright (C) dirlt */ #include <cstdio> #include "Hello.h" JNIEXPORT void JNICALL Java_Hello_run (JNIEnv * env, jclass cls) printf("Hello,World\n"); }
NOTE(dirlt):可以看到在pointer下面还有一个internal VM数据块,这个是线程级别的私有数据跟在pointer之后,可以通过指针偏移进行访问
对于类型还说分为primitive和reference两种类型,reference type都是opaque pointer获取数据必须通过JNIEnv接口提供的方法才能够获得。 至于具体类型是pritmitive还是reference的话,可以通过阅读jni.h和jni_md.h来判断。primitive type只有下面几种 NOTE(dirlt):in jni_md.h
#ifndef _JAVASOFT_JNI_MD_H_ #define _JAVASOFT_JNI_MD_H_ #define JNIEXPORT #define JNIIMPORT #define JNICALL typedef int jint; #ifdef _LP64 /* 64-bit Solaris */ typedef long jlong; #else typedef long long jlong; #endif typedef signed char jbyte; #endif /* !_JAVASOFT_JNI_MD_H_ */
NOTE(dirlt):field和method的访问接口,非常类似google::protobuf提供的reflection接口
Field操作:
Method操作:
Cache fieldID/methodID:
Let us start by comparing the cost of Java/native calls with the cost of Java/Java calls. Java/native calls are potentially slower than Java/Java calls for the fol-lowing reasons: (Java/Java calls和Java/native calls的对比,Java/native calls通常更慢):
The overhead of field access using the JNI lies in the cost of calling through the JNIEnv. Rather than directly dereferencing objects, the native code has to per- form a C function call which in turn dereferences the object. The function call is necessary because it isolates the native code from the internal object representa-tion maintained by the virtual machine implementation. The JNI field access over-head is typically negligible because a function call takes only a few cycles.(字段访问开销主要是通过一次得到ID间接访问造成的,但是这样带来的收益是能够将内部object表示不暴露出来,但是索性的是带来的开 销并不大)
NOTE(dirlt):我理解这里的意思主要是说在调用和字段访问方面,Java/native calls的开销更大,但是native methods本身在运行速度上可能会带来更大的收益
reference和GC非常相关,决定了哪些对象作用域多大以及生命周期多长:
分为三类references:
JNI programmers may deal with an exception in two ways:
It is extremely important to check, handle, and clear a pending exception before calling any subsequent JNI functions.
native code如果不处理异常的话,可以直接返回交给caller来处理异常。如果是自己处理异常的话,获得具体异常之后最好立刻清除状态,然后做后续操作。
Calling most JNI functions with a pending exception leads to undefined results. The following is the complete list of JNI functions that can be called safely when there is a pending exception:
NOTE(dirlt):代码整个过程还是比较清晰的
/* coding:utf-8 * Copyright (C) dirlt */ #include <cstdio> #include <cstdlib> #include <jni.h> static JNIEnv* env; static JavaVM* jvm; void destroy() { if (env->ExceptionOccurred()) { env->ExceptionDescribe(); } jvm->DestroyJavaVM(); } int main() { JavaVMInitArgs vm_args; JavaVMOption options[1]; options[0].optionString = "-Djava.class.path=."; vm_args.version = JNI_VERSION_1_6; vm_args.options = options; vm_args.nOptions = 1; vm_args.ignoreUnrecognized = JNI_TRUE; /* Create the Java VM */ jint res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); if (res < 0) { // can't create jvm. fprintf(stderr, "Can't create Java VM\n"); exit(1); } jclass cls = env->FindClass("Hello"); if (cls == NULL) { // can't find class. destroy(); } jmethodID mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V"); if (mid == NULL) { // no main method. destroy(); } jstring jstr = env->NewStringUTF(" from C!"); if (jstr == NULL) { destroy(); } jclass stringClass = env->FindClass("java/lang/String"); jobjectArray args = env->NewObjectArray(1, stringClass, jstr); if (args == NULL) { destroy(); } env->CallStaticVoidMethod(cls, mid, args); destroy(); }
➜ ~ g++ Hello.cc -I$JAVA_HOME/include -L$JAVA_HOME/jre/lib/amd64/server -ljvm Hello.cc: In function ‘int main()’: Hello.cc:22:29: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings] ➜ ~ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/server ➜ ~ ./a.out Hello,World
可以通过创建一个JVM来将多个线程attach上去,相当于这个JVM启动的多个线程。这里的线程使用的是OS native thread实现。
load/unload工作流程是这样的:
NOTE(dirlt):因此如果ClassLoader里面关键了global reference的话那么这个class loader是不会被卸载的
如何使用现有的native library:
NOTE(dirlt):个人觉得one-to-one mapping虽然实现比较麻烦,可是用起来比较简单,而shared stubs则相反。自己完全可以实现一些简单的common library来简化编写过程
Locating Native Libraries
Linking Native Methods
Passing Data
使用reference的好处可以使得访问数据更加灵活。
Accessing Objects
插件 Tools->Plugins
HPROF: A Heap/CPU Profiling Tool
使用java -agentlib:hprof=help可以察看hprof的调用方式
HPROF: Heap and CPU Profiling Agent (JVMTI Demonstration Code) hprof usage: java -agentlib:hprof=[help]|[<option>=<value>, ...] Option Name and Value Description Default --------------------- ----------- ------- heap=dump|sites|all heap profiling all cpu=samples|times|old CPU usage off monitor=y|n monitor contention n format=a|b text(txt) or binary output a file=<file> write data to file java.hprof[{.txt}] net=<host>:<port> send data over a socket off depth=<size> stack trace depth 4 interval=<ms> sample interval in ms 10 cutoff=<value> output cutoff point 0.0001 lineno=y|n line number in traces? y thread=y|n thread in traces? n doe=y|n dump on exit? y msa=y|n Solaris micro state accounting n force=y|n force output to <file> y verbose=y|n print messages about dumps y Obsolete Options ---------------- gc_okay=y|n Examples -------- - Get sample cpu information every 20 millisec, with a stack depth of 3: java -agentlib:hprof=cpu=samples,interval=20,depth=3 classname - Get heap usage information based on the allocation sites: java -agentlib:hprof=heap=sites classname Notes ----- - The option format=b cannot be used with monitor=y. - The option format=b cannot be used with cpu=old|times. - Use of the -Xrunhprof interface can still be used, e.g. java -Xrunhprof:[help]|[<option>=<value>, ...] will behave exactly the same as: java -agentlib:hprof=[help]|[<option>=<value>, ...] Warnings -------- - This is demonstration code for the JVMTI interface and use of BCI, it is not an official product or formal part of the JDK. - The -Xrunhprof interface will be removed in a future release. - The option format=b is considered experimental, this format may change in a future release.
代码处理选项部分还是比较诡异的,可以看看代码是如何处理的 https://cluster.earlham.edu/trac/bccd-ng/browser/branches/skylar-install_jdk/trees/software/bccd/software/jdk1.6.0_14/demo/jvmti/hprof/src/hprof_init.c?rev=1854
How Does HPROF Work?