我对Java语言的理解
去年七月初通过校招进入了现在的公司,转眼已经过去一年多了,经过一年多的实战开发,重新整理一下自己对java的理解,希望能对正在面试以及即将入职工作的blogger有所帮助。
1、平台无关性
Java源码首先被编译成字节码,再由不同的平台的JVM(Java Virtual Machine)进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。
下面我们简单看一下jvm执行的字节码,命令 javap
public class T {
public static void main(String[] args) {
int i = 1;
System.out.println(i++);
}
}
使用 javap查看字节码
2、JVM加载class文件
JVM如何加载编译好的class文件呢?
Java虚拟机:是一种抽象化的虚拟机,通过在实际计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统,JVM屏蔽了与具体操作系统windows、linux等相关的信息,使得Java程序只需生成在Java虚拟机上运行的代码及字节码,就可以在多种平台上不加修改的运行。
通常呢,我们不需要知道JVM的运行原理,只需要专注于Java代码就可以了,当然,这也是虚拟机之所以存在的原因,即屏蔽底层操作系统的不同,并且,减少基于原生语言的开发的复杂性。只要虚拟机厂商在特定操作系统上实现了虚拟机,定义如何将字节码解析成本操作系统可执行的二进制码,java语言便能跨越各种的平台。
JVM的架构 = Class Loader + Runtime Data Access + Execution Engine + Native Interface
Class Loader: 依据特定格式,加载class文件到内存。
Execution Engine: 对命令进行解析,即解析class文件中的字节码,解析完成之后,提交操作系统执行。
Runtime Data Area: JVM内存空间结构模型,我们所写的程序都会被加载到这里,之后才开始运行。
Native Interface: 融合不同开发语言的原生库为Java所用,JVM开辟了一块区域用来专门处理标记为native的代码,具体做法是,在Native Method Stack中登记native方法,在Excution执行时,加载Native Libraries。
Class.forName中调用的forName0
/**
* Returns the {@code Class} object associated with the class or
* interface with the given string name. Invoking this method is
* equivalent to:
*
* <blockquote>
* {@code Class.forName(className, true, currentLoader)}
* </blockquote>
*
* where {@code currentLoader} denotes the defining class loader of
* the current class.
*
* <p> For example, the following code fragment returns the
* runtime {@code Class} descriptor for the class named
* {@code java.lang.Thread}:
*
* <blockquote>
* {@code Class t = Class.forName("java.lang.Thread")}
* </blockquote>
* <p>
* A call to {@code forName("X")} causes the class named
* {@code X} to be initialized.
*
* @param className the fully qualified name of the desired class.
* @return the {@code Class} object for the class with the
* specified name.
* @exception LinkageError if the linkage fails
* @exception ExceptionInInitializerError if the initialization provoked
* by this method fails
* @exception ClassNotFoundException if the class cannot be located
*/
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
/** Called after security check for system loader access checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
3、谈谈反射
Java反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
简单例子
public class Dog {
private String name;
public void sayHi(String hi) {
System.out.println(hi + " " + name);
}
private String returnHello(String hello) {
return hello;
}
}
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class c = Class.forName("Dog");
if(!c.isAssignableFrom(Dog.class))
return ;
Dog dog = (Dog) c.newInstance();
System.out.println("Class name is " + c.getName());
Method returnHello = c.getDeclaredMethod("returnHello", String.class);
returnHello.setAccessible(true);
String str = (String) returnHello.invoke(dog, "ao wu");
System.out.println(str);
Method sayHi = c.getMethod("sayHi", String.class);
sayHi.invoke(dog, "hi");
Field name = c.getDeclaredField("name");
name.setAccessible(true);
name.set(dog, "Doge");
sayHi.invoke(dog, "hi");
}
}
编译运行
Class name is Dog
ao wu
hi null
hi Doge
通过这个例子,我们可以知道,反射就是把Java类中的各种成分映射成一个个Java对象,Method、Field、Class。
4、ClassLoader
在上面我们之所以能够获得类的属性或者方法,并对其进行调用,必须要获取Class对象,而要获取该类的Class对象必先获取该类所对应字节码文件对象。
编译器将Dog.java源文件编译成Dog.class字节码文件。
ClassLoader将字节码转换成JVM中的Class<Dog>对象。
JVM利用Class<Dog>对象实例化为Dog对象。
ClassLoader: ClassLoader在Java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装进系统,然后交给Java虚拟机进行连接、初始化等操作。
ClassLoader的种类
BootStrapClassLoader: C++编写,加载Java自带的核心类库java.\*,比如java.lang包。
ExtClassLoader: Java编写,加载扩展库javax.\*,加载位于jre/lib/ext目录下的jar包。
AppClassLoader: Java编写,加载程序所在目录。
自定义ClassLoader: Java编写,定制化加载。
我们可以看一下ExtClassLoader的代码
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
可以看一下 "java.ext.dirs" 的路径
System.out.println(System.getProperty("java.ext.dirs"));
C:\ProgramFiles\Java\jdk1.8.0_131\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext
我们可以看一下AppClassLoader的代码
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
可以看一下 "java.class.path" 的路径
System.out.println(System.getProperty("java.class.path"));
C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar
;C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;
D:\IdeaProjects\JavaStudy\target\classes;
D:\maven\repository\org\projectlombok\lombok\1.18.4\lombok-1.18.4.jar;
D:\maven\repository\org\springframework\boot\spring-boot-starter-security\2.1.6.RELEASE\spring-boot-starter-security-2.1.6.RELEASE.jar;
D:\maven\repository\org\springframework\boot\spring-boot-starter\2.1.6.RELEASE\spring-boot-starter-2.1.6.RELEASE.jar;
D:\maven\repository\org\springframework\boot\spring-boot\2.1.6.RELEASE\spring-boot-2.1.6.RELEASE.jar;
D:\maven\repository\org\springframework\boot\spring-boot-autoconfigure\2.1.6.RELEASE\spring-boot-autoconfigure-2.1.6.RELEASE.jar;
D:\maven\repository\org\springframework\boot\spring-boot-starter-logging\2.1.6.RELEASE\spring-boot-starter-logging-2.1.6.RELEASE.jar;
D:\maven\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;
D:\maven\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;
D:\maven\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;
D:\maven\repository\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;
D:\maven\repository\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\maven\repository\org\slf4j\jul-to-slf4j\1.7.26\jul-to-slf4j-1.7.26.jar;
D:\maven\repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;
D:\maven\repository\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;D:\maven\repository\org\springframework\spring-aop\5.1.8.RELEASE\spring-aop-5.1.8.RELEASE.jar;
D:\maven\repository\org\springframework\spring-beans\5.1.8.RELEASE\spring-beans-5.1.8.RELEASE.jar;
D:\maven\repository\org\springframework\security\spring-security-config\5.1.5.RELEASE\spring-security-config-5.1.5.RELEASE.jar;
D:\maven\repository\org\springframework\security\spring-security-core\5.1.5.RELEASE\spring-security-core-5.1.5.RELEASE.jar;
D:\maven\repository\org\springframework\spring-context\5.1.6.RELEASE\spring-context-5.1.6.RELEASE.jar;
D:\maven\repository\org\springframework\security\spring-security-web\5.1.5.RELEASE\spring-security-web-5.1.5.RELEASE.jar;
D:\maven\repository\org\springframework\spring-expression\5.1.6.RELEASE\spring-expression-5.1.6.RELEASE.jar;
D:\maven\repository\org\springframework\spring-web\5.1.6.RELEASE\spring-web-5.1.6.RELEASE.jar;
D:\maven\repository\org\springframework\spring-core\5.1.5.RELEASE\spring-core-5.1.5.RELEASE.jar;
D:\maven\repository\org\springframework\spring-jcl\5.1.5.RELEASE\spring-jcl-5.1.5.RELEASE.jar;
C:\Program Files\JetBrains\IntelliJ IDEA 2018.2.1\lib\idea_rt.jar
自定义ClassLoader的实现
关键函数 findClass、defineClass
/**
* Finds the class with the specified <a href="#name">binary name</a>.
* This method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* the {@link #loadClass <tt>loadClass</tt>} method after checking the
* parent class loader for the requested class. The default implementation
* throws a <tt>ClassNotFoundException</tt>.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*
* @since 1.2
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
/**
* Converts an array of bytes into an instance of class <tt>Class</tt>.
* Before the <tt>Class</tt> can be used it must be resolved.
*
* <p> This method assigns a default {@link java.security.ProtectionDomain
* <tt>ProtectionDomain</tt>} to the newly defined class. The
* <tt>ProtectionDomain</tt> is effectively granted the same set of
* permissions returned when {@link
* java.security.Policy#getPermissions(java.security.CodeSource)
* <tt>Policy.getPolicy().getPermissions(new CodeSource(null, null))</tt>}
* is invoked. The default domain is created on the first invocation of
* {@link #defineClass(String, byte[], int, int) <tt>defineClass</tt>},
* and re-used on subsequent invocations.
*
* <p> To assign a specific <tt>ProtectionDomain</tt> to the class, use
* the {@link #defineClass(String, byte[], int, int,
* java.security.ProtectionDomain) <tt>defineClass</tt>} method that takes a
* <tt>ProtectionDomain</tt> as one of its arguments. </p>
*
* @param name
* The expected <a href="#name">binary name</a> of the class, or
* <tt>null</tt> if not known
*
* @param b
* The bytes that make up the class data. The bytes in positions
* <tt>off</tt> through <tt>off+len-1</tt> should have the format
* of a valid class file as defined by
* <cite>The Java™ Virtual Machine Specification</cite>.
*
* @param off
* The start offset in <tt>b</tt> of the class data
*
* @param len
* The length of the class data
*
* @return The <tt>Class</tt> object that was created from the specified
* class data.
*
* @throws ClassFormatError
* If the data did not contain a valid class
*
* @throws IndexOutOfBoundsException
* If either <tt>off</tt> or <tt>len</tt> is negative, or if
* <tt>off+len</tt> is greater than <tt>b.length</tt>.
*
* @throws SecurityException
* If an attempt is made to add this class to a package that
* contains classes that were signed by a different set of
* certificates than this class (which is unsigned), or if
* <tt>name</tt> begins with "<tt>java.</tt>".
*
* @see #loadClass(String, boolean)
* @see #resolveClass(Class)
* @see java.security.CodeSource
* @see java.security.SecureClassLoader
*
* @since 1.1
*/
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
自定义装载简单demo
public class T {
static {
System.out.println("Hello I'm T");
}
}
import java.io.*;
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoadername;
public MyClassLoader(String path, String classLoadername) {
this.path = path;
this.classLoadername = classLoadername;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = classLoaderName(name);
return defineClass(name, bytes, 0, bytes.length);
}
private byte[] classLoaderName(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while((i = in.read()) != -1) {
out.write(i);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
public String getClassLoadername() {
return classLoadername;
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader myClassLoader = new MyClassLoader("C:\\Users\\Administrator\\Desktop\\", "myClassLoader");
Class c = myClassLoader.loadClass("T");
System.out.println(myClassLoader.getClassLoadername());
System.out.println(myClassLoader.getParent());
System.out.println(myClassLoader.getParent().getParent());
System.out.println(myClassLoader.getParent().getParent().getParent());
c.newInstance();
}
}
运行测试main函数
myClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5ca881b5
null
Hello I'm T
进程完成,退出码 0
5、类加载器的双亲委派机制
不同ClassLoader加载路径不同,逻辑明确,为了实现分工,各自负责各自的区块。
ClassLoader的loadClass源码如下,感兴趣可以了解一下。
/**
* Loads the class with the specified <a href="#name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader. If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* <tt>resolve</tt> flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
*
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
自底向上检查名字为name的类是否已被装载,如果没有,自顶向下装载名字为name的类,关键代码如下:
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
上述代码也正可以和输出结果做对比,当parent为null时,则会调用我们的BootStrapClassLoader。
myClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5ca881b5
null
Hello I'm T
进程完成,退出码 0
// return null if not found
private native Class<?> findBootstrapClass(String name);
注意,findBootstrapClass为native标记的方法,在运行时,会调用本地库或外置的非Java代码。
如何确定findBootstrapClass为C++代码呢?查看网址[http://hg.openjdk.java.net/][http_hg.openjdk.java.net],一下为ClassLoader.c截图
查看源码之后,再来回答一下,为什么要使用双亲委派机制:避免多分同样字节码的加载。
6、loadClass和forName的区别
隐式加载: new,隐式调用类加载器,加载对应类,创建对应实例。
显式加载: loadClass、forName等,显式加载构建Class对象,然后调用Class的newInstance创建实例。
loadClass和forName: 都能知道该类的所有属性和方法,对于任一对象都能调用它的任意防范和属性。
loadClass: ClassLoader.loadClass得到的class是还没有链接的。
forName: Class.forName得到的class是已经初始化完成的。
类的装载过程
来看一下下面的小demo
public class T {
static {
System.out.println("Hello I'm T");
}
}
public class T2 {
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("我是ClassLoader.loadClass");
ClassLoader cl = T.class.getClassLoader();
Class<T> tLoadClass = (Class<T>) cl.loadClass("T");
System.out.println("我是Class.forName");
Class<T> tForName = (Class<T>) Class.forName("T");
}
}
运行T2的main方法:
我是ClassLoader.loadClass
我是Class.forName
Hello I'm T
从上述执行结果,我们可知道,Class.forName得到的class是已经初始化完成的,ClassLoader.loadClass得到的class是还没有链接的。比如,我们在程序中要加载我们的数据库驱动Driver,我们要使Class.forName。而为什么还会有ClassLoader.loadClass,主要是与LazyLoading有关,ClassLoader.loadClass不需要执行类的链接和初始化,加快了初始化速度。
7、Java的内存模型
线程私有:程序计数器、虚拟机栈、本地方法栈。
线程共享:MetaSpace、Java堆。
Java虚拟机栈(Stack)
Java方法执行的内存模型。
包含多个栈帧。
局部变量表和操作数栈
局部变量表:包含方法执行过程中的所有变量。
操作数栈:入栈、出栈、复制、交换、产生消费变量。
递归为什么会引发 java.lang.StackOverflowError 异常: 当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压入虚拟机栈中,当方法执行完,便会将栈帧出栈,因此可知,线程当前执行的方法对应的栈帧位于Java栈的顶部,而我们的递归函数不断的去调用自身,每一次方法调用会涉及:(1)每新调用一个方法就会生成一个栈帧;(2)会保存当前方法的栈帧状态,将它放在虚拟机栈中;(3)栈帧上下文切换,会切换到最新的方法栈中,而我们每个虚拟机栈是固定的,递归过深,栈帧数超过虚拟机栈深度。解决思路:(1)限制递归次数;(2)使用循环替换递归等等。
虚拟机栈过多引发 java.lang.OutOfMemoryError异常: 当虚拟机栈可以动态扩展时,如果无法申请足够多的内存,就会抛出这个异常。
public void stackLeakByThread() {
while(true) {
new Thread(() -> {
while(true) {}
}).start();
}
}
本地方法栈
与虚拟机相似,主要作用于标注了native的方法。
元空间(MetaSpace)与永久代(PermGen)
存储class的相关信息,包括class的method、field等等,两者均是方法区的实现,方法区知识JVM的一种规范。Java1.7之后,原先位于方法区中的字符串常量池已被移动到了Java堆当中,并且,1.8之后,使用元空间替代了永久代。 元空间使用本地内存,而永久代使用的是jvm的内存。
Java堆(Heap)
对象实例的分配区域
GC管理的主要区域
还没有评论,来说两句吧...