优质博文:IT-BLOG-CN
Zstandard
(简称zstd
)是一种无损压缩算法,由Facebook
开发并开源。它旨在提供高压缩比和高解压速度的平衡,适用于多种数据压缩需求。
特点
【1】高压缩比: zstd
能够在保持较高压缩比的同时,提供比其他压缩算法更快的压缩和解压速度。
【2】快速解压: 解压速度非常快,适合需要高效解压的应用场景。
【3】可调压缩级别: zstd
支持多种压缩级别,从快速压缩到高压缩比,可以根据具体需求进行调整。
【4】流压缩: 支持流式压缩和解压,适合处理大文件或数据流。
【5】多线程支持: zstd
支持多线程压缩,能够充分利用多核处理器的性能。
用途
【1】数据存储: 用于压缩存储在磁盘上的数据,以减少存储空间占用。
【2】数据传输: 在网络传输中使用zstd压缩数据,可以减少传输时间和带宽消耗。
【3】日志压缩: 用于压缩日志文件,减少日志存储空间。
【4】数据库: 一些数据库系统使用zstd来压缩数据页,提升存储效率。
【5】文件系统: 某些文件系统支持zstd压缩,以提高存储效率。
示例代码:以下是一个简单的Java
示例,演示如何使用zstd进行压缩和解压:
import com.github.luben.zstd.Zstd;
import java.nio.charset.StandardCharsets;public class ZstdExample {public static void main(String[] args) {String originalString = "This is a string to be compressed using zstd!";byte[] originalData = originalString.getBytes(StandardCharsets.UTF_8);// 压缩数据byte[] compressedData = Zstd.compress(originalData);// 解压数据byte[] decompressedData = Zstd.decompress(compressedData, originalData.length);String decompressedString = new String(decompressedData, StandardCharsets.UTF_8);System.out.println("Original: " + originalString);System.out.println("Compressed size: " + compressedData.length);System.out.println("Decompressed: " + decompressedString);}
}
我们在使用zstd
组件时发现有一个问题,容易造成内存泄露,大家如有使用或即将使用时要注意下,具体分析如下:
大家能从使用代码上看出问题来吗?看着非常正常。
try (ByteArrayInputstream byteInputstream = new ByteArrayInputstream(obj. getBytes(encoding));ByteArrayoutputstream byteOutputstream new ByteArrayoutputstream()Zstdoutputstream zstdoutputstream = new Zstdoutputstream(byteoutputstream))(byte[] buffer . new byte[CompressConstant.DEFAULT_SIZE_512];int count;while((count= byteInputStream. read(buffer, 0, CompressConstant. DEFAULT SIZE 512))1=-1){zstdoutputstream.write(buffer, , count);}zstdoutputStream.flush();zstdoutputStream.close();
Zstd
原码, 重写了finalize
,这就是问题根源。
package com.github.luben.zstd;
import java.io.Outputstream;
import java.io.FilterOutputStream;
import java.io. IOException;
import com.github.luben.zstd.util.Native;public class Zstdoutputstream extends Filteroutputstream(static{}/** Opaque pointer to Zstd context object */private long stream;private long srcPos - 0;private long dstPos = 0;private byte[] dst = null;private boolean isclosed.false;private static final int dstSize =(int) recommendedCOutSize();private boolean closeFrameonFlush;private boolean useChecksum;private boolean frameClosed . true;private int level;private byte[] dict = null;private ZstdDictCompress fastDict . null;/* JNI methods */private static native long recommendedcoutsize();private static native long createcstream();private static native int freeCStream(long ctx);private native int initCStream(long ctx, int level, int checksum);private native int initCStreamithDict(long ctx, byte[] dict, int dict_size, int level, int checksum);private native int initCStreamwithFastDict(long ctx, ZstdDictCompress dict, int checksum);private native int compressstream(long ctx, byte[] dst, int dst_size, byte[] src, int src_size);private native int flushStream(long ctx, byte[] dst, int dst_size);private native int endStream(long ctx, byte[] dst, int dst_size);/* The constuctor */public Zstdoutputstream(Outputstream outstream, int level, boolean closeFrameOnFlush, boolean useChecksum) throws IOException(}public synchronized void write(byte[] src, int offset, int len) throws IOException (}public void write(int i) throws IOException(}/***Flushes the output*/public synchronized void flush() throws IOException {}public synchronized void close() throws IOException {}@overrideprotected void finalize() throws Throwable(close();}
咱们来了解下finalize
:
finalize
是Object
对象里的一个方法,它最大的一个特点是执行时机不可预知。实现了object
的finalize
方法的类,在创建时会新建一个FinalizerReference
,这个对象是强引用类型,重写finallize
的对象如果没有被其它对象引用时,执行GC
不会马上被清除掉,而是放入到一个静态队列中,有一个守护线程专门去处理这个队列,因为守护线程的优先级比较低,运行的时间少,如果在短时间内创建较多或较大对象时,无论怎么调用GC
都无用,只能等到守护线程从队列中取出finalize
方法,FinalizerReference
才能被回收。
Object.finalize
方法是Java
中定义在java.lang.Object
类中的一个方法。它的主要作用是在对象被垃圾回收器回收之前进行一些清理操作。具体来说,finalize
方法允许开发者在对象被销毁之前执行一些必要的清理工作,比如释放资源(文件、网络连接等)。
作用:
资源释放:在对象被垃圾回收前释放系统资源,如关闭文件、网络连接等。
清理操作:执行一些清理工作,确保对象的状态一致性。
优点
资源管理:在某些情况下,finalize
方法可以用来确保资源被正确释放,避免资源泄漏。
自动调用:在垃圾回收时自动调用,无需手动触发。
缺点
不确定性:finalize
方法的调用时间是不确定的,垃圾回收器何时调用finalize
方法是不可预测的,这可能导致资源长时间未被释放。
性能开销:使用finalize
方法会增加垃圾回收的开销,影响性能。
复杂性: finalize
方法容易引入复杂的错误和难以调试的问题,因为它是在垃圾回收的过程中异步调用的。
替代方案: Java 7
引入了try-with-resources
语法和AutoCloseable
接口,这些提供了更好的资源管理方式,通常不需要依赖finalize
方法。
实际使用
由于上述缺点,finalize
方法在实际开发中已经很少被使用。现代Java
开发更倾向于使用try-with-resources
语法和AutoCloseable
接口来管理资源。例如:
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {// 读取文件内容
} catch (IOException e) {e.printStackTrace();
}
// 这里BufferedReader会自动关闭,无需依赖finalize
看下Finalizer
类的原码:
final class Finalizer extends FinalReference<Object> {
/* Package-private; must be in
same package as the Reference
class */private static ReferenceQueue<Object> queue = new ReferenceQueue<>();private static Finalizer unfinalized = null,private static final Object lock = new Object();
private Finalizer(Object finalizee){super(finalizee, queue);add();
}
/* Invoked by VM */
static void register(Object finalizee) { new Finalizer(finalizee); }
static {ThreadGrouptg=Thread.currentThread().getThreadGroup();for (ThreadGroup tgn = tg;tgn != null;tg = tgn, tgn = tg.getParent());Thread finalizer = new FinalizerThread(tg);___finalizer.setPriority(Thread.MAX PRIORITY - 2);finalizer.setDaemon(true);|finalizer.start();
}
private static class FinalizerThread extends Thread {private volatile boolean running;FinalizerThread(ThreadGroup g){super(g, name: "Finalizer");}public void run() {if(running)return;// Finalizer thread starts before System.initializeSystemClass// is called. Wait until JavaLangAccess is availablewhile(!VM.isBooted()){// delay until VM completes initialization try {VM.awaitBooted();} catch (InterruptedException x){// ignore and continue}}final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();running = true;for(;;){try {Finalizer f = (Finalizer)queue.remove();_f.runFinalizer(jla);} catch(InterruptedException x){// ignore and continue}
}
看下重写了finalize
方法的效果,因为我们的对象比较大且请求次数多,所以发上生产就能立马见效。
Dump
的结果:
解决方法有两个:
【1】直接用Zstd
的compress(byte[] src),decompress(byte[] src, int originalSize)
;
【2】写一个类继承ZstdOutputStream
,重载finalize
,方法内置空。
大家在写代码时,尽量不要重写object
的finalize
.