Java

Java 垃圾回收机制

Submitted by Lizhe on Tue, 05/16/2017 - 11:35

Java如何进行GC已经是一个老话题了

这里我们分成两个部分讨论  一是回收方式(引用计数器或者移动对象方式等) ,二是GC算法(Concurrent或者非Concurrent等)

通常情况下要进行GC就要知道 

1. 哪些对象需要回收 (标记空对象)

2. 如何重新分配内存 (先释放对象,然后整理压缩内存空间)

现代jvm几乎都使用了分代回收的方式 (即划分成新生代和老生代) , 通过对象的年龄(第一次回收存活的对象年龄为1,第二次仍然存活为2,依次类推)来处理对象是该处在新生代还是老生代

回收新生代的GC称为 Minor GC , 类似BigDecimal这种创建之后无法修改,而又经常需要丢弃的临时对象往往都是被Minor GC回收掉的

当对象不停的被移动到老生代, 老生代填满之后需要进行Full GC

两种GC方式都需要暂停所有线程, jvm会进入一种"假死"的状态等待本次GC运行完成

那么有没有什么方法可以缩短这种"假死"状态的时间呢?

答案是 "在线程运行期间就开始查找整理不在使用的可回收对象" , 

JIT 编译器 2

Submitted by Lizhe on Mon, 05/15/2017 - 11:31

 

-XX:ReservedCodeCacheSize=1024m 可以帮助设置JIT编译的本地代码的最大容量

你可以简单的改大这个值以获取更多的本地代码空间, 不过内存始终是一种有限资源, 也可以通过编译阀值来控制编译行为

影响编译阀值的最主要因素是代码的执行频率,一旦执行达到了一定次数,就达到了编译阀值,编译器就可以获取足够的信息然后编译代码了

jvm通过两种值来确定代码的执行频率的

1. 方法调用计数器, 也就是方法被调用了多少次

2. 方法中的循环回边计数器, 也就是方法中的for或者while的运行次数, 全部循环结束或者continue都算做完整的循环回边

如果一段代码达到了编译条件, 就会被送进编译队列.

但是还有一种极端情况例如while(true), 这种循环永远不会退出, jvm会在其不中断运行的情况下直接stack上的代码(On-Stack Replacement, OSR)

普通的编译触发可以通过-XX:CompileThreshold=N修改, client的默认值是1500, server的默认值是10000

OSR的阀值不同由公式

JIT 编译器

Submitted by Lizhe on Sun, 05/14/2017 - 20:22

 

JIT编译器 ( Just-In-Time ) , 是保证java程序运行性能的核心概念.

我们说C++之类的语言是编译型语言,而php或者perl之类的脚本语言一般为解释型语言

对于只运行一次的代码, 解释型语言更为灵活也更快, 因为节约了预处理(编译链接之类)的步骤

对于运行多次的代码, 编译型语言则更快(二进制码的运行速度要优于可读代码)

这里我们知道java实际上是一种混合语言,它会把java代码先编译成字节码,然后在jvm上运行, 但是实际上jvm运行的也不全是java字节码,对于调用次数最多的一些代码,jvm会在运行多次以后将其编译成跟C++一样的二进制码,从而达到更快的运行速度

这里不同的jvm虚拟机的处理方式是不一样的, Oracle的HotSpot JVM只会将"最热"的一部分代码进行彻底编译

1. 编译器的类型

编译器的类型分为两种, 一种是client 另一种是server

你需要使用 java -client 或者 java -server 来使用它们

两种编译器的主要区别在于编译代码的时机不同(这里指的是编译成为二进制码的时机而不是javac编译字节码的时机)

java nio 异步通道

Submitted by Lizhe on Wed, 04/19/2017 - 16:55

 

传统的java代码当需要异步处理io时往往需要手写的多线程支持,nio包新增了3个异步通道

AsynchronousFileChannel 用于文件

AsynchronousSocketChannel 用于套接字,支持超时

AsynchronousServerSocketChannel 用于套接字接受服务端

当你希望由主线程发起io操作,并等待结果时,需要使用Future(对,就是current包下那个接口)来接收结果

相对应的它也提供一个回调的模式来处理结果

下面我尝试用AsynchronousFileChannel来读取一个文本文件的全部内容, 网上大部分的例子都是直接一次性将文件内容读入缓存

这里我们分几次读,因为实际生产环境中你很难碰到一个可以一次性读取的小文件还需要异步处理的

通常这种操作都是面向大文件

文件内容是 1234567890测试中文

package com.nio;

java nio 读写文件

Submitted by Lizhe on Wed, 04/19/2017 - 16:46

package com.nio;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestPath3 {

java nio 处理目录和目录树

Submitted by Lizhe on Wed, 04/19/2017 - 14:59

例子很简单,不过这里也有一个坑,就是path对应的系统文件也可能是不存在的,而且它的很多方法返回的都是Path类型

代码

        Path workspace = Paths.get("C:/DEV/workspace_study");
        Path p = workspace.resolve("Sample/src/com/nio/TestPath.java");
        System.out.println(p);

会给出

        C:\DEV\workspace_study\Sample\src\com\nio\TestPath.java


写成Path p = workspace.resolve("Sample/src/com/nio/TestPath.java2");

同样会给出C:\DEV\workspace_study\Sample\src\com\nio\TestPath.java2,不过注意这个java2文件不是真实存在的

另外 p.getFileName() 则会返回一个Path对象,而不是String

java8 stream 并发

Submitted by Lizhe on Fri, 04/14/2017 - 15:02

 

先随便写个代码打印1o个随机数,顺便加上parallel

package com.stream;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Lambda表达式的线程安全问题与闭包

Submitted by Lizhe on Fri, 04/14/2017 - 11:07

 

Lambda表达式所使用的外部变量不能是可变的局部变量 (也就是说它只能接受成员变量,final的局部变量和参数)

下面例子中的test就是一个外部变量

实际上就是说,它不能接受非final的保存在线程祯中的变量, 等等, 这句话看起来是不是有点眼熟, 为什么会有这样的限制呢

首先这个Lambda表达式是运行在一个独立的子线程中,当这个线程运行时,它实际上拿到的是一个原始变量的副本(一个拷贝)

类似于经典的swap(a,b)函数,我相信每一个在大学学过c语言的人都碰到过这个考试题 :P

还记得Java8推出之前的热点问题么, java是否需要引入闭包, 那么Lambda表达式是否符合闭包的含义呢

闭包本身是一个函数,一段代码,一个可以被执行的逻辑, 它可以随意访问自身外部的其他变量,

Java8的Lambda表达式和匿名类也可以做类似于闭包的事情,比如下面这个例子里的Lambda表达式就访问了一个外部变量test

一般情况下很多人接触闭包都是通过javascript, javascript的闭包实际上是通过子函数来实现的,因为在javascript中只有一个函数的子函数才能访问这个函数的局部变量